Prelude CLI/SDK setup/usage

Installing the CLI

pip install prelude-cli

Upgrading the CLI

pip install prelude-cli --upgrade

Setting Up the CLI

The user must configure their CLI profile if they are a new CLI user by running:

prelude configure

CLI Usage

  • Before using any CLI command, the user must run
prelude auth login --password <PASSWORD>

to login and save their access tokens for 90 days.   After 90 days when the refresh token expires, the user must run

prelude auth login --password <PASSWORD>
  • If for some reason the auto-token refreshing is not working the user can manually run
prelude auth refresh
  • Tokens are saved per user/api (us1/eu1/us2) pair so adding a new account to your keychain with the same user as one of the other profiles in the keychain that is already logged in will not require another authentication call
  • Token Expiration Details:  By default, we store an access token and a refresh token. The access token is good for 3 hours but the refresh token is good for 90 days. so whenever the access token returns 401 (unauthenticated) the CLI automatically uses the refresh token (if still valid) to get a new access token and reauthenticate.

New CLI commands

Login using password

  • Saves tokens to disk and returns to user
prelude auth login --password <PASSWORD>

Refresh tokens

  • Refreshes saved tokens, must have called auth login at least once before
prelude auth refresh

Invite user

Invite a new user to your account
prelude iam invite-user --permission <PERMISSION> --name <INVITED USER NAME> --oidc <OIDC APP NAME or 'none'> <INVITED USER EMAIL>

Create service user

Create a service user is used to generate a user and registration string for deploying probes and creating endpoints via the CLI

prelude iam create-service-user <SERVICE USER NAME>

SDK

SDK Snippets

There are various functions to aid in authenticating via the SDK.  Using a password, using an existing token, pulling an existing token automatically etc.

Here are a few snippets, followed by a fuller example that puts them into practice!

def init_keychain():
    keychain = Keychain()
    keychain.configure_keychain(ACCOUNT, HANDLE, HQ, profile="default")
    profile_items = dict(keychain.get_profile(profile="default").items())

# keychain must have already been configured
def from_keychain_password():
    account = Account.from_keychain(profile="default")
    controller = IAMAccountController(account)
    account.password_login(PASSWORD)
    account_details = controller.get_account()

# keychain must have already been configured, can only be used if account.password_login(PASSWORD) has been called before
def from_keychain_refresh():
    account = Account.from_keychain(profile="default")
    controller = IAMAccountController(account)
    account.refresh_tokens()
    account_details = controller.get_account()

# won't be able to use account.password_login(PASSWORD) or account.refresh_token(), which is restricted to only be used by keychain-instantiated accounts
def from_id_token():
    tokens = exchange_token(ACCOUNT, HANDLE, HQ, "password", {"password": PASSWORD})
    account = Account.from_token(
        ACCOUNT, HANDLE, hq=HQ, token=tokens["token"]
    )
    controller = IAMAccountController(account)
    account_details = controller.get_account()

# won't be able to use account.password_login(PASSWORD) or account.refresh_token(), which is restricted to only be used by keychain-instantiated accounts
def from_refresh_token():
    tokens = exchange_token(ACCOUNT, HANDLE, HQ, "password", {"password": PASSWORD})
    account = Account.from_token(
        ACCOUNT, HANDLE, hq=HQ, refresh_token=tokens["refresh_token"]
    )
    controller = IAMAccountController(account)
    account_details = controller.get_account()

 

SDK Example

In this example, we provide a complete Python script to authenticate and retrieve the SCM statistics/summary.  If already authenticated, it will use an existing token.  If there is no existing token it will use a password that can be provided on the command line, as an environment variable or it will be requested when needed.

 

import argparse
import os
import json
from dotenv import load_dotenv
import getpass
from prelude_sdk.controllers.scm_controller import ScmController
from prelude_sdk.controllers.iam_controller import IAMAccountController
from prelude_sdk.models.codes import (
    Control,
    ControlCategory,
    PartnerEvents,
    RunCode,
    SCMCategory,
)
from prelude_sdk.models.account import Account, Keychain

account = None
scm = None

# Load environment variables from a .env file if it exists
load_dotenv()

def parse_arguments():
    """
    Parse command-line arguments for Account initialization.

    Returns:
        argparse.Namespace: Parsed arguments containing profile, password, and refresh token.
    """
    parser = argparse.ArgumentParser(description="Retrieve SCM endpoints using Prelude SDK")
    
    parser.add_argument("--profile", default="default", type=str, help="Prelude account profile")
    parser.add_argument("--password", required=False, type=str, help="Prelude account password")
    
    return parser.parse_args()

def from_keychain_password(profile="default", password=None) -> Account:
    """
    Authenticate an account using the password stored in the keychain.

    Args:
        profile (str): Profile name for the account in the keychain.
        password (str, optional): Account password. If None, prompts for password input.

    Returns:
        Account: Authenticated account instance.
    """
    account = Account.from_keychain(profile=profile)
    try:
        if password is not None:
            account.password_login(password)
        else:
            password = getpass.getpass("Enter your password: ")
            account.password_login(password)
    except:
        print("Authentication failed")
        return None
    return account

def from_keychain_refresh(profile="default") -> Account:
    """
    Authenticate an account using refresh tokens stored in the keychain.

    Args:
        profile (str): Profile name for the account in the keychain.

    Returns:
        Account or None: Authenticated account instance if successful, None otherwise.
    """
    account = Account.from_keychain(profile=profile)
    try:
        account.refresh_tokens()
    except json.JSONDecodeError:
        print("Unable to decode tokens. Login with password.")
        return None
    except AttributeError:
        print("Unable to decode tokens. Login with password.")
        return None
    except Exception as e1:
        print(f"{e1}. Login with password.")
        return None
    return account

def setup_account(args):
    """
    Setup account using provided arguments or environment variables.

    Args:
        args (argparse.Namespace): Parsed command-line arguments.

    Returns:
        Account: Authenticated account instance.
    """
    profile = args.profile or os.getenv("PRELUDE_PROFILE")
    password = args.password or os.getenv("PRELUDE_PASSWORD")
    account = from_keychain_refresh(profile=profile)
    if account is None:
        account = from_keychain_password(profile=profile, password=password)
        
    return account

def get_scm_basic_stats(controller):
    """
    Retrieve basic SCM evaluation summary from the SCM controller.

    Args:
        controller (ScmController): Initialized ScmController instance.

    Returns:
        dict: SCM evaluation summary or error status.
    """
    try:
        summary = controller.evaluation_summary()
        return summary
    except Exception as e:
        print("Error retrieving summary:", str(e))
    return {"status": "error", "results": []}

def main():
    """
    Main function to parse arguments, authenticate the account, and retrieve SCM stats.
    """
    args = parse_arguments()

    account = setup_account(args)
    if account is None:
        exit(1)

    # Create an instance of ScmController
    scm = ScmController(account)

    try:
        input("Press enter to get Basic SCM stats: ")
        stats = get_scm_basic_stats(scm)
        print(json.dumps(stats, indent=4))
    except Exception as e:
        print("Error retrieving results:", str(e))

if __name__ == "__main__":
    main()

 

Example output:

% python scm_sdk_new_auth.py

No refresh token found, please login first to continue. Login with password.
Enter your password: 
Press enter to get Basic SCM stats: 
{
    "endpoint_summary": {
        "categories": [
            {
                "category": 5,
                "endpoint_count": 302,
                "excepted": {
                    "control_failure_count": 50,
                    "missing_asset_manager_count": 292,
                    "missing_edr_count": null
                },
              "instances": [...]
            },
            {
                "category": 6,
                "endpoint_count": 867,
                "excepted": {
                    "control_failure_count": null,
                    "missing_asset_manager_count": null,
                    "missing_edr_count": 857
                },
              "instances": [...]
            }
        ],
        "endpoint_count": 1159
    },
    "inbox_summary": {
        "categories": [
            {
                "category": 2,
                "inbox_count": 9,
                "instances": [
                    {
                        "control": 10,
                        "inbox_count": 9,
                      "setting_count": 250,
                        "excepted": {
                            "setting_misconfiguration_count": 116
                        }
                    }
                ]
            }
        ],
        "inbox_count": 9
    },
    "user_summary": {
        "categories": [
            {
                "category": 3,
                "user_count": 6,
                "excepted": {
                    "control_failure_count": 0
                },
              "instances": [...]
            }
        ],
        "user_count": 6
    }
}