refactor: migrate Secrets Manager secrets to SSM Parameter Store (free tier)
This commit is contained in:
@@ -20,8 +20,8 @@
|
||||
"OAUTH_START_URL": "https://sptejrymri.execute-api.us-east-1.amazonaws.com/oauth/start",
|
||||
"USERS_TABLE_NAME": "agent-claw-users",
|
||||
"WORKSPACE_BUCKET_NAME": "agent-claw-workspace-495395224548",
|
||||
"TELEGRAM_BOT_TOKEN_SECRET_ARN": "arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/telegram-bot-token-Oq3in3",
|
||||
"BRAVE_API_KEY_SECRET_ARN": "arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/brave-api-key-uUSgzi",
|
||||
"TELEGRAM_BOT_TOKEN_SSM_PARAM": "/agent-claw/telegram-bot-token",
|
||||
"BRAVE_API_KEY_SSM_PARAM": "/agent-claw/brave-api-key",
|
||||
"SCHEDULER_LAMBDA_ARN": "arn:aws:lambda:us-east-1:495395224548:function:agent-claw-scheduler"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@ class TelegramAdapter:
|
||||
if self._token is None:
|
||||
with self._lock:
|
||||
if self._token is None:
|
||||
secret_arn = self._secret_arn or os.environ.get(
|
||||
'TELEGRAM_BOT_TOKEN_SECRET_ARN',
|
||||
'arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/telegram-bot-token-Oq3in3'
|
||||
param_name = self._secret_arn or os.environ.get(
|
||||
'TELEGRAM_BOT_TOKEN_SSM_PARAM',
|
||||
'/agent-claw/telegram-bot-token'
|
||||
)
|
||||
sm = boto3.client('secretsmanager')
|
||||
self._token = sm.get_secret_value(
|
||||
SecretId=secret_arn
|
||||
)['SecretString']
|
||||
ssm = boto3.client('ssm')
|
||||
self._token = ssm.get_parameter(
|
||||
Name=param_name, WithDecryption=True
|
||||
)['Parameter']['Value']
|
||||
return self._token
|
||||
|
||||
def _api(self, method: str, data: dict) -> dict:
|
||||
|
||||
@@ -15,12 +15,12 @@ def _get_brave_key() -> str:
|
||||
if _brave_key is None:
|
||||
with _brave_lock:
|
||||
if _brave_key is None:
|
||||
secret_arn = os.environ.get(
|
||||
'BRAVE_API_KEY_SECRET_ARN',
|
||||
'arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/brave-api-key-uUSgzi'
|
||||
param_name = os.environ.get(
|
||||
'BRAVE_API_KEY_SSM_PARAM',
|
||||
'/agent-claw/brave-api-key'
|
||||
)
|
||||
sm = boto3.client('secretsmanager')
|
||||
_brave_key = sm.get_secret_value(SecretId=secret_arn)['SecretString']
|
||||
ssm = boto3.client('ssm')
|
||||
_brave_key = ssm.get_parameter(Name=param_name, WithDecryption=True)['Parameter']['Value']
|
||||
return _brave_key
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as apigatewayv2 from 'aws-cdk-lib/aws-apigatewayv2';
|
||||
import * as apigatewayv2integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations';
|
||||
import * as iam from 'aws-cdk-lib/aws-iam';
|
||||
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
||||
import * as ssm from 'aws-cdk-lib/aws-ssm';
|
||||
import * as events from 'aws-cdk-lib/aws-events';
|
||||
import * as eventsTargets from 'aws-cdk-lib/aws-events-targets';
|
||||
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
|
||||
@@ -20,25 +21,33 @@ export class AgentClawStack extends cdk.Stack {
|
||||
super(scope, id, props);
|
||||
|
||||
// ── Context parameters ─────────────────────────────────────────────────
|
||||
const telegramBotTokenSecretArn = this.node.tryGetContext('telegramBotTokenSecretArn') as string | undefined;
|
||||
const braveApiKeySecretArn = this.node.tryGetContext('braveApiKeySecretArn') as string | undefined;
|
||||
const telegramBotTokenParamName = this.node.tryGetContext('telegramBotTokenParamName') as string | undefined;
|
||||
const braveApiKeyParamName = this.node.tryGetContext('braveApiKeyParamName') as string | undefined;
|
||||
const googleOAuthClientParamName = this.node.tryGetContext('googleOAuthClientParamName') as string | undefined;
|
||||
const existingWorkspaceBucketName = this.node.tryGetContext('workspaceBucketName') as string | undefined;
|
||||
const runtime1Arn = this.node.tryGetContext('runtime1Arn') as string | undefined;
|
||||
|
||||
if (!telegramBotTokenSecretArn) {
|
||||
throw new Error('Context param required: telegramBotTokenSecretArn');
|
||||
if (!telegramBotTokenParamName) {
|
||||
throw new Error('Context param required: telegramBotTokenParamName');
|
||||
}
|
||||
if (!braveApiKeySecretArn) {
|
||||
throw new Error('Context param required: braveApiKeySecretArn');
|
||||
if (!braveApiKeyParamName) {
|
||||
throw new Error('Context param required: braveApiKeyParamName');
|
||||
}
|
||||
if (!googleOAuthClientParamName) {
|
||||
throw new Error('Context param required: googleOAuthClientParamName');
|
||||
}
|
||||
|
||||
// ── Secrets (reference existing) ───────────────────────────────────────
|
||||
const botTokenSecret = secretsmanager.Secret.fromSecretCompleteArn(
|
||||
this, 'TelegramBotToken', telegramBotTokenSecretArn
|
||||
);
|
||||
const braveApiKeySecret = secretsmanager.Secret.fromSecretCompleteArn(
|
||||
this, 'BraveApiKey', braveApiKeySecretArn
|
||||
);
|
||||
// ── SSM Parameters (reference existing SecureString params) ────────────
|
||||
const ssmParamArns = [
|
||||
`arn:aws:ssm:${this.region}:${this.account}:parameter${telegramBotTokenParamName}`,
|
||||
`arn:aws:ssm:${this.region}:${this.account}:parameter${braveApiKeyParamName}`,
|
||||
`arn:aws:ssm:${this.region}:${this.account}:parameter${googleOAuthClientParamName}`,
|
||||
];
|
||||
|
||||
const ssmReadPolicy = new iam.PolicyStatement({
|
||||
actions: ['ssm:GetParameter'],
|
||||
resources: ssmParamArns,
|
||||
});
|
||||
|
||||
// ── S3 workspace bucket ────────────────────────────────────────────────
|
||||
const workspaceBucket = existingWorkspaceBucketName
|
||||
@@ -94,13 +103,13 @@ export class AgentClawStack extends cdk.Stack {
|
||||
memorySize: 128,
|
||||
environment: {
|
||||
MESSAGE_QUEUE_URL: messageQueue.queueUrl,
|
||||
TELEGRAM_BOT_TOKEN_SECRET_ARN: telegramBotTokenSecretArn,
|
||||
TELEGRAM_BOT_TOKEN_SSM_PARAM: telegramBotTokenParamName,
|
||||
TELEGRAM_WEBHOOK_SECRET: '', // set via SSM or direct env after deploy
|
||||
ATTACHMENTS_BUCKET_NAME: workspaceBucket.bucketName,
|
||||
},
|
||||
});
|
||||
messageQueue.grantSendMessages(tgIngestFn);
|
||||
botTokenSecret.grantRead(tgIngestFn);
|
||||
tgIngestFn.addToRolePolicy(ssmReadPolicy);
|
||||
workspaceBucket.grantWrite(tgIngestFn);
|
||||
|
||||
// ── Lambda: agent-runner ───────────────────────────────────────────────
|
||||
@@ -114,8 +123,8 @@ export class AgentClawStack extends cdk.Stack {
|
||||
environment: {
|
||||
SESSION_TABLE_NAME: sessionTable.tableName,
|
||||
WORKSPACE_BUCKET_NAME: workspaceBucket.bucketName,
|
||||
TELEGRAM_BOT_TOKEN_SECRET_ARN: telegramBotTokenSecretArn,
|
||||
BRAVE_API_KEY_SECRET_ARN: braveApiKeySecretArn,
|
||||
TELEGRAM_BOT_TOKEN_SSM_PARAM: telegramBotTokenParamName,
|
||||
BRAVE_API_KEY_SSM_PARAM: braveApiKeyParamName,
|
||||
RUNTIME_1_ARN: runtime1Arn ?? 'PLACEHOLDER_SET_AFTER_RUNTIME_DEPLOY',
|
||||
AWS_REGION_NAME: 'us-east-1',
|
||||
},
|
||||
@@ -125,8 +134,7 @@ export class AgentClawStack extends cdk.Stack {
|
||||
usersTable.grantReadWriteData(agentRunnerFn);
|
||||
agentRunnerFn.addEnvironment('USERS_TABLE_NAME', usersTable.tableName);
|
||||
workspaceBucket.grantRead(agentRunnerFn);
|
||||
botTokenSecret.grantRead(agentRunnerFn);
|
||||
braveApiKeySecret.grantRead(agentRunnerFn);
|
||||
agentRunnerFn.addToRolePolicy(ssmReadPolicy);
|
||||
messageQueue.grantConsumeMessages(agentRunnerFn);
|
||||
|
||||
// AgentCore invoke permission
|
||||
@@ -172,8 +180,7 @@ export class AgentClawStack extends cdk.Stack {
|
||||
resources: ['*'],
|
||||
}));
|
||||
workspaceBucket.grantRead(runtime1Role);
|
||||
botTokenSecret.grantRead(runtime1Role);
|
||||
braveApiKeySecret.grantRead(runtime1Role);
|
||||
runtime1Role.addToPolicy(ssmReadPolicy);
|
||||
usersTable.grantReadWriteData(runtime1Role);
|
||||
// Google secret grants added after workspace_mcp section below
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
@@ -192,15 +199,12 @@ export class AgentClawStack extends cdk.Stack {
|
||||
// and fed back as context param runtime1Arn.
|
||||
|
||||
// ── Google Workspace MCP ──────────────────────────────────────────────
|
||||
const googleOAuthClientSecret = secretsmanager.Secret.fromSecretCompleteArn(
|
||||
this, 'GoogleOAuthClient', 'arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/google-oauth-client-subXHl'
|
||||
);
|
||||
|
||||
// workspace-mcp Lambda execution role (import existing — created during initial setup)
|
||||
const _workspaceMcpRole = iam.Role.fromRoleName(
|
||||
this, 'WorkspaceMcpRole', 'agent-claw-workspace-mcp-role'
|
||||
);
|
||||
googleOAuthClientSecret.grantRead(_workspaceMcpRole);
|
||||
_workspaceMcpRole.addToPrincipalPolicy?.(ssmReadPolicy);
|
||||
// Grant workspace-mcp role read access to all per-user Google credential secrets
|
||||
(_workspaceMcpRole as iam.Role).addToPrincipalPolicy?.(new iam.PolicyStatement({
|
||||
sid: 'PerUserGoogleCredentialsRead',
|
||||
@@ -225,8 +229,7 @@ export class AgentClawStack extends cdk.Stack {
|
||||
conditions: { StringEquals: { 'lambda:FunctionUrlAuthType': 'AWS_IAM' } },
|
||||
}));
|
||||
|
||||
// Grant AgentCore execution role read access to Google OAuth client + per-user credentials
|
||||
googleOAuthClientSecret.grantRead(runtime1Role);
|
||||
// Grant AgentCore execution role read access to per-user Google credentials (stays in Secrets Manager)
|
||||
runtime1Role.addToPolicy(new iam.PolicyStatement({
|
||||
sid: 'PerUserGoogleCredentialsReadRuntime',
|
||||
actions: ['secretsmanager:GetSecretValue'],
|
||||
@@ -250,22 +253,15 @@ export class AgentClawStack extends cdk.Stack {
|
||||
timeout: cdk.Duration.seconds(30),
|
||||
memorySize: 128,
|
||||
environment: {
|
||||
GOOGLE_OAUTH_CLIENT_SECRET_ARN: googleOAuthClientSecret.secretArn,
|
||||
GOOGLE_OAUTH_CLIENT_SSM_PARAM: googleOAuthClientParamName,
|
||||
USERS_TABLE_NAME: usersTable.tableName,
|
||||
TELEGRAM_BOT_TOKEN_SECRET_ARN: telegramBotTokenSecretArn,
|
||||
TELEGRAM_BOT_TOKEN_SSM_PARAM: telegramBotTokenParamName,
|
||||
// OAUTH_REDIRECT_URI set after API GW URL is known — injected via addEnvironment below
|
||||
OAUTH_REDIRECT_URI: 'PLACEHOLDER',
|
||||
},
|
||||
});
|
||||
googleOAuthClientSecret.grantRead(oauthHandlerFn);
|
||||
botTokenSecret.grantRead(oauthHandlerFn);
|
||||
oauthHandlerFn.addToRolePolicy(ssmReadPolicy);
|
||||
usersTable.grantReadWriteData(oauthHandlerFn);
|
||||
// Explicit access to the OAuth client secret (fromSecretNameV2 wildcard may not resolve)
|
||||
oauthHandlerFn.addToRolePolicy(new iam.PolicyStatement({
|
||||
sid: 'GoogleOAuthClientSecretExact',
|
||||
actions: ['secretsmanager:GetSecretValue'],
|
||||
resources: ['arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/google-oauth-client-subXHl'],
|
||||
}));
|
||||
// Grant OAuth handler write access to per-user credential secrets
|
||||
oauthHandlerFn.addToRolePolicy(new iam.PolicyStatement({
|
||||
sid: 'PerUserGoogleCredentialsWrite',
|
||||
@@ -344,10 +340,10 @@ export class AgentClawStack extends cdk.Stack {
|
||||
timeout: cdk.Duration.seconds(30),
|
||||
memorySize: 128,
|
||||
environment: {
|
||||
TELEGRAM_BOT_TOKEN_SECRET_ARN: telegramBotTokenSecretArn,
|
||||
TELEGRAM_BOT_TOKEN_SSM_PARAM: telegramBotTokenParamName,
|
||||
},
|
||||
});
|
||||
botTokenSecret.grantRead(schedulerFn);
|
||||
schedulerFn.addToRolePolicy(ssmReadPolicy);
|
||||
// Allow EventBridge to invoke the scheduler Lambda
|
||||
schedulerFn.addPermission('EventBridgeInvoke', {
|
||||
principal: new iam.ServicePrincipal('events.amazonaws.com'),
|
||||
|
||||
@@ -163,11 +163,11 @@ def handler(event, context):
|
||||
user_profile['status'] = 'active'
|
||||
prompt = f"[System: User just registered with name '{name}'. Welcome them warmly and ask how you can help.]"
|
||||
else:
|
||||
bot_token_secret_arn = os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', '')
|
||||
bot_token_secret_arn = os.environ.get('TELEGRAM_BOT_TOKEN_SSM_PARAM', '')
|
||||
bot_token = ''
|
||||
if bot_token_secret_arn:
|
||||
sm = boto3.client('secretsmanager', region_name='us-east-1')
|
||||
bot_token = sm.get_secret_value(SecretId=bot_token_secret_arn)['SecretString']
|
||||
ssm = boto3.client('ssm', region_name='us-east-1')
|
||||
bot_token = ssm.get_parameter(Name=bot_token_secret_arn, WithDecryption=True)['Parameter']['Value']
|
||||
send_telegram_direct(chat_id, bot_token, "Hi! I don't recognize you yet. What's your name?", thread_id=message_thread_id)
|
||||
return
|
||||
# ── Get or create AgentCore session ──────────────────────────────────
|
||||
@@ -212,7 +212,7 @@ def handler(event, context):
|
||||
'type': channel,
|
||||
'target_id': str(chat_id),
|
||||
'message_thread_id': message_thread_id,
|
||||
'bot_token_secret_arn': os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', ''),
|
||||
'bot_token_secret_arn': os.environ.get('TELEGRAM_BOT_TOKEN_SSM_PARAM', ''),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -232,11 +232,11 @@ def handler(event, context):
|
||||
|
||||
# Process streaming response: buffer text chunks and send to Telegram as paragraphs arrive
|
||||
bot_token = ''
|
||||
bot_token_secret_arn = os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', '')
|
||||
if bot_token_secret_arn:
|
||||
sm = boto3.client('secretsmanager', region_name='us-east-1')
|
||||
bot_token_param = os.environ.get('TELEGRAM_BOT_TOKEN_SSM_PARAM', '')
|
||||
if bot_token_param:
|
||||
ssm = boto3.client('ssm', region_name='us-east-1')
|
||||
try:
|
||||
bot_token = sm.get_secret_value(SecretId=bot_token_secret_arn)['SecretString']
|
||||
bot_token = ssm.get_parameter(Name=bot_token_param, WithDecryption=True)['Parameter']['Value']
|
||||
except Exception as e:
|
||||
print(f'[agent-runner] Failed to get bot token: {e}')
|
||||
|
||||
|
||||
@@ -44,9 +44,10 @@ def get_ddb():
|
||||
|
||||
|
||||
def get_oauth_client() -> tuple[str, str]:
|
||||
"""Return (client_id, client_secret) from Secrets Manager."""
|
||||
arn = os.environ['GOOGLE_OAUTH_CLIENT_SECRET_ARN']
|
||||
secret = json.loads(get_sm().get_secret_value(SecretId=arn)['SecretString'])
|
||||
"""Return (client_id, client_secret) from SSM Parameter Store."""
|
||||
param_name = os.environ['GOOGLE_OAUTH_CLIENT_SSM_PARAM']
|
||||
ssm = boto3.client('ssm', region_name=os.environ.get('AWS_REGION', 'us-east-1'))
|
||||
secret = json.loads(ssm.get_parameter(Name=param_name, WithDecryption=True)['Parameter']['Value'])
|
||||
return secret['client_id'], secret['client_secret']
|
||||
|
||||
|
||||
@@ -222,10 +223,11 @@ def handle_callback(params: dict) -> dict:
|
||||
|
||||
# Best-effort Telegram confirmation
|
||||
try:
|
||||
bot_token_arn = os.environ.get('TELEGRAM_BOT_TOKEN_SECRET_ARN', '')
|
||||
if bot_token_arn and actor_id.startswith('telegram:'):
|
||||
bot_token_param = os.environ.get('TELEGRAM_BOT_TOKEN_SSM_PARAM', '')
|
||||
if bot_token_param and actor_id.startswith('telegram:'):
|
||||
chat_id = actor_id.split(':', 1)[1]
|
||||
bot_token = get_sm().get_secret_value(SecretId=bot_token_arn)['SecretString']
|
||||
ssm = boto3.client('ssm', region_name=os.environ.get('AWS_REGION', 'us-east-1'))
|
||||
bot_token = ssm.get_parameter(Name=bot_token_param, WithDecryption=True)['Parameter']['Value']
|
||||
tg_text = f'✅ Connected {user_email} as "{label}"'
|
||||
tg_payload = json.dumps({'chat_id': chat_id, 'text': tg_text}).encode()
|
||||
tg_req = urllib.request.Request(
|
||||
|
||||
@@ -11,8 +11,8 @@ def handler(event, context):
|
||||
rule_name = event['rule_name']
|
||||
|
||||
# Fetch bot token
|
||||
sm = boto3.client('secretsmanager', region_name='us-east-1')
|
||||
token = sm.get_secret_value(SecretId=os.environ['TELEGRAM_BOT_TOKEN_SECRET_ARN'])['SecretString']
|
||||
ssm = boto3.client('ssm', region_name='us-east-1')
|
||||
token = ssm.get_parameter(Name=os.environ['TELEGRAM_BOT_TOKEN_SSM_PARAM'], WithDecryption=True)['Parameter']['Value']
|
||||
|
||||
# Send Telegram message
|
||||
payload = json.dumps({'chat_id': chat_id, 'text': message}).encode()
|
||||
|
||||
@@ -20,10 +20,11 @@ def get_bot_token() -> str:
|
||||
if _bot_token is None:
|
||||
with _token_lock:
|
||||
if _bot_token is None:
|
||||
sm = boto3.client('secretsmanager')
|
||||
_bot_token = sm.get_secret_value(
|
||||
SecretId=os.environ['TELEGRAM_BOT_TOKEN_SECRET_ARN']
|
||||
)['SecretString']
|
||||
ssm = boto3.client('ssm')
|
||||
_bot_token = ssm.get_parameter(
|
||||
Name=os.environ['TELEGRAM_BOT_TOKEN_SSM_PARAM'],
|
||||
WithDecryption=True
|
||||
)['Parameter']['Value']
|
||||
return _bot_token
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Fetch Google OAuth credentials and client secrets from Secrets Manager.
|
||||
Fetch Google OAuth credentials from SSM (client secret) and Secrets Manager (per-user tokens).
|
||||
Called by bootstrap before starting workspace-mcp.
|
||||
"""
|
||||
import json
|
||||
@@ -12,10 +12,11 @@ def main():
|
||||
sm = boto3.client('secretsmanager', region_name=region)
|
||||
|
||||
# Fetch OAuth client credentials (client_id + client_secret)
|
||||
client_secret_arn = os.environ.get('GOOGLE_OAUTH_CLIENT_SECRET_ARN')
|
||||
if client_secret_arn:
|
||||
client_secret_param = os.environ.get('GOOGLE_OAUTH_CLIENT_SSM_PARAM')
|
||||
if client_secret_param:
|
||||
try:
|
||||
client_creds = json.loads(sm.get_secret_value(SecretId=client_secret_arn)['SecretString'])
|
||||
ssm = boto3.client('ssm', region_name=region)
|
||||
client_creds = json.loads(ssm.get_parameter(Name=client_secret_param, WithDecryption=True)['Parameter']['Value'])
|
||||
os.environ['GOOGLE_OAUTH_CLIENT_ID'] = client_creds['client_id']
|
||||
os.environ['GOOGLE_OAUTH_CLIENT_SECRET'] = client_creds['client_secret']
|
||||
print('[fetch_credentials] OAuth client credentials loaded', file=sys.stderr)
|
||||
|
||||
@@ -21,11 +21,11 @@ def _setup_shared_environment():
|
||||
os.environ.setdefault('HOME', '/tmp')
|
||||
os.environ.setdefault('GOOGLE_WORKSPACE_MCP_CREDENTIALS_DIR', '/tmp/workspace_mcp_credentials')
|
||||
|
||||
client_arn = os.environ.get('GOOGLE_OAUTH_CLIENT_SECRET_ARN', '')
|
||||
if client_arn:
|
||||
client_param = os.environ.get('GOOGLE_OAUTH_CLIENT_SSM_PARAM', '')
|
||||
if client_param:
|
||||
try:
|
||||
sm = boto3.client('secretsmanager', region_name=os.environ.get('AWS_REGION', 'us-east-1'))
|
||||
client_creds = json.loads(sm.get_secret_value(SecretId=client_arn)['SecretString'])
|
||||
ssm = boto3.client('ssm', region_name=os.environ.get('AWS_REGION', 'us-east-1'))
|
||||
client_creds = json.loads(ssm.get_parameter(Name=client_param, WithDecryption=True)['Parameter']['Value'])
|
||||
os.environ['GOOGLE_OAUTH_CLIENT_ID'] = client_creds['client_id']
|
||||
os.environ['GOOGLE_OAUTH_CLIENT_SECRET'] = client_creds['client_secret']
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user