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