Prelude CLI/SDK setup/usage

Installing the CLI

pip install prelude-cli

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

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
    }
}