diff --git a/agentclaw/agentcore/agentcore.json b/agentclaw/agentcore/agentcore.json index e07916f..807b31a 100644 --- a/agentclaw/agentcore/agentcore.json +++ b/agentclaw/agentcore/agentcore.json @@ -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" } } diff --git a/agentclaw/app/agent_claw_main/channels/telegram.py b/agentclaw/app/agent_claw_main/channels/telegram.py index 6417b19..b7a24b5 100644 --- a/agentclaw/app/agent_claw_main/channels/telegram.py +++ b/agentclaw/app/agent_claw_main/channels/telegram.py @@ -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: diff --git a/agentclaw/app/agent_claw_main/tools/web.py b/agentclaw/app/agent_claw_main/tools/web.py index 6ca244b..6835352 100644 --- a/agentclaw/app/agent_claw_main/tools/web.py +++ b/agentclaw/app/agent_claw_main/tools/web.py @@ -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 diff --git a/cdk/lib/agent-claw-stack.ts b/cdk/lib/agent-claw-stack.ts index 401bbf7..159c176 100644 --- a/cdk/lib/agent-claw-stack.ts +++ b/cdk/lib/agent-claw-stack.ts @@ -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'), diff --git a/src/lambdas/agent-runner/handler.py b/src/lambdas/agent-runner/handler.py index ee05504..eb07b75 100644 --- a/src/lambdas/agent-runner/handler.py +++ b/src/lambdas/agent-runner/handler.py @@ -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}') diff --git a/src/lambdas/oauth-handler/handler.py b/src/lambdas/oauth-handler/handler.py index 84daee8..507fc82 100644 --- a/src/lambdas/oauth-handler/handler.py +++ b/src/lambdas/oauth-handler/handler.py @@ -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( diff --git a/src/lambdas/scheduler/handler.py b/src/lambdas/scheduler/handler.py index ced960f..d67efda 100644 --- a/src/lambdas/scheduler/handler.py +++ b/src/lambdas/scheduler/handler.py @@ -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() diff --git a/src/lambdas/tg-ingest/handler.py b/src/lambdas/tg-ingest/handler.py index defcb48..74ab39d 100644 --- a/src/lambdas/tg-ingest/handler.py +++ b/src/lambdas/tg-ingest/handler.py @@ -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 diff --git a/src/lambdas/workspace-mcp/fetch_credentials.py b/src/lambdas/workspace-mcp/fetch_credentials.py index 8933b95..0786a5f 100644 --- a/src/lambdas/workspace-mcp/fetch_credentials.py +++ b/src/lambdas/workspace-mcp/fetch_credentials.py @@ -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) diff --git a/src/lambdas/workspace-mcp/handler.py b/src/lambdas/workspace-mcp/handler.py index fbd2bc5..ed0cf35 100644 --- a/src/lambdas/workspace-mcp/handler.py +++ b/src/lambdas/workspace-mcp/handler.py @@ -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: