chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# Data Sources
|
||||
# =============================================================================
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# =============================================================================
|
||||
# Local Values
|
||||
# =============================================================================
|
||||
|
||||
locals {
|
||||
account_id = data.aws_caller_identity.current.account_id
|
||||
region = data.aws_region.current.id
|
||||
|
||||
app_name = "${var.stack_name_base}-frontend"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# S3 Bucket for Access Logs
|
||||
# =============================================================================
|
||||
|
||||
resource "aws_s3_bucket" "access_logs" {
|
||||
bucket_prefix = "${lower(var.stack_name_base)}-access-logs-"
|
||||
force_destroy = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "access_logs" {
|
||||
bucket = aws_s3_bucket.access_logs.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_lifecycle_configuration" "access_logs" {
|
||||
bucket = aws_s3_bucket.access_logs.id
|
||||
|
||||
rule {
|
||||
id = "DeleteOldAccessLogs"
|
||||
status = "Enabled"
|
||||
|
||||
expiration {
|
||||
days = var.access_logs_expiry_days
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# S3 Bucket for Staging (Amplify Deployments)
|
||||
# =============================================================================
|
||||
|
||||
resource "aws_s3_bucket" "staging" {
|
||||
bucket_prefix = "${lower(var.stack_name_base)}-staging-"
|
||||
force_destroy = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "staging" {
|
||||
bucket = aws_s3_bucket.staging.id
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "staging" {
|
||||
bucket = aws_s3_bucket.staging.id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_lifecycle_configuration" "staging" {
|
||||
bucket = aws_s3_bucket.staging.id
|
||||
|
||||
rule {
|
||||
id = "DeleteOldDeployments"
|
||||
status = "Enabled"
|
||||
|
||||
expiration {
|
||||
days = var.staging_bucket_expiry_days
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_logging" "staging" {
|
||||
bucket = aws_s3_bucket.staging.id
|
||||
|
||||
target_bucket = aws_s3_bucket.access_logs.id
|
||||
target_prefix = "staging-bucket-access-logs/"
|
||||
}
|
||||
|
||||
# Bucket policy: Allow Amplify service access
|
||||
resource "aws_s3_bucket_policy" "staging" {
|
||||
bucket = aws_s3_bucket.staging.id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AmplifyAccess"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "amplify.amazonaws.com"
|
||||
}
|
||||
Action = [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectVersion"
|
||||
]
|
||||
Resource = "${aws_s3_bucket.staging.arn}/*"
|
||||
},
|
||||
{
|
||||
Sid = "DenyInsecureConnections"
|
||||
Effect = "Deny"
|
||||
Principal = "*"
|
||||
Action = "s3:*"
|
||||
Resource = [
|
||||
aws_s3_bucket.staging.arn,
|
||||
"${aws_s3_bucket.staging.arn}/*"
|
||||
]
|
||||
Condition = {
|
||||
Bool = {
|
||||
"aws:SecureTransport" = "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Amplify App
|
||||
# =============================================================================
|
||||
# Note: This creates a manual deployment app (no Git integration)
|
||||
# Frontend deployments are handled via the deploy-frontend.py script
|
||||
# Environment variables are set at deployment time, not at app creation
|
||||
|
||||
resource "aws_amplify_app" "frontend" {
|
||||
name = local.app_name
|
||||
platform = var.platform
|
||||
description = "${var.stack_name_base} - React/Next.js Frontend"
|
||||
|
||||
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Amplify Branch (main)
|
||||
# =============================================================================
|
||||
|
||||
resource "aws_amplify_branch" "main" {
|
||||
app_id = aws_amplify_app.frontend.id
|
||||
branch_name = "main"
|
||||
stage = "PRODUCTION"
|
||||
|
||||
description = "Main production branch"
|
||||
|
||||
# Enable auto-build on push (if using Git integration)
|
||||
enable_auto_build = false
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
output "app_id" {
|
||||
description = "Amplify App ID"
|
||||
value = aws_amplify_app.frontend.id
|
||||
}
|
||||
|
||||
output "app_arn" {
|
||||
description = "Amplify App ARN"
|
||||
value = aws_amplify_app.frontend.arn
|
||||
}
|
||||
|
||||
output "default_domain" {
|
||||
description = "Amplify default domain"
|
||||
value = aws_amplify_app.frontend.default_domain
|
||||
}
|
||||
|
||||
output "app_url" {
|
||||
description = "Full Amplify app URL (main branch) - predictable format"
|
||||
value = "https://main.${aws_amplify_app.frontend.id}.amplifyapp.com"
|
||||
}
|
||||
|
||||
output "branch_name" {
|
||||
description = "Main branch name"
|
||||
value = aws_amplify_branch.main.branch_name
|
||||
}
|
||||
|
||||
output "staging_bucket_name" {
|
||||
description = "S3 staging bucket name for deployments"
|
||||
value = aws_s3_bucket.staging.bucket
|
||||
}
|
||||
|
||||
output "staging_bucket_arn" {
|
||||
description = "S3 staging bucket ARN"
|
||||
value = aws_s3_bucket.staging.arn
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
variable "stack_name_base" {
|
||||
description = "Base name for all resources."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "platform" {
|
||||
description = "Platform type for Amplify app (WEB or WEB_COMPUTE)."
|
||||
type = string
|
||||
default = "WEB"
|
||||
}
|
||||
|
||||
variable "staging_bucket_expiry_days" {
|
||||
description = "Number of days before staging bucket objects expire."
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
|
||||
variable "access_logs_expiry_days" {
|
||||
description = "Number of days before access log objects expire."
|
||||
type = number
|
||||
default = 90
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.82.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# Machine-to-Machine (M2M) Authentication
|
||||
# Maps to: backend-stack.ts createMachineAuthentication()
|
||||
# =============================================================================
|
||||
|
||||
# Resource Server for M2M Authentication
|
||||
# Defines API scopes that machine clients can request access to
|
||||
|
||||
resource "aws_cognito_resource_server" "gateway" {
|
||||
identifier = "${var.stack_name_base}-gateway"
|
||||
name = "${var.stack_name_base}-gateway-resource-server"
|
||||
user_pool_id = var.user_pool_id
|
||||
|
||||
scope {
|
||||
scope_name = "read"
|
||||
scope_description = "Read access to gateway"
|
||||
}
|
||||
|
||||
scope {
|
||||
scope_name = "write"
|
||||
scope_description = "Write access to gateway"
|
||||
}
|
||||
}
|
||||
|
||||
# Machine Client for AgentCore Gateway authentication
|
||||
# Uses OAuth2 Client Credentials flow for service-to-service auth
|
||||
|
||||
resource "aws_cognito_user_pool_client" "machine" {
|
||||
name = "${var.stack_name_base}-machine-client"
|
||||
user_pool_id = var.user_pool_id
|
||||
|
||||
# Secret required for client credentials flow
|
||||
generate_secret = true
|
||||
|
||||
# OAuth configuration for M2M
|
||||
allowed_oauth_flows = ["client_credentials"]
|
||||
allowed_oauth_flows_user_pool_client = true
|
||||
|
||||
# Resource server scopes
|
||||
allowed_oauth_scopes = [
|
||||
"${aws_cognito_resource_server.gateway.identifier}/read",
|
||||
"${aws_cognito_resource_server.gateway.identifier}/write"
|
||||
]
|
||||
|
||||
# Supported identity providers
|
||||
supported_identity_providers = ["COGNITO"]
|
||||
|
||||
# Token validity for M2M
|
||||
access_token_validity = 1
|
||||
|
||||
token_validity_units {
|
||||
access_token = "hours"
|
||||
}
|
||||
|
||||
depends_on = [aws_cognito_resource_server.gateway]
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
resource "aws_cloudwatch_log_group" "copilotkit_runtime" {
|
||||
name = "/aws/lambda/${var.stack_name_base}-copilotkit-runtime"
|
||||
retention_in_days = local.log_retention_days
|
||||
}
|
||||
|
||||
data "aws_ssm_parameter" "langgraph_runtime_arn" {
|
||||
name = "/langgraph-stack/runtime-arn"
|
||||
}
|
||||
|
||||
data "aws_ssm_parameter" "strands_runtime_arn" {
|
||||
name = "/strands-stack/runtime-arn"
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "copilotkit_runtime_assume_role" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["lambda.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "copilotkit_runtime" {
|
||||
name = "${var.stack_name_base}-copilotkit-runtime-role"
|
||||
assume_role_policy = data.aws_iam_policy_document.copilotkit_runtime_assume_role.json
|
||||
description = "Execution role for CopilotKit runtime Lambda"
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "copilotkit_runtime_policy" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
resources = ["${aws_cloudwatch_log_group.copilotkit_runtime.arn}:*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "copilotkit_runtime" {
|
||||
name = "${var.stack_name_base}-copilotkit-runtime-policy"
|
||||
role = aws_iam_role.copilotkit_runtime.id
|
||||
policy = data.aws_iam_policy_document.copilotkit_runtime_policy.json
|
||||
}
|
||||
|
||||
resource "null_resource" "copilotkit_runtime_build" {
|
||||
triggers = {
|
||||
source_hash = sha256(join("", [
|
||||
for f in fileset(local.copilotkit_runtime_source_path, "**") :
|
||||
filesha256("${local.copilotkit_runtime_source_path}/${f}")
|
||||
if !endswith(f, "/")
|
||||
]))
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
command = <<-EOT
|
||||
set -e
|
||||
BUILD_DIR="${path.module}/artifacts/copilotkit_runtime_build"
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cp -R ${local.copilotkit_runtime_source_path}/src "$BUILD_DIR/"
|
||||
cp ${local.copilotkit_runtime_source_path}/package.json "$BUILD_DIR/"
|
||||
cp ${local.copilotkit_runtime_source_path}/package-lock.json "$BUILD_DIR/"
|
||||
cp ${local.copilotkit_runtime_source_path}/tsconfig.json "$BUILD_DIR/"
|
||||
cd "$BUILD_DIR"
|
||||
npm ci --no-audit --no-fund
|
||||
npm run build
|
||||
npm prune --omit=dev
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
data "archive_file" "copilotkit_runtime" {
|
||||
type = "zip"
|
||||
source_dir = "${path.module}/artifacts/copilotkit_runtime_build"
|
||||
output_path = "${path.module}/artifacts/copilotkit_runtime.zip"
|
||||
excludes = ["__pycache__", "*.pyc"]
|
||||
|
||||
depends_on = [null_resource.copilotkit_runtime_build]
|
||||
}
|
||||
|
||||
resource "aws_lambda_function" "copilotkit_runtime" {
|
||||
function_name = "${var.stack_name_base}-copilotkit-runtime"
|
||||
role = aws_iam_role.copilotkit_runtime.arn
|
||||
handler = "dist/index.handler"
|
||||
runtime = "nodejs20.x"
|
||||
architectures = ["arm64"]
|
||||
timeout = 30
|
||||
memory_size = 1024
|
||||
|
||||
filename = data.archive_file.copilotkit_runtime.output_path
|
||||
source_code_hash = data.archive_file.copilotkit_runtime.output_base64sha256
|
||||
|
||||
environment {
|
||||
variables = {
|
||||
AGENTCORE_AG_UI_URL = "https://bedrock-agentcore.${local.region}.amazonaws.com/runtimes/${urlencode(var.backend_pattern == "strands-single-agent" ? data.aws_ssm_parameter.strands_runtime_arn.value : data.aws_ssm_parameter.langgraph_runtime_arn.value)}/invocations"
|
||||
COPILOTKIT_AGENT_NAME = var.backend_pattern
|
||||
LANGGRAPH_AGENTCORE_AG_UI_URL = "https://bedrock-agentcore.${local.region}.amazonaws.com/runtimes/${urlencode(data.aws_ssm_parameter.langgraph_runtime_arn.value)}/invocations"
|
||||
STRANDS_AGENTCORE_AG_UI_URL = "https://bedrock-agentcore.${local.region}.amazonaws.com/runtimes/${urlencode(data.aws_ssm_parameter.strands_runtime_arn.value)}/invocations"
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_cloudwatch_log_group.copilotkit_runtime,
|
||||
aws_iam_role_policy.copilotkit_runtime
|
||||
]
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_api" "copilotkit_runtime" {
|
||||
name = "${var.stack_name_base}-copilotkit-runtime-api"
|
||||
protocol_type = "HTTP"
|
||||
|
||||
cors_configuration {
|
||||
allow_headers = ["content-type", "authorization"]
|
||||
allow_methods = ["GET", "OPTIONS", "POST"]
|
||||
allow_origins = [var.frontend_url, "http://localhost:3000"]
|
||||
max_age = 300
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_integration" "copilotkit_runtime" {
|
||||
api_id = aws_apigatewayv2_api.copilotkit_runtime.id
|
||||
integration_type = "AWS_PROXY"
|
||||
integration_uri = aws_lambda_function.copilotkit_runtime.invoke_arn
|
||||
integration_method = "POST"
|
||||
payload_format_version = "2.0"
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_route" "copilotkit_runtime_root_get" {
|
||||
api_id = aws_apigatewayv2_api.copilotkit_runtime.id
|
||||
route_key = "GET /copilotkit"
|
||||
target = "integrations/${aws_apigatewayv2_integration.copilotkit_runtime.id}"
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_route" "copilotkit_runtime_root_post" {
|
||||
api_id = aws_apigatewayv2_api.copilotkit_runtime.id
|
||||
route_key = "POST /copilotkit"
|
||||
target = "integrations/${aws_apigatewayv2_integration.copilotkit_runtime.id}"
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_route" "copilotkit_runtime_proxy_get" {
|
||||
api_id = aws_apigatewayv2_api.copilotkit_runtime.id
|
||||
route_key = "GET /copilotkit/{proxy+}"
|
||||
target = "integrations/${aws_apigatewayv2_integration.copilotkit_runtime.id}"
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_route" "copilotkit_runtime_proxy_post" {
|
||||
api_id = aws_apigatewayv2_api.copilotkit_runtime.id
|
||||
route_key = "POST /copilotkit/{proxy+}"
|
||||
target = "integrations/${aws_apigatewayv2_integration.copilotkit_runtime.id}"
|
||||
}
|
||||
|
||||
resource "aws_apigatewayv2_stage" "copilotkit_runtime" {
|
||||
api_id = aws_apigatewayv2_api.copilotkit_runtime.id
|
||||
name = "prod"
|
||||
auto_deploy = true
|
||||
}
|
||||
|
||||
resource "aws_lambda_permission" "copilotkit_runtime" {
|
||||
statement_id = "AllowCopilotKitRuntimeInvoke"
|
||||
action = "lambda:InvokeFunction"
|
||||
function_name = aws_lambda_function.copilotkit_runtime.function_name
|
||||
principal = "apigateway.amazonaws.com"
|
||||
source_arn = "${aws_apigatewayv2_api.copilotkit_runtime.execution_arn}/*/*"
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# AgentCore Gateway
|
||||
# Maps to: backend-stack.ts createAgentCoreGateway()
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# IAM Role for Gateway
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
data "aws_iam_policy_document" "gateway_assume_role" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["bedrock-agentcore.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "gateway" {
|
||||
name = "${var.stack_name_base}-gateway-role"
|
||||
assume_role_policy = data.aws_iam_policy_document.gateway_assume_role.json
|
||||
description = "Role for AgentCore Gateway"
|
||||
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "gateway_policy" {
|
||||
# Bedrock permissions (region-agnostic)
|
||||
statement {
|
||||
sid = "BedrockInvoke"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:bedrock:*::foundation-model/*",
|
||||
"arn:aws:bedrock:*:${local.account_id}:inference-profile/*"
|
||||
]
|
||||
}
|
||||
|
||||
# SSM parameter access
|
||||
statement {
|
||||
sid = "SSMAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters"
|
||||
]
|
||||
resources = ["arn:aws:ssm:${local.region}:${local.account_id}:parameter/${var.stack_name_base}/*"]
|
||||
}
|
||||
|
||||
# Cognito permissions
|
||||
statement {
|
||||
sid = "CognitoAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"cognito-idp:DescribeUserPoolClient",
|
||||
"cognito-idp:InitiateAuth"
|
||||
]
|
||||
resources = [var.user_pool_arn]
|
||||
}
|
||||
|
||||
# CloudWatch Logs
|
||||
statement {
|
||||
sid = "CloudWatchLogs"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
resources = ["arn:aws:logs:${local.region}:${local.account_id}:log-group:/aws/bedrock-agentcore/*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "gateway" {
|
||||
name = "${var.stack_name_base}-gateway-policy"
|
||||
role = aws_iam_role.gateway.id
|
||||
policy = data.aws_iam_policy_document.gateway_policy.json
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Wait for IAM permission propagation
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "time_sleep" "gateway_iam_propagation" {
|
||||
create_duration = "10s"
|
||||
|
||||
depends_on = [aws_iam_role_policy.gateway]
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# AgentCore Gateway
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "aws_bedrockagentcore_gateway" "main" {
|
||||
name = "${var.stack_name_base}-gateway"
|
||||
role_arn = aws_iam_role.gateway.arn
|
||||
description = "AgentCore Gateway with MCP protocol and JWT authentication"
|
||||
|
||||
protocol_type = "MCP"
|
||||
protocol_configuration {
|
||||
mcp {
|
||||
supported_versions = ["2025-03-26"]
|
||||
}
|
||||
}
|
||||
|
||||
authorizer_type = "CUSTOM_JWT"
|
||||
authorizer_configuration {
|
||||
custom_jwt_authorizer {
|
||||
discovery_url = local.oidc_discovery_url
|
||||
allowed_clients = [aws_cognito_user_pool_client.machine.id]
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [time_sleep.gateway_iam_propagation]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# Data Sources (shared across all backend resources)
|
||||
# =============================================================================
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# =============================================================================
|
||||
# Local Values
|
||||
# =============================================================================
|
||||
|
||||
locals {
|
||||
account_id = data.aws_caller_identity.current.account_id
|
||||
region = data.aws_region.current.id
|
||||
|
||||
# Normalized stack name (lowercase, hyphens only)
|
||||
stack_name_normalized = lower(replace(var.stack_name_base, "_", "-"))
|
||||
|
||||
# Stack name for resource naming (underscores for some AWS resources)
|
||||
stack_name_underscore = replace(var.stack_name_base, "-", "_")
|
||||
|
||||
# Agent name used in runtime naming
|
||||
agent_name = "FASTAgent"
|
||||
|
||||
# Runtime name (underscores required by AgentCore)
|
||||
runtime_name = "${local.stack_name_underscore}_${local.agent_name}"
|
||||
|
||||
# Memory name (unique within account/region)
|
||||
# Must match ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ - no hyphens allowed
|
||||
memory_name = "${local.stack_name_underscore}_memory"
|
||||
|
||||
# OIDC discovery URL for Cognito JWT authorizer
|
||||
oidc_discovery_url = "https://cognito-idp.${local.region}.amazonaws.com/${var.user_pool_id}/.well-known/openid-configuration"
|
||||
|
||||
# Lambda Powertools layer ARN (region-specific, Python 3.13, ARM64)
|
||||
powertools_layer_arn = "arn:aws:lambda:${local.region}:017000801446:layer:AWSLambdaPowertoolsPythonV3-python313-arm64:18"
|
||||
|
||||
# Deployment type flags
|
||||
is_docker = var.backend_deployment_type == "docker"
|
||||
is_zip = var.backend_deployment_type == "zip"
|
||||
|
||||
# Pattern flags
|
||||
is_claude_agent_sdk = contains(["claude-agent-sdk-single-agent", "claude-agent-sdk-multi-agent"], var.backend_pattern)
|
||||
|
||||
# Project paths (for zip packaging)
|
||||
project_root = "${path.module}/../../.."
|
||||
pattern_dir = "${local.project_root}/patterns/${var.backend_pattern}"
|
||||
|
||||
# Zip deployment configuration
|
||||
zip_entry_point = ["opentelemetry-instrument", "basic_agent.py"]
|
||||
zip_packager_lambda_source_path = "${path.module}/../../lambdas/zip-packager"
|
||||
|
||||
# Lambda source paths
|
||||
feedback_lambda_source_path = "${path.module}/../../../infra-cdk/lambdas/feedback"
|
||||
copilotkit_runtime_source_path = "${path.module}/../../../infra-cdk/lambdas/copilotkit-runtime"
|
||||
|
||||
# SSM parameter paths
|
||||
ssm_parameter_prefix = "/${var.stack_name_base}"
|
||||
|
||||
# Log retention in days
|
||||
log_retention_days = var.log_retention_days
|
||||
|
||||
# API Gateway settings
|
||||
api_throttling_rate_limit = var.throttling_rate_limit
|
||||
api_throttling_burst_limit = var.throttling_burst_limit
|
||||
api_cache_ttl_seconds = 300
|
||||
|
||||
# Memory event expiry (hardcoded in CDK backend-stack.ts)
|
||||
memory_event_expiry_days = 30
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# AgentCore Memory
|
||||
# Maps to: backend-stack.ts createAgentCoreRuntime() - memory section
|
||||
# =============================================================================
|
||||
|
||||
# IAM Role for Memory Execution
|
||||
# Role assumed by AgentCore Memory service for processing operations
|
||||
|
||||
data "aws_iam_policy_document" "memory_assume_role" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["bedrock-agentcore.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "memory_execution" {
|
||||
name = "${var.stack_name_base}-memory-execution-role"
|
||||
assume_role_policy = data.aws_iam_policy_document.memory_assume_role.json
|
||||
description = "Execution role for AgentCore Memory"
|
||||
}
|
||||
|
||||
# Attach the AWS managed policy for Bedrock model inference
|
||||
# Required for long-term memory strategies that use model processing
|
||||
resource "aws_iam_role_policy_attachment" "memory_bedrock_policy" {
|
||||
role = aws_iam_role.memory_execution.name
|
||||
policy_arn = "arn:aws:iam::aws:policy/AmazonBedrockAgentCoreMemoryBedrockModelInferenceExecutionRolePolicy"
|
||||
}
|
||||
|
||||
# Persistent memory resource for AI agent interactions
|
||||
# Configured with short-term memory (conversation history) as default
|
||||
resource "aws_bedrockagentcore_memory" "main" {
|
||||
name = local.memory_name
|
||||
event_expiry_duration = local.memory_event_expiry_days
|
||||
description = "Short-term memory for ${var.stack_name_base} agent"
|
||||
|
||||
# Memory execution role for model processing (required for long-term strategies)
|
||||
memory_execution_role_arn = aws_iam_role.memory_execution.arn
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name_base}_Memory"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# OAuth2 Credential Provider
|
||||
# Maps to: backend-stack.ts createOAuth2CredentialProvider()
|
||||
# =============================================================================
|
||||
# Creates Lambda function that manages OAuth2 Credential Provider lifecycle
|
||||
# for AgentCore Runtime to authenticate with AgentCore Gateway.
|
||||
# Uses CloudFormation Custom Resource pattern via null_resource invocation.
|
||||
#
|
||||
# Background:
|
||||
# AgentCore doesn't have a native Terraform/CloudFormation resource for OAuth2
|
||||
# Credential Provider yet. This Lambda calls the bedrock-agentcore-control API
|
||||
# directly to create/update/delete the provider. The Custom Resource pattern
|
||||
# is used to avoid logging sensitive credentials in CloudWatch (client secret
|
||||
# is read from Secrets Manager at runtime).
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# CloudWatch Log Group for OAuth2 Provider Lambda
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "aws_cloudwatch_log_group" "oauth2_provider" {
|
||||
name = "/aws/lambda/${var.stack_name_base}-oauth2-provider"
|
||||
retention_in_days = 7
|
||||
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# IAM Role for OAuth2 Provider Lambda
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
data "aws_iam_policy_document" "oauth2_provider_assume_role" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["lambda.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "oauth2_provider" {
|
||||
name = "${var.stack_name_base}-oauth2-provider-role"
|
||||
assume_role_policy = data.aws_iam_policy_document.oauth2_provider_assume_role.json
|
||||
|
||||
}
|
||||
|
||||
# IAM Policy for OAuth2 Provider Lambda
|
||||
data "aws_iam_policy_document" "oauth2_provider_policy" {
|
||||
# CloudWatch Logs
|
||||
statement {
|
||||
sid = "CloudWatchLogsAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
resources = ["${aws_cloudwatch_log_group.oauth2_provider.arn}:*"]
|
||||
}
|
||||
|
||||
# Read Machine Client Secret
|
||||
# Lambda needs to read the machine client secret to register OAuth2 provider
|
||||
statement {
|
||||
sid = "ReadMachineClientSecret"
|
||||
effect = "Allow"
|
||||
actions = ["secretsmanager:GetSecretValue"]
|
||||
resources = [aws_secretsmanager_secret.machine_client_secret.arn]
|
||||
}
|
||||
|
||||
# OAuth2 Credential Provider Operations
|
||||
# Note: Need both vault-level and nested resource permissions because:
|
||||
# - CreateOauth2CredentialProvider checks permission on vault itself (token-vault/default)
|
||||
# - Also checks permission on the nested resource path (token-vault/default/oauth2credentialprovider/*)
|
||||
statement {
|
||||
sid = "OAuth2CredentialProviderOperations"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock-agentcore:CreateOauth2CredentialProvider",
|
||||
"bedrock-agentcore:GetOauth2CredentialProvider",
|
||||
"bedrock-agentcore:UpdateOauth2CredentialProvider",
|
||||
"bedrock-agentcore:DeleteOauth2CredentialProvider"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:token-vault/default",
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:token-vault/default/oauth2credentialprovider/*"
|
||||
]
|
||||
}
|
||||
|
||||
# Token Vault Operations
|
||||
# Note: Need both exact match (default) and wildcard (default/*) because:
|
||||
# - AWS checks permission on the vault container itself (token-vault/default)
|
||||
# - AWS also checks permission on resources inside (token-vault/default/*)
|
||||
statement {
|
||||
sid = "TokenVaultOperations"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock-agentcore:CreateTokenVault",
|
||||
"bedrock-agentcore:GetTokenVault",
|
||||
"bedrock-agentcore:DeleteTokenVault"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:token-vault/default",
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:token-vault/default/*"
|
||||
]
|
||||
}
|
||||
|
||||
# Token Vault Secret Management
|
||||
# Lambda creates secrets in AgentCore Identity namespace for Token Vault
|
||||
statement {
|
||||
sid = "TokenVaultSecretManagement"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"secretsmanager:CreateSecret",
|
||||
"secretsmanager:DeleteSecret",
|
||||
"secretsmanager:DescribeSecret",
|
||||
"secretsmanager:PutSecretValue"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:secretsmanager:${local.region}:${local.account_id}:secret:bedrock-agentcore-identity!default/oauth2/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "oauth2_provider" {
|
||||
name = "${var.stack_name_base}-oauth2-provider-policy"
|
||||
role = aws_iam_role.oauth2_provider.id
|
||||
policy = data.aws_iam_policy_document.oauth2_provider_policy.json
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Lambda Function for OAuth2 Provider Lifecycle
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Package the Lambda code
|
||||
data "archive_file" "oauth2_provider" {
|
||||
type = "zip"
|
||||
source_file = "${path.module}/../../../infra-cdk/lambdas/oauth2-provider/index.py"
|
||||
output_path = "${path.module}/artifacts/oauth2-provider.zip"
|
||||
}
|
||||
|
||||
resource "aws_lambda_function" "oauth2_provider" {
|
||||
filename = data.archive_file.oauth2_provider.output_path
|
||||
function_name = "${var.stack_name_base}-oauth2-provider"
|
||||
role = aws_iam_role.oauth2_provider.arn
|
||||
handler = "index.handler"
|
||||
source_code_hash = data.archive_file.oauth2_provider.output_base64sha256
|
||||
runtime = "python3.13"
|
||||
timeout = 300 # 5 minutes
|
||||
|
||||
|
||||
depends_on = [
|
||||
aws_cloudwatch_log_group.oauth2_provider,
|
||||
aws_iam_role_policy.oauth2_provider
|
||||
]
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Custom Resource Invocation via null_resource
|
||||
# Simulates CloudFormation Custom Resource by invoking Lambda directly
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "null_resource" "invoke_oauth2_provider" {
|
||||
# Recreate when any of these values change
|
||||
triggers = {
|
||||
provider_name = "${var.stack_name_base}-runtime-gateway-auth"
|
||||
client_id = aws_cognito_user_pool_client.machine.id
|
||||
client_secret = aws_secretsmanager_secret_version.machine_client_secret.version_id
|
||||
discovery_url = local.oidc_discovery_url
|
||||
function_name = aws_lambda_function.oauth2_provider.function_name
|
||||
region = local.region
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
interpreter = ["bash", "-c"]
|
||||
command = <<-EOT
|
||||
set -e
|
||||
|
||||
# Build the CloudFormation Custom Resource payload
|
||||
PAYLOAD=$(cat <<'PAYLOAD_EOF'
|
||||
{
|
||||
"RequestType": "Create",
|
||||
"ResourceProperties": {
|
||||
"ProviderName": "${self.triggers.provider_name}",
|
||||
"ClientSecretArn": "${aws_secretsmanager_secret.machine_client_secret.arn}",
|
||||
"DiscoveryUrl": "${self.triggers.discovery_url}",
|
||||
"ClientId": "${self.triggers.client_id}"
|
||||
}
|
||||
}
|
||||
PAYLOAD_EOF
|
||||
)
|
||||
|
||||
echo "Invoking OAuth2 provider Lambda: ${self.triggers.function_name}"
|
||||
|
||||
# Invoke Lambda (--cli-binary-format raw-in-base64-out ensures JSON payload is accepted)
|
||||
aws lambda invoke \
|
||||
--function-name ${self.triggers.function_name} \
|
||||
--cli-binary-format raw-in-base64-out \
|
||||
--payload "$PAYLOAD" \
|
||||
--region ${self.triggers.region} \
|
||||
/tmp/oauth2_provider_response.json
|
||||
|
||||
# Check for errors
|
||||
if grep -q "FunctionError" /tmp/oauth2_provider_response.json; then
|
||||
echo "ERROR: OAuth2 provider creation failed"
|
||||
cat /tmp/oauth2_provider_response.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "OAuth2 provider created successfully"
|
||||
cat /tmp/oauth2_provider_response.json
|
||||
EOT
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
when = destroy
|
||||
interpreter = ["bash", "-c"]
|
||||
command = <<-EOT
|
||||
# Build the CloudFormation Custom Resource Delete payload
|
||||
PAYLOAD=$(cat <<'PAYLOAD_EOF'
|
||||
{
|
||||
"RequestType": "Delete",
|
||||
"PhysicalResourceId": "${self.triggers.provider_name}",
|
||||
"ResourceProperties": {
|
||||
"ProviderName": "${self.triggers.provider_name}"
|
||||
}
|
||||
}
|
||||
PAYLOAD_EOF
|
||||
)
|
||||
|
||||
echo "Deleting OAuth2 provider: ${self.triggers.provider_name}"
|
||||
|
||||
# Invoke Lambda for deletion (ignore errors if already deleted)
|
||||
aws lambda invoke \
|
||||
--function-name ${self.triggers.function_name} \
|
||||
--cli-binary-format raw-in-base64-out \
|
||||
--payload "$PAYLOAD" \
|
||||
--region ${self.triggers.region} \
|
||||
/tmp/oauth2_provider_delete.json || true
|
||||
|
||||
echo "OAuth2 provider deletion completed"
|
||||
cat /tmp/oauth2_provider_delete.json || true
|
||||
EOT
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_lambda_function.oauth2_provider,
|
||||
aws_cognito_user_pool_client.machine,
|
||||
aws_secretsmanager_secret_version.machine_client_secret
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# Memory Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "memory_arn" {
|
||||
description = "AgentCore Memory ARN"
|
||||
value = aws_bedrockagentcore_memory.main.arn
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Gateway Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "gateway_id" {
|
||||
description = "AgentCore Gateway ID"
|
||||
value = aws_bedrockagentcore_gateway.main.gateway_id
|
||||
}
|
||||
|
||||
output "gateway_arn" {
|
||||
description = "AgentCore Gateway ARN"
|
||||
value = aws_bedrockagentcore_gateway.main.gateway_arn
|
||||
}
|
||||
|
||||
output "gateway_url" {
|
||||
description = "AgentCore Gateway URL"
|
||||
value = aws_bedrockagentcore_gateway.main.gateway_url
|
||||
}
|
||||
|
||||
output "gateway_target_id" {
|
||||
description = "AgentCore Gateway Target ID"
|
||||
value = aws_bedrockagentcore_gateway_target.sample_tool.target_id
|
||||
}
|
||||
|
||||
output "tool_lambda_arn" {
|
||||
description = "Sample tool Lambda function ARN"
|
||||
value = aws_lambda_function.sample_tool.arn
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Runtime Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "runtime_id" {
|
||||
description = "AgentCore Runtime ID"
|
||||
value = aws_bedrockagentcore_agent_runtime.main.agent_runtime_id
|
||||
}
|
||||
|
||||
output "runtime_arn" {
|
||||
description = "AgentCore Runtime ARN"
|
||||
value = aws_bedrockagentcore_agent_runtime.main.agent_runtime_arn
|
||||
}
|
||||
|
||||
output "runtime_role_arn" {
|
||||
description = "AgentCore Runtime execution role ARN"
|
||||
value = aws_iam_role.runtime.arn
|
||||
}
|
||||
|
||||
output "copilotkit_runtime_url" {
|
||||
description = "CopilotKit runtime endpoint URL"
|
||||
value = "${aws_apigatewayv2_stage.copilotkit_runtime.invoke_url}/copilotkit"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Machine Client Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "machine_client_id" {
|
||||
description = "Cognito Machine Client ID (for M2M authentication)"
|
||||
value = aws_cognito_user_pool_client.machine.id
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# AgentCore Runtime
|
||||
# Maps to: backend-stack.ts createAgentCoreRuntime() - runtime section
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ECR Repository (for container image)
|
||||
# Only created if container_uri is not provided
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "aws_ecr_repository" "agent" {
|
||||
count = local.is_docker && var.container_uri == null ? 1 : 0
|
||||
|
||||
name = "${var.stack_name_base}-agent-runtime"
|
||||
image_tag_mutability = "MUTABLE"
|
||||
force_delete = true
|
||||
|
||||
image_scanning_configuration {
|
||||
scan_on_push = true
|
||||
}
|
||||
|
||||
encryption_configuration {
|
||||
encryption_type = "AES256"
|
||||
}
|
||||
}
|
||||
|
||||
# ECR Lifecycle policy to keep only recent images
|
||||
resource "aws_ecr_lifecycle_policy" "agent" {
|
||||
count = local.is_docker && var.container_uri == null ? 1 : 0
|
||||
|
||||
repository = aws_ecr_repository.agent[0].name
|
||||
|
||||
policy = jsonencode({
|
||||
rules = [
|
||||
{
|
||||
rulePriority = 1
|
||||
description = "Keep only 5 most recent images"
|
||||
selection = {
|
||||
tagStatus = "any"
|
||||
countType = "imageCountMoreThan"
|
||||
countNumber = 5
|
||||
}
|
||||
action = {
|
||||
type = "expire"
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Docker Build & Push (docker mode only)
|
||||
# Automatically builds and pushes the agent container image during apply
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# Content hash for Docker image change detection — triggers rebuild and runtime replacement.
|
||||
# Always created (no count) so the runtime's replace_triggered_by can reference it in both modes.
|
||||
# In zip mode the value is static ("zip"), so it never triggers a replacement.
|
||||
resource "terraform_data" "docker_image_hash" {
|
||||
input = local.is_docker && var.container_uri == null ? sha256(join("", concat(
|
||||
[filesha256("${local.pattern_dir}/Dockerfile")],
|
||||
[filesha256("${local.pattern_dir}/requirements.txt")],
|
||||
[for f in fileset(local.pattern_dir, "**/*.py") : filesha256("${local.pattern_dir}/${f}")],
|
||||
[for f in fileset("${local.project_root}/patterns/utils", "**/*.py") : filesha256("${local.project_root}/patterns/utils/${f}")],
|
||||
[for f in fileset("${local.project_root}/gateway", "**/*.py") : filesha256("${local.project_root}/gateway/${f}")],
|
||||
[for f in fileset("${local.project_root}/tools", "**/*.py") : filesha256("${local.project_root}/tools/${f}")],
|
||||
[filesha256("${local.project_root}/pyproject.toml")],
|
||||
))) : "zip"
|
||||
}
|
||||
|
||||
resource "null_resource" "docker_build_push" {
|
||||
count = local.is_docker && var.container_uri == null ? 1 : 0
|
||||
|
||||
triggers = {
|
||||
content_hash = terraform_data.docker_image_hash.output
|
||||
repository_url = aws_ecr_repository.agent[0].repository_url
|
||||
}
|
||||
|
||||
provisioner "local-exec" {
|
||||
interpreter = ["bash", "-c"]
|
||||
command = <<-EOT
|
||||
set -e
|
||||
|
||||
ECR_REPO="${aws_ecr_repository.agent[0].repository_url}"
|
||||
REGION="${local.region}"
|
||||
ACCOUNT_ID="${local.account_id}"
|
||||
PROJECT_ROOT="${local.project_root}"
|
||||
DOCKERFILE="patterns/${var.backend_pattern}/Dockerfile"
|
||||
|
||||
# Verify Docker is running
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "ERROR: Docker is not running. Please start Docker Desktop." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ECR login
|
||||
echo "Logging into ECR..."
|
||||
aws ecr get-login-password --region "$REGION" | \
|
||||
docker login --username AWS --password-stdin "$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com"
|
||||
|
||||
# Build ARM64 image
|
||||
echo "Building Docker image (ARM64)..."
|
||||
cd "$PROJECT_ROOT"
|
||||
docker build \
|
||||
--platform linux/arm64 \
|
||||
-f "$DOCKERFILE" \
|
||||
-t "$ECR_REPO:latest" \
|
||||
.
|
||||
|
||||
# Push to ECR
|
||||
echo "Pushing image to ECR..."
|
||||
docker push "$ECR_REPO:latest"
|
||||
|
||||
echo "SUCCESS: Image pushed to $ECR_REPO:latest"
|
||||
EOT
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_ecr_repository.agent[0],
|
||||
aws_ecr_lifecycle_policy.agent[0]
|
||||
]
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# IAM Role for AgentCore Runtime
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
data "aws_iam_policy_document" "runtime_assume_role" {
|
||||
statement {
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["bedrock-agentcore.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "runtime" {
|
||||
name = "${var.stack_name_base}-agentcore-runtime-role"
|
||||
assume_role_policy = data.aws_iam_policy_document.runtime_assume_role.json
|
||||
description = "Execution role for AgentCore Runtime"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# IAM Policy Document
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
data "aws_iam_policy_document" "runtime_policy" {
|
||||
# ECRImageAccess (docker mode only)
|
||||
dynamic "statement" {
|
||||
for_each = local.is_docker ? [1] : []
|
||||
content {
|
||||
sid = "ECRImageAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ecr:BatchGetImage",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchCheckLayerAvailability"
|
||||
]
|
||||
resources = ["arn:aws:ecr:${local.region}:${local.account_id}:repository/*"]
|
||||
}
|
||||
}
|
||||
|
||||
# ECRTokenAccess (docker mode only)
|
||||
dynamic "statement" {
|
||||
for_each = local.is_docker ? [1] : []
|
||||
content {
|
||||
sid = "ECRTokenAccess"
|
||||
effect = "Allow"
|
||||
actions = ["ecr:GetAuthorizationToken"]
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
# S3 Agent Code Access (zip mode only)
|
||||
dynamic "statement" {
|
||||
for_each = local.is_zip ? [1] : []
|
||||
content {
|
||||
sid = "S3AgentCodeAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"s3:GetObject",
|
||||
"s3:GetBucketLocation"
|
||||
]
|
||||
resources = [
|
||||
aws_s3_bucket.agent_code[0].arn,
|
||||
"${aws_s3_bucket.agent_code[0].arn}/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# CloudWatchLogsGroupAccess
|
||||
statement {
|
||||
sid = "CloudWatchLogsGroupAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"logs:DescribeLogStreams",
|
||||
"logs:CreateLogGroup"
|
||||
]
|
||||
resources = ["arn:aws:logs:${local.region}:${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"]
|
||||
}
|
||||
|
||||
# CloudWatchLogsDescribeGroups
|
||||
statement {
|
||||
sid = "CloudWatchLogsDescribeGroups"
|
||||
effect = "Allow"
|
||||
actions = ["logs:DescribeLogGroups"]
|
||||
resources = ["arn:aws:logs:${local.region}:${local.account_id}:log-group:*"]
|
||||
}
|
||||
|
||||
# CloudWatchLogsStreamAccess
|
||||
statement {
|
||||
sid = "CloudWatchLogsStreamAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents"
|
||||
]
|
||||
resources = ["arn:aws:logs:${local.region}:${local.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:log-stream:*"]
|
||||
}
|
||||
|
||||
# X-Ray Tracing
|
||||
statement {
|
||||
sid = "XRayTracing"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
"xray:GetSamplingRules",
|
||||
"xray:GetSamplingTargets"
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
# CloudWatch Metrics
|
||||
statement {
|
||||
sid = "CloudWatchMetrics"
|
||||
effect = "Allow"
|
||||
actions = ["cloudwatch:PutMetricData"]
|
||||
resources = ["*"]
|
||||
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "cloudwatch:namespace"
|
||||
values = ["bedrock-agentcore"]
|
||||
}
|
||||
}
|
||||
|
||||
# GetAgentAccessToken
|
||||
statement {
|
||||
sid = "GetAgentAccessToken"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock-agentcore:GetWorkloadAccessToken",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForJWT",
|
||||
"bedrock-agentcore:GetWorkloadAccessTokenForUserId"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:workload-identity-directory/default",
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:workload-identity-directory/default/workload-identity/*"
|
||||
]
|
||||
}
|
||||
|
||||
# BedrockModelInvocation
|
||||
statement {
|
||||
sid = "BedrockModelInvocation"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock:InvokeModel",
|
||||
"bedrock:InvokeModelWithResponseStream"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:bedrock:*::foundation-model/*",
|
||||
"arn:aws:bedrock:${local.region}:${local.account_id}:*"
|
||||
]
|
||||
}
|
||||
|
||||
# SecretsManagerOAuth2Access
|
||||
# Runtime needs to read OAuth2 credentials from Token Vault secret
|
||||
# created by AgentCore Identity (not the machine client secret directly)
|
||||
statement {
|
||||
sid = "SecretsManagerOAuth2Access"
|
||||
effect = "Allow"
|
||||
actions = ["secretsmanager:GetSecretValue"]
|
||||
resources = [
|
||||
"arn:aws:secretsmanager:${local.region}:${local.account_id}:secret:bedrock-agentcore-identity!default/oauth2/${var.stack_name_base}-runtime-gateway-auth*"
|
||||
]
|
||||
}
|
||||
|
||||
# MemoryResourceAccess - references memory resource directly (no variable passing)
|
||||
statement {
|
||||
sid = "MemoryResourceAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock-agentcore:CreateEvent",
|
||||
"bedrock-agentcore:GetEvent",
|
||||
"bedrock-agentcore:ListEvents",
|
||||
"bedrock-agentcore:RetrieveMemoryRecords"
|
||||
]
|
||||
resources = [aws_bedrockagentcore_memory.main.arn]
|
||||
}
|
||||
|
||||
# SSMParameterAccess
|
||||
statement {
|
||||
sid = "SSMParameterAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters"
|
||||
]
|
||||
resources = ["arn:aws:ssm:${local.region}:${local.account_id}:parameter/${var.stack_name_base}/*"]
|
||||
}
|
||||
|
||||
# CodeInterpreterAccess
|
||||
statement {
|
||||
sid = "CodeInterpreterAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock-agentcore:StartCodeInterpreterSession",
|
||||
"bedrock-agentcore:StopCodeInterpreterSession",
|
||||
"bedrock-agentcore:InvokeCodeInterpreter"
|
||||
]
|
||||
resources = ["arn:aws:bedrock-agentcore:${local.region}:aws:code-interpreter/*"]
|
||||
}
|
||||
|
||||
# OAuth2CredentialProviderAccess
|
||||
# The @requires_access_token decorator performs a two-stage process:
|
||||
# GetOauth2CredentialProvider - Looks up provider metadata
|
||||
# GetResourceOauth2Token - Fetches the actual access token from Token Vault
|
||||
statement {
|
||||
sid = "OAuth2CredentialProviderAccess"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"bedrock-agentcore:GetOauth2CredentialProvider",
|
||||
"bedrock-agentcore:GetResourceOauth2Token"
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:oauth2-credential-provider/*",
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:token-vault/*",
|
||||
"arn:aws:bedrock-agentcore:${local.region}:${local.account_id}:workload-identity-directory/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "runtime" {
|
||||
name = "${var.stack_name_base}-agentcore-runtime-policy"
|
||||
role = aws_iam_role.runtime.id
|
||||
policy = data.aws_iam_policy_document.runtime_policy.json
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Default Security Group (for VPC mode, when none provided)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
locals {
|
||||
# Use user-provided security groups, or fall back to the auto-created default
|
||||
effective_security_group_ids = (
|
||||
var.backend_network_mode == "VPC" && length(var.backend_vpc_security_group_ids) == 0
|
||||
? [aws_security_group.runtime_default[0].id]
|
||||
: var.backend_vpc_security_group_ids
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_security_group" "runtime_default" {
|
||||
count = var.backend_network_mode == "VPC" && length(var.backend_vpc_security_group_ids) == 0 ? 1 : 0
|
||||
|
||||
name = "${var.stack_name_base}-agentcore-runtime-sg"
|
||||
description = "Default security group for AgentCore Runtime VPC deployment"
|
||||
vpc_id = var.backend_vpc_id
|
||||
|
||||
tags = {
|
||||
Name = "${var.stack_name_base}-agentcore-runtime-sg"
|
||||
}
|
||||
}
|
||||
|
||||
# Self-referencing ingress rule: allows HTTPS traffic between runtime and VPC endpoints
|
||||
resource "aws_vpc_security_group_ingress_rule" "runtime_default_https" {
|
||||
count = var.backend_network_mode == "VPC" && length(var.backend_vpc_security_group_ids) == 0 ? 1 : 0
|
||||
|
||||
security_group_id = aws_security_group.runtime_default[0].id
|
||||
referenced_security_group_id = aws_security_group.runtime_default[0].id
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
ip_protocol = "tcp"
|
||||
description = "Allow HTTPS from self (VPC endpoint access)"
|
||||
}
|
||||
|
||||
# Egress rule: allow all outbound traffic (matches CDK allowAllOutbound: true)
|
||||
resource "aws_vpc_security_group_egress_rule" "runtime_default_all" {
|
||||
count = var.backend_network_mode == "VPC" && length(var.backend_vpc_security_group_ids) == 0 ? 1 : 0
|
||||
|
||||
security_group_id = aws_security_group.runtime_default[0].id
|
||||
cidr_ipv4 = "0.0.0.0/0"
|
||||
ip_protocol = "-1"
|
||||
description = "Allow all outbound traffic"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# AgentCore Runtime
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "aws_bedrockagentcore_agent_runtime" "main" {
|
||||
agent_runtime_name = local.runtime_name
|
||||
role_arn = aws_iam_role.runtime.arn
|
||||
description = "${var.backend_pattern} agent runtime for ${var.stack_name_base}"
|
||||
|
||||
# Artifact configuration (docker or zip)
|
||||
agent_runtime_artifact {
|
||||
# Docker mode: container image from ECR or custom URI
|
||||
dynamic "container_configuration" {
|
||||
for_each = local.is_docker ? [1] : []
|
||||
content {
|
||||
container_uri = var.container_uri != null ? var.container_uri : "${aws_ecr_repository.agent[0].repository_url}:latest"
|
||||
}
|
||||
}
|
||||
|
||||
# Zip mode: S3 Python package
|
||||
dynamic "code_configuration" {
|
||||
for_each = local.is_zip ? [1] : []
|
||||
content {
|
||||
runtime = "PYTHON_3_12"
|
||||
entry_point = local.zip_entry_point
|
||||
code {
|
||||
s3 {
|
||||
bucket = aws_s3_bucket.agent_code[0].id
|
||||
prefix = "deployment_package.zip"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Network configuration
|
||||
# PUBLIC: Runtime is accessible over the public internet (default).
|
||||
# VPC: Runtime is deployed into a user-provided VPC for private network isolation.
|
||||
# The user must ensure their VPC has the necessary VPC endpoints for AWS services.
|
||||
# See docs/DEPLOYMENT.md for the full list of required VPC endpoints.
|
||||
network_configuration {
|
||||
network_mode = var.backend_network_mode
|
||||
|
||||
dynamic "network_mode_config" {
|
||||
for_each = var.backend_network_mode == "VPC" ? [1] : []
|
||||
content {
|
||||
subnets = var.backend_vpc_subnet_ids
|
||||
security_groups = local.effective_security_group_ids
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# JWT authorizer configuration (Cognito)
|
||||
authorizer_configuration {
|
||||
custom_jwt_authorizer {
|
||||
discovery_url = local.oidc_discovery_url
|
||||
allowed_clients = [var.web_client_id]
|
||||
}
|
||||
}
|
||||
|
||||
# Protocol configuration (HTTP for agent communication)
|
||||
protocol_configuration {
|
||||
server_protocol = "HTTP"
|
||||
}
|
||||
|
||||
# Request header configuration (allowlist Authorization for JWT sub claim)
|
||||
request_header_configuration {
|
||||
request_header_allowlist = ["Authorization"]
|
||||
}
|
||||
|
||||
# Environment variables for the runtime
|
||||
environment_variables = merge(
|
||||
{
|
||||
AWS_REGION = local.region
|
||||
AWS_DEFAULT_REGION = local.region
|
||||
MEMORY_ID = aws_bedrockagentcore_memory.main.id
|
||||
STACK_NAME = var.stack_name_base
|
||||
GATEWAY_CREDENTIAL_PROVIDER_NAME = "${var.stack_name_base}-runtime-gateway-auth"
|
||||
},
|
||||
# claude-agent-sdk patterns require CLAUDE_CODE_USE_BEDROCK=1
|
||||
local.is_claude_agent_sdk ? { CLAUDE_CODE_USE_BEDROCK = "1" } : {}
|
||||
)
|
||||
|
||||
# Force runtime replacement when agent code changes (zip or docker)
|
||||
lifecycle {
|
||||
precondition {
|
||||
condition = !local.is_claude_agent_sdk || local.is_docker
|
||||
error_message = "claude-agent-sdk patterns require Docker deployment (backend_deployment_type = \"docker\") because they need Node.js and the claude-code CLI installed at build time."
|
||||
}
|
||||
precondition {
|
||||
condition = var.backend_network_mode != "VPC" || (var.backend_vpc_id != null && var.backend_vpc_id != "")
|
||||
error_message = "backend_vpc_id is required when backend_network_mode is 'VPC'."
|
||||
}
|
||||
precondition {
|
||||
condition = var.backend_network_mode != "VPC" || length(var.backend_vpc_subnet_ids) > 0
|
||||
error_message = "backend_vpc_subnet_ids must contain at least one subnet ID when backend_network_mode is 'VPC'."
|
||||
}
|
||||
replace_triggered_by = [
|
||||
terraform_data.agent_code_hash,
|
||||
terraform_data.docker_image_hash,
|
||||
]
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
aws_iam_role_policy.runtime,
|
||||
null_resource.invoke_zip_packager,
|
||||
null_resource.docker_build_push,
|
||||
null_resource.invoke_oauth2_provider # Ensure provider is registered before Runtime starts
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# SSM Parameters & Secrets Manager
|
||||
# Maps to: backend-stack.ts createRuntimeSSMParameters() + createCognitoSSMParameters()
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# SSM Parameters
|
||||
# Store configuration values for cross-stack references and frontend access
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "aws_ssm_parameter" "runtime_arn" {
|
||||
name = "${local.ssm_parameter_prefix}/runtime-arn"
|
||||
description = "AgentCore Runtime ARN"
|
||||
type = "String"
|
||||
value = aws_bedrockagentcore_agent_runtime.main.agent_runtime_arn
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "cognito_user_pool_id" {
|
||||
name = "${local.ssm_parameter_prefix}/cognito-user-pool-id"
|
||||
description = "Cognito User Pool ID"
|
||||
type = "String"
|
||||
value = var.user_pool_id
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "cognito_user_pool_client_id" {
|
||||
name = "${local.ssm_parameter_prefix}/cognito-user-pool-client-id"
|
||||
description = "Cognito User Pool Client ID"
|
||||
type = "String"
|
||||
value = var.web_client_id
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "machine_client_id" {
|
||||
name = "${local.ssm_parameter_prefix}/machine_client_id"
|
||||
description = "Machine Client ID for M2M authentication"
|
||||
type = "String"
|
||||
value = aws_cognito_user_pool_client.machine.id
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "cognito_provider" {
|
||||
name = "${local.ssm_parameter_prefix}/cognito_provider"
|
||||
description = "Cognito domain URL for token endpoint"
|
||||
type = "String"
|
||||
value = var.cognito_domain_url
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "feedback_api_url" {
|
||||
name = "${local.ssm_parameter_prefix}/feedback-api-url"
|
||||
description = "Feedback API Gateway URL"
|
||||
type = "String"
|
||||
value = "${aws_api_gateway_stage.prod.invoke_url}/feedback"
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "copilotkit_runtime_url" {
|
||||
name = "${local.ssm_parameter_prefix}/copilotkit-runtime-url"
|
||||
description = "CopilotKit runtime API URL"
|
||||
type = "String"
|
||||
value = "${aws_apigatewayv2_stage.copilotkit_runtime.invoke_url}/copilotkit"
|
||||
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "gateway_url" {
|
||||
name = "${local.ssm_parameter_prefix}/gateway_url"
|
||||
description = "AgentCore Gateway URL"
|
||||
type = "String"
|
||||
value = aws_bedrockagentcore_gateway.main.gateway_url
|
||||
|
||||
}
|
||||
|
||||
# Agent Code Bucket (zip mode only) - matches CDK's AgentCodeBucketNameParam
|
||||
resource "aws_ssm_parameter" "agent_code_bucket" {
|
||||
count = local.is_zip ? 1 : 0
|
||||
|
||||
name = "${local.ssm_parameter_prefix}/agent-code-bucket"
|
||||
description = "S3 bucket for agent code deployment packages"
|
||||
type = "String"
|
||||
value = aws_s3_bucket.agent_code[0].id
|
||||
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Secrets Manager - Machine Client Secret
|
||||
# Store the machine client secret securely
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
resource "aws_secretsmanager_secret" "machine_client_secret" {
|
||||
name = "${local.ssm_parameter_prefix}/machine_client_secret"
|
||||
description = "Machine Client Secret for M2M authentication"
|
||||
|
||||
}
|
||||
|
||||
resource "aws_secretsmanager_secret_version" "machine_client_secret" {
|
||||
secret_id = aws_secretsmanager_secret.machine_client_secret.id
|
||||
secret_string = aws_cognito_user_pool_client.machine.client_secret
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# Core Configuration
|
||||
# =============================================================================
|
||||
|
||||
variable "stack_name_base" {
|
||||
description = "Base name for all resources."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "backend_pattern" {
|
||||
description = "Agent pattern to deploy."
|
||||
type = string
|
||||
default = "strands-single-agent"
|
||||
}
|
||||
|
||||
variable "backend_deployment_type" {
|
||||
description = "Deployment type: 'docker' (container via ECR) or 'zip' (Python package via S3). Note: claude-agent-sdk patterns require 'docker'."
|
||||
type = string
|
||||
default = "docker"
|
||||
}
|
||||
|
||||
variable "backend_network_mode" {
|
||||
description = "Network mode for AgentCore Runtime (PUBLIC or VPC)."
|
||||
type = string
|
||||
default = "PUBLIC"
|
||||
}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# VPC Configuration (Required if backend_network_mode = VPC)
|
||||
# =============================================================================
|
||||
|
||||
variable "backend_vpc_id" {
|
||||
description = "VPC ID for VPC network mode. Required when backend_network_mode is 'VPC'."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "backend_vpc_subnet_ids" {
|
||||
description = "List of subnet IDs for VPC network mode. Required when backend_network_mode is 'VPC'."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "backend_vpc_security_group_ids" {
|
||||
description = "List of security group IDs for VPC network mode. Optional when backend_network_mode is 'VPC'. If omitted, a default security group is created."
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Cognito Configuration (passed from cognito module)
|
||||
# =============================================================================
|
||||
|
||||
variable "user_pool_id" {
|
||||
description = "Cognito User Pool ID."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "user_pool_arn" {
|
||||
description = "Cognito User Pool ARN."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "web_client_id" {
|
||||
description = "Cognito Web Client ID (for frontend OAuth)."
|
||||
type = string
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Amplify Configuration (passed from amplify module)
|
||||
# =============================================================================
|
||||
|
||||
variable "frontend_url" {
|
||||
description = "Frontend URL for CORS and callback configuration."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cognito_domain_url" {
|
||||
description = "Cognito domain URL for OAuth token endpoint."
|
||||
type = string
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Optional Configuration
|
||||
# =============================================================================
|
||||
|
||||
variable "container_uri" {
|
||||
description = "Container image URI. If not provided, ECR repository will be created."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "log_retention_days" {
|
||||
description = "CloudWatch log retention in days."
|
||||
type = number
|
||||
default = 7
|
||||
}
|
||||
|
||||
variable "throttling_rate_limit" {
|
||||
description = "API Gateway throttling rate limit."
|
||||
type = number
|
||||
default = 100
|
||||
}
|
||||
|
||||
variable "throttling_burst_limit" {
|
||||
description = "API Gateway throttling burst limit."
|
||||
type = number
|
||||
default = 200
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.82.0"
|
||||
}
|
||||
archive = {
|
||||
source = "hashicorp/archive"
|
||||
version = ">= 2.4.0"
|
||||
}
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = ">= 3.2.0"
|
||||
}
|
||||
time = {
|
||||
source = "hashicorp/time"
|
||||
version = ">= 0.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# Data Sources
|
||||
# =============================================================================
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
# =============================================================================
|
||||
# Local Values
|
||||
# =============================================================================
|
||||
|
||||
locals {
|
||||
account_id = data.aws_caller_identity.current.account_id
|
||||
region = data.aws_region.current.id
|
||||
|
||||
# Cognito domain prefix (must be globally unique and lowercase)
|
||||
domain_prefix = "${lower(replace(var.stack_name_base, "_", "-"))}-${local.account_id}-${local.region}"
|
||||
|
||||
# Callback URLs (hardcoded to match CDK cognito-stack.ts defaults)
|
||||
default_callback_urls = ["http://localhost:3000", "https://localhost:3000"]
|
||||
|
||||
# Combine callback URLs with Amplify URL if provided
|
||||
all_callback_urls = var.amplify_url != null ? concat(local.default_callback_urls, [var.amplify_url]) : local.default_callback_urls
|
||||
|
||||
# Password minimum length (hardcoded to match CDK cognito-stack.ts)
|
||||
password_minimum_length = 8
|
||||
|
||||
# User invitation email template
|
||||
invitation_email_subject = "Welcome to ${var.stack_name_base}!"
|
||||
invitation_email_body = <<-EOF
|
||||
<p>Hello {username},</p>
|
||||
<p>Welcome to ${var.stack_name_base}! Your username is <strong>{username}</strong> and your temporary password is: <strong>{####}</strong></p>
|
||||
<p>Please use this temporary password to log in and set your permanent password.</p>
|
||||
<p>The CloudFront URL to your application is stored as an output in the "${var.stack_name_base}" stack, and will be printed to your terminal once the deployment process completes.</p>
|
||||
<p>Thanks,</p>
|
||||
<p>Fullstack AgentCore Solution Template Team</p>
|
||||
EOF
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Cognito User Pool
|
||||
# =============================================================================
|
||||
# Main user pool for authentication with password policy and invitation templates
|
||||
|
||||
resource "aws_cognito_user_pool" "main" {
|
||||
name = "${var.stack_name_base}-user-pool"
|
||||
|
||||
# Admin-only user creation (self sign-up disabled)
|
||||
admin_create_user_config {
|
||||
allow_admin_create_user_only = true
|
||||
|
||||
# User invitation email template
|
||||
invite_message_template {
|
||||
email_subject = local.invitation_email_subject
|
||||
email_message = local.invitation_email_body
|
||||
sms_message = "Your username is {username} and temporary password is {####}"
|
||||
}
|
||||
}
|
||||
|
||||
# Sign-in with email
|
||||
username_attributes = ["email"]
|
||||
|
||||
# Auto-verify email
|
||||
auto_verified_attributes = ["email"]
|
||||
|
||||
# Account recovery via email only
|
||||
account_recovery_setting {
|
||||
recovery_mechanism {
|
||||
name = "verified_email"
|
||||
priority = 1
|
||||
}
|
||||
}
|
||||
|
||||
# Password policy
|
||||
password_policy {
|
||||
minimum_length = local.password_minimum_length
|
||||
require_lowercase = true
|
||||
require_uppercase = true
|
||||
require_numbers = true
|
||||
require_symbols = true
|
||||
temporary_password_validity_days = 7
|
||||
}
|
||||
|
||||
# Email attribute (required and immutable)
|
||||
schema {
|
||||
name = "email"
|
||||
attribute_data_type = "String"
|
||||
required = true
|
||||
mutable = false
|
||||
developer_only_attribute = false
|
||||
|
||||
string_attribute_constraints {
|
||||
min_length = 0
|
||||
max_length = 2048
|
||||
}
|
||||
}
|
||||
|
||||
# Email configuration
|
||||
email_configuration {
|
||||
email_sending_account = "COGNITO_DEFAULT"
|
||||
}
|
||||
|
||||
# Allow deletion (no protection)
|
||||
deletion_protection = "INACTIVE"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Cognito User Pool Domain
|
||||
# =============================================================================
|
||||
# Domain for hosted UI with managed login V2
|
||||
|
||||
resource "aws_cognito_user_pool_domain" "main" {
|
||||
domain = local.domain_prefix
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Cognito Managed Login Branding (V2)
|
||||
# =============================================================================
|
||||
# Required for the v2 managed login to display properly
|
||||
|
||||
resource "aws_cognito_managed_login_branding" "main" {
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
client_id = aws_cognito_user_pool_client.web.id
|
||||
|
||||
# Use Cognito's default styles
|
||||
use_cognito_provided_values = true
|
||||
|
||||
depends_on = [aws_cognito_user_pool_domain.main]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Cognito User Pool Client - Web (Frontend)
|
||||
# =============================================================================
|
||||
# OAuth client for frontend application
|
||||
|
||||
resource "aws_cognito_user_pool_client" "web" {
|
||||
name = "${var.stack_name_base}-client"
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
|
||||
# No secret for public client
|
||||
generate_secret = false
|
||||
|
||||
# Auth flows
|
||||
explicit_auth_flows = [
|
||||
"ALLOW_USER_PASSWORD_AUTH",
|
||||
"ALLOW_USER_SRP_AUTH",
|
||||
"ALLOW_REFRESH_TOKEN_AUTH"
|
||||
]
|
||||
|
||||
# OAuth configuration
|
||||
allowed_oauth_flows = ["code"]
|
||||
allowed_oauth_flows_user_pool_client = true
|
||||
allowed_oauth_scopes = ["openid", "email", "profile"]
|
||||
|
||||
# Callback and logout URLs
|
||||
callback_urls = local.all_callback_urls
|
||||
logout_urls = local.all_callback_urls
|
||||
|
||||
# Supported identity providers
|
||||
supported_identity_providers = ["COGNITO"]
|
||||
|
||||
# Prevent user existence errors
|
||||
prevent_user_existence_errors = "ENABLED"
|
||||
|
||||
# Token validity
|
||||
access_token_validity = 1
|
||||
id_token_validity = 1
|
||||
refresh_token_validity = 30
|
||||
|
||||
token_validity_units {
|
||||
access_token = "hours"
|
||||
id_token = "hours"
|
||||
refresh_token = "days"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Cognito Admin User (Conditional)
|
||||
# =============================================================================
|
||||
# Creates admin user if email is provided
|
||||
|
||||
resource "aws_cognito_user" "admin" {
|
||||
count = var.admin_user_email != null ? 1 : 0
|
||||
|
||||
user_pool_id = aws_cognito_user_pool.main.id
|
||||
username = var.admin_user_email
|
||||
|
||||
attributes = {
|
||||
email = var.admin_user_email
|
||||
email_verified = true
|
||||
}
|
||||
|
||||
# Send invitation email with temporary password
|
||||
desired_delivery_mediums = ["EMAIL"]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# =============================================================================
|
||||
# User Pool Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "user_pool_id" {
|
||||
description = "Cognito User Pool ID"
|
||||
value = aws_cognito_user_pool.main.id
|
||||
}
|
||||
|
||||
output "user_pool_arn" {
|
||||
description = "Cognito User Pool ARN"
|
||||
value = aws_cognito_user_pool.main.arn
|
||||
}
|
||||
|
||||
output "user_pool_endpoint" {
|
||||
description = "Cognito User Pool endpoint"
|
||||
value = aws_cognito_user_pool.main.endpoint
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Web Client Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "web_client_id" {
|
||||
description = "Cognito Web Client ID (for frontend)"
|
||||
value = aws_cognito_user_pool_client.web.id
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Domain Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "domain_name" {
|
||||
description = "Cognito domain name"
|
||||
value = aws_cognito_user_pool_domain.main.domain
|
||||
}
|
||||
|
||||
output "cognito_domain_url" {
|
||||
description = "Full Cognito domain URL for OAuth token endpoint"
|
||||
value = "${aws_cognito_user_pool_domain.main.domain}.auth.${local.region}.amazoncognito.com"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# OIDC Configuration Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "oidc_issuer_url" {
|
||||
description = "OIDC issuer URL for JWT validation"
|
||||
value = "https://cognito-idp.${local.region}.amazonaws.com/${aws_cognito_user_pool.main.id}"
|
||||
}
|
||||
|
||||
output "oidc_discovery_url" {
|
||||
description = "OIDC discovery URL (well-known configuration)"
|
||||
value = "https://cognito-idp.${local.region}.amazonaws.com/${aws_cognito_user_pool.main.id}/.well-known/openid-configuration"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Admin User Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "admin_user_created" {
|
||||
description = "Whether admin user was created"
|
||||
value = var.admin_user_email != null
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
variable "stack_name_base" {
|
||||
description = "Base name for all resources."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "admin_user_email" {
|
||||
description = "Email address for the admin user. If provided, creates an admin user."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "amplify_url" {
|
||||
description = "Amplify app URL to add to callback URLs."
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.82.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user