Example workflow supporting multiple user accounts with Gateway Standalone authentication - Platform - BlueCat Gateway - 25.3.0

Gateway Administration Guide

ft:locale
en-US
Product name
BlueCat Gateway
Version
25.3.0

When using Standalone authentication, Gateway supports authentication of a single user account. This might not be enough for your needs. To manage multiple user accounts while using a Standalone authentication, you can set up a separate user database and write custom authentication functions for Gateway to use.

Note: The following configuration and code snippets illustrate how to build custom authentication functions for Gateway Standalone authentication, where Gateway does not connect to Micetro or Address Manager. They do not apply otherwise.

Custom authentication functions replace the default authentication for Gateway Standalone authentication deployments. These functions should access the authentication system and database that you want your Standalone Gateway installation to use. The custom authentication functions must be inside a Python module script that is contained within the Built-In workspace. This means that the functions will be available only if Gateway is built into a Docker image with the custom authentication code before it is installed and deployed.

The example below uses the standard Gateway MongoDB workflow to authenticate users with the MongoDB client, allowing for authentication of multiple user credentials.

To set up authentication scripts that connect to a MongoDB database:

Important: For the purposes of illustration, the example below does not implement password hashing. For security reasons, workflow developers should make sure their own custom authentication functions implement password hashing and other techniques as appropriate for their authentication infrastructure.
  1. Create a MongoDB database to hold the user account authentication details.

    For more details, see Creating the MongoDB database and collection.

  2. Create a local directory for your Standalone authentication project.

  3. In the same directory, create a file named config.py with the following code, replacing each setting as appropriate:

    MONGO_HOST = '10.244.141.44' # Replace with your MongoDB host URI
    MONGO_PORT = 27017 # Replace with your MongoDB port number
    MONGO_DBNAME = 'gateway'  # Replace with your MongoDB Database name
    MONGO_USERNAME = 'go' # Replace with your MongoDB username
    MONGO_PASSWORD_PATH = 'customizations/.mongosecret' # Replace with your MongoDB password file location
    • MONGO_PASSWORD_PATH must be a relative path from the workspace root folder. It cannot be an absolute path.

    • The .mongosecret file referenced by MONGO_PASSWORD_PATH must be encrypted. This encryption must use the Secret key that is specified in the Gateway Certificates settings. (Click Settings at the bottom of the navigator on the left, expand Configurations, then click General configuration. Click Certificates to scroll to the Certificates section.)

      Tip: You can use Gateway's Encrypt password page to encrypt the password file. Make sure the secret key you're using to encrypt the file is the same one specified in the Certificates settings.

      Make sure that you store the encrypted .mongosecret file in the customizations subfolder under the local directory that you specified earlier.

  4. Within the same directory, create a custom authentication script named gw_custom_authentication.py.

    Add the following code to the gw_custom_authentication.py script:

    import base64
    import json
    from bluecat.util import get_portal_mongo_db
    def generate_token(username, password):
        """
        Generates a simple token
        """
        user_data = {
            'username': username,
            'password': password
        }
        # Convert the dictionary to a JSON string
        json_data = json.dumps(user_data)
        # Encode the JSON string to bytes and then to base64
        encoded_data = base64.b64encode(json_data.encode()).decode()
        return encoded_data
    def decode_token(token):
        """
        Decodes the token and returns the username and password
        """
        # Decode the base64 encoded data back to JSON string
        json_data = base64.b64decode(token).decode()
        # Convert the JSON string back to a dictionary
        user_data = json.loads(json_data)
        return user_data
    def retrieve_user_information(username: str, password: str) -> dict:
        """
        Validate the custom user credentials.
        :return: A dictionary contain the information of the custom user.
        """
        client = get_portal_mongo_db()
        users_collection = client.db.users
        username_identifier = {'username': username, 'password': password}
        user = users_collection.find_one(username_identifier)
        if user :
            token = generate_token(user.get('username'), user.get('password'))
            return {
                "username": user.get('username'),
                "token": token,
                "group": user.get('group')
            }
        return {}
    def retrieve_user_information_via_token(token: str) -> dict:
        """
        Validate the custom user token.
        :return: A dictionary contain the information of the custom user.
        """
        user = decode_token(token)
        client = get_portal_mongo_db()
        users_collection = client.db.users
        username_identifier = {'username': user.get('username'), 'password': user.get('password')}
        user = users_collection.find_one(username_identifier)
        if user:
            return {
                 "username": user.get('username'),
                "token": token,
                "group": user.get('group')
            }
        return {}

    This script implements the required retrieve_user_information() and retrieve_user_information_via_token() functions. These functions validate the user's credentials by either username/password or from a session token.

    Important: This code assumes that the MongoDB database implements the users collection with username, password, and group as strings. If your schema differs, change the code as needed to fetch and compare the indicated values.

    As described in Authentication function script format, if the credentials or token are valid, these functions will return the user's details in the following format:

    {
        "username": <User name>,
        "token": <Token>,
        "group": <Gateway group>,
    }
  5. To create a new Docker image with your custom authentication scripts:

    1. In the same directory where your custom authentication script is located, first create the following Dockerfile script (with the file name Dockerfile):

      FROM quay.io/bluecat/gateway:v25.3.0
      USER root
      
      COPY ./gw_custom_authentication.py /builtin/customizations/
      COPY ./.mongosecret /builtin/customizations/
      COPY ./config.py /builtin/
      
      # Ensure the content is available if the container is run with a custom User ID.
      RUN chgrp -R 0 /builtin && chmod -R g=u /builtin
      
      USER flask

      When executed, this file first copies required files to the builtin builtin/customizations folder in the built-in workspace. Then, it makes sure the folder is available to containers run with a custom User ID.

    2. To execute the Dockerfile you just created, run the following command from the same directory as the Dockerfile script you just created:

      docker build . -t <Custom project:Version>

      Where:

      • <Custom project is the a custom name for your project, with no spaces.

      • <Versionis a version number for your project, with no spaces. This can be useful during development to track different versions of your authentication scripts.

      Running this command creates a docker image named <Custom project:Version>.

  6. Run the container with this newly-created image. Use the same docker command you normally would, replacing the final --name parameter with <Custom project:Version>. For example:

    docker run -d \
    -p 80:8000 \
    -p 443:44300 \
    -v <Path to mapped workspace directory>:/bluecat_gateway/ \
    -v <Path to mapped logs directory>:/logs/ \
    -e AUTHENTICATION=Standalone \
    -e STANDALONE_USERNAME=<Standalone account user name>
    -e STANDALONE_PASSWORD=<Standalone account password> \
    --name bluecat_gateway <Custom project:Version> 
    Note: If you are mounting a custom workspace with a config.py file with the MongoDB configuration in it, the configuration in the custom workspace will have a higher precedence than the MongoDB configuration in the built-in workspace.
  7. You can now log in to Gateway as a user with your custom credentials, validated by MongoDB.

Setting up MongoDB without using the Gateway MongoDB workflow

The example above assumes you are configuring your MongoDB client through the Gateway UI. If desired, you can also create the MongoDB client from the gw_custom_authentication.py script itself, using the Flask-PyMongo package that comes installed with Gateway.

To do so, add the following to gw_custom_authentication.py:

from flask_pymongo import PyMongo
from flask import current_app
def get_mongo_client():
    return PyMongo(current_app, "mongodb://<User name>:<Password>@<Host>:<Port>/<Database>")

Creating the MongoDB database and collection

The following steps describe how to create a MongoDB database and collection, and insert data into the new collection. You can use this to test the example authentication scripts.

  1. Connect to the MongoDB deployment. To do so, run the following command on the machine to which MongoDB is deployed:

    mongosh
    Note: The default MongoDB port is 27017. To use a different port, run the following command instead:
    mongosh "mongodb://localhost:<Port number>"
    Tip: For other ways to connect to a MongoDB deployment, see Connect to a Deployment.
  2. Create a new MongoDB database. To do so, run the following command in the MongoDB shell:

    use <Database name>

    This command creates a new database and switches to that database.

  3. Create a new collection in the database and add data for the list of users. To do so, run the following command:

    db.users.insertOne({username: "mongoExampleUser", password: "mongoExampleUser", group: "all"})

    This command creates a new collection called users and adds a sample user named mongoExampleUser. This collection will store a list of user details including username, password and group.

  4. Create a new user in the new database for BlueCat Gateway to use when connecting to the MongoDB database.

    BlueCat Gateway needs a username and password to connect to a MongoDB instance. These details must match those used in the config.py file used to set BlueCat Gateway's MongoDB configuration.

    To create this user, run the following command in the MongoDB shell:

    db.createUser ({
      user: "<Username>",
      pwd: "<Password>",
      roles: [
        { role: "readWrite", db: "<Database name>" }
      ]
    })

    Replace <Database name> with the name of the MongoDB database that you specified earlier.

    To confirm that the user was created successfully, run the following command:

    show users

    You now have a MongoDB database that BlueCat Gateway can log into, using the username and password specified in Step 3 as <Username> and <Password>. This username and password should be used when setting up the config.py file during main authentication script setup, with the password file encrypted.