refactor: migrate Secrets Manager secrets to SSM Parameter Store (free tier)

This commit is contained in:
daniel
2026-05-13 12:55:16 -05:00
parent 3a34e61479
commit 74f74ef877
10 changed files with 81 additions and 81 deletions

View File

@@ -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'),