This repository contains an AWS CDK v2 application (written in Python) that runs a monthly rotation job for multiple Snowflake tenants:
- Connects to each configured Snowflake tenant
- Executes a SQL query that returns a new SCIM key for that tenant
- Stores the key in AWS Systems Manager Parameter Store (SSM) per tenant
- Calls OneLogin to update the SCIM key/token for the tenant's Snowflake application
The application supports rotating SCIM keys for multiple Snowflake tenants in a single Lambda invocation:
- Single OneLogin tenant: One shared OneLogin instance with API key authentication
- Multiple Snowflake applications: Each tenant represents a different Snowflake application in OneLogin
- Tenant-based Snowflake config: Each tenant has its own Snowflake connection (key-pair authentication)
- Tenant list: A master SSM parameter lists all tenant IDs to process
- Isolated processing: Each tenant is processed independently; failures in one tenant don't affect others
- Single or batch mode: Can process all tenants (default) or a specific tenant via event payload
- OneLogin: API key authentication (shared across all tenants)
- Format:
Authorization: Bearer api_key:<api_key>
- Format:
- Snowflake: Key-pair authentication only (per tenant)
- Uses RSA private key (PEM format)
- Optional passphrase support
- Lambda: runs the rotation logic for all configured tenants
- EventBridge Schedule: triggers Lambda monthly (processes all tenants)
- CloudWatch Logs: Lambda logs with tenant-specific prefixes
- IAM: least-privilege access to tenant-specific SSM parameters
- AWS credentials configured for the target account
- AWS CDK installed (CDK CLI)
- Python 3.11+
- Docker (for Lambda dependency bundling)
/trps/scimkey/onelogin/base_url(String) - OneLogin API base URL (e.g.,https://api.us.onelogin.com)/trps/scimkey/onelogin/api_key(SecureString) - OneLogin API key for authentication
/trps/scimkey/tenants/list(String)- Comma-separated list:
tenant1,tenant2,tenant3 - Or JSON array:
["tenant1","tenant2","tenant3"]
- Comma-separated list:
For each tenant ID (e.g., tenant1), create the following parameters:
account(String) - Snowflake account identifieruser(String) - Snowflake usernameprivate_key(SecureString) - RSA private key in PEM format (including BEGIN/END markers)private_key_passphrase(SecureString, optional) - Passphrase for the private key (if encrypted)warehouse(String) - Snowflake warehouse namerole(String) - Snowflake role namedatabase(String) - Snowflake database nameschema(String) - Snowflake schema namescim_key_sql(String) - SQL query that returns one row, one column with the SCIM key
ppid(String) - OneLogin application ppId for this Snowflake application (e.g.,ABC234)update_endpoint(String) - API endpoint path (e.g.,/api/2/apps/{ppid})scim_key_field(String) - JSON field name for SCIM key (e.g.,scim_token)
current_scim_key(SecureString) - Stores the current SCIM key (overwritten each rotation)
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt# Set up shared OneLogin API credentials (one-time setup)
./scripts/setup_onelogin.sh# Set up tenant list
./scripts/setup_tenant_list.sh tenant1,tenant2,tenant3
# Configure each tenant (Snowflake + OneLogin app settings)
./scripts/setup_tenant.sh tenant1
./scripts/setup_tenant.sh tenant2
./scripts/setup_tenant.sh tenant3# Create tenant list
aws ssm put-parameter \
--name "/trps/scimkey/tenants/list" \
--value "tenant1,tenant2" \
--type "String" \
--overwrite
# Create tenant-specific parameters (example for tenant1)
aws ssm put-parameter --name "/trps/scimkey/tenants/tenant1/snowflake/account" --value "your-account" --type "String"
aws ssm put-parameter --name "/trps/scimkey/tenants/tenant1/snowflake/user" --value "your-user" --type "String"
aws ssm put-parameter --name "/trps/scimkey/tenants/tenant1/snowflake/password" --value "your-password" --type "SecureString"
# ... (continue for all parameters)cdk bootstrapcdk deployThe EventBridge schedule automatically triggers the Lambda on the 1st day of each month at 2:00 AM UTC, processing all tenants listed in /trps/scimkey/tenants/list.
FUNCTION_NAME="ScimKeyRotatorStack-RotateScimKeyFunction-XXXXX"
aws lambda invoke \
--function-name "$FUNCTION_NAME" \
--payload '{}' \
response.json
cat response.json | python3 -m json.toolaws lambda invoke \
--function-name "$FUNCTION_NAME" \
--payload '{"tenant_id": "tenant1"}' \
response.json
cat response.json | python3 -m json.toolOr use the helper script:
./scripts/test_lambda.sh "$FUNCTION_NAME"{
"statusCode": 200,
"body": {
"message": "Processed 3 tenant(s): 3 successful, 0 failed",
"results": [
{
"tenant_id": "tenant1",
"status": "success",
"ppid": "ABC234"
},
{
"tenant_id": "tenant2",
"status": "success",
"ppid": "XYZ789"
},
{
"tenant_id": "tenant3",
"status": "failed",
"error": "Connection timeout"
}
],
"summary": {
"total": 3,
"successful": 2,
"failed": 1
}
}
}-
Configure the tenant:
./scripts/setup_tenant.sh new-tenant-id
-
Add to tenant list:
# Get current list CURRENT_LIST=$(aws ssm get-parameter --name /trps/scimkey/tenants/list --query 'Parameter.Value' --output text) # Append new tenant aws ssm put-parameter \ --name "/trps/scimkey/tenants/list" \ --value "${CURRENT_LIST},new-tenant-id" \ --type "String" \ --overwrite
Or use the helper script:
./scripts/setup_tenant_list.sh tenant1,tenant2,new-tenant-id
-
Remove from tenant list:
./scripts/setup_tenant_list.sh tenant1,tenant2 # Remove tenant3 -
(Optional) Delete tenant parameters:
# List all parameters for the tenant aws ssm get-parameters-by-path --path "/trps/scimkey/tenants/tenant3" --recursive # Delete them (be careful!) aws ssm delete-parameter --name "/trps/scimkey/tenants/tenant3/..."
- CloudWatch Logs:
/aws/lambda/ScimKeyRotatorStack-RotateScimKeyFunction-*- Logs include tenant ID prefix:
[tenant1],[tenant2], etc.
- Logs include tenant ID prefix:
- EventBridge Schedule: Runs on the 1st day of each month at 2:00 AM UTC
- Lambda Metrics: Check CloudWatch metrics for success/failure rates per invocation
- Response Status Codes:
200: All tenants processed successfully207: Partial success (some tenants failed)500: Failed to process tenant list or critical error
The Lambda uses:
boto3(AWS SDK - included in runtime)snowflake-connector-pythonrequestscryptography(for parsing RSA private keys)
CDK bundles dependencies using Docker during deployment.
- Single OneLogin Tenant: One shared OneLogin instance with API key authentication
- Multiple Snowflake Applications: Each tenant represents a different Snowflake application in OneLogin
- Tenant Isolation: Each tenant's Snowflake configuration and SCIM key are stored separately
- Error Handling: Failures in one tenant don't stop processing of other tenants
- Scalability: Can handle any number of tenants (limited by Lambda timeout/memory)
- Flexibility: Each tenant can have different:
- Snowflake accounts
- Snowflake users/keys
- SQL queries for SCIM key retrieval
- OneLogin application ppIds (different apps in the same OneLogin tenant)
-
Check tenant is in the list:
aws ssm get-parameter --name /trps/scimkey/tenants/list
-
Verify tenant parameters exist:
aws ssm get-parameters-by-path --path "/trps/scimkey/tenants/tenant1" --recursive -
Check CloudWatch Logs for tenant-specific errors
If processing many tenants, consider:
- Increasing Lambda timeout in
infra/scimkey_rotator_stack.py - Using Step Functions for parallel processing (future enhancement)
- Processing tenants in batches
- Step Functions orchestration for parallel tenant processing
- CloudWatch alarms for failed tenant rotations
- SNS notifications on failures
- Dry-run mode for testing without updating OneLogin
- Support for multiple OneLogin tenants (if needed)