The AWS FinOps Agent is an MCP (Model Context Protocol) gateway for Cloud Financial Management (CFM) that accesses AWS Cost Explorer, Amazon Athena, and related services. It deploys on Amazon Bedrock AgentCore. This document describes the security controls, shared responsibilities, and design decisions.
| Responsibility | Project Provides | Deployer Must Configure |
|---|---|---|
| Authentication | CUSTOM_JWT gateway integration | OIDC identity provider, client credentials |
| Network | VPC module with private subnets, VPC endpoints, security groups | Enable VPC (enable_vpc = true), review CIDR ranges |
| Encryption | KMS variable support for Lambda env vars and CloudWatch Logs | Provide KMS key ARN if required by policy |
| S3 Security | IAM policies scoped to CUR bucket | Enable SSE, Block Public Access, versioning, access logging on CUR bucket |
| IAM | Least-privilege roles per Lambda function | Review IAM policies for your environment |
| Monitoring | CloudWatch Logs (365-day retention), X-Ray tracing | Set up alarms, dashboards, and log analysis |
| Patching | Terraform modules with pinned provider versions | Keep Terraform and providers updated |
AgentCore Gateway supports three authentication modes:
- CUSTOM_JWT (recommended): Validates JWT tokens from an OIDC-compliant identity provider. Configure
jwt_discovery_urlandjwt_allowed_audiencesin your tfvars. - AWS_IAM: Uses SigV4 request signing. Suitable for programmatic access from AWS services.
- NONE: No authentication. Use only for local testing.
Each component has a dedicated IAM role following least-privilege principles:
| Component | Role | Permissions |
|---|---|---|
| AgentCore Runtime | {project}-runtime-role |
ReadOnlyAccess (configurable), X-Ray, CloudWatch Logs |
| Lambda Proxy | {project}-lambda-role |
InvokeAgentRuntime (scoped to runtime ARN), X-Ray, CloudWatch Logs |
| Cost Explorer MCP | {project}-cost-explorer-mcp-role |
ce:Get* (4 actions), CloudWatch Logs |
| Athena MCP | {project}-athena-mcp-role |
athena:* (12 actions), s3:* (scoped to CUR + Athena results buckets), glue:Get* (6 actions), CloudWatch Logs |
| CUR Analyst MCP | {project}-cur-analyst-mcp-role |
ce:Get* (8 actions), athena:* (4 actions), s3:* (scoped to CUR bucket), glue:Get* (6 actions), CloudWatch Logs |
| Management Account Role | {project}-management-role |
ce:Get*, athena:, s3: (CUR bucket only), glue:Get* |
Cross-account access uses STS AssumeRole with:
- External ID: Auto-generated random value stored in Terraform state, preventing confused deputy attacks
- Trust Policy: Restricted to the data collection account ID only
- Session Duration: 1-hour maximum
- Lambda Environment Variables: Encrypted with AWS managed keys by default. Configure
lambda_kms_key_arnfor customer-managed KMS encryption. - CloudWatch Logs: Optionally encrypted with KMS via
log_group_kms_key_arnmodule variable. - S3 (CUR Bucket): Deployer responsibility. Enable SSE-S3 or SSE-KMS on your CUR bucket.
- Terraform State: Contains sensitive values (External ID). Store in an encrypted S3 backend with restricted access.
- Customer-managed KMS keys are optional. If provided via
lambda_kms_key_arn, the deployer is responsible for key policy configuration, rotation, and access control. - AWS managed keys (default) handle rotation automatically.
- KMS key ARNs are passed through Terraform variables — review key policies before deployment to ensure they grant appropriate access.
- All AWS API calls use TLS 1.2+.
- When VPC mode is enabled, traffic routes through VPC endpoints (PrivateLink), without traversing the public internet.
When enable_vpc = true:
- Lambda functions run in private subnets with no internet gateway or NAT gateway
- VPC endpoints provide private connectivity to AWS services: S3 (gateway), STS, CloudWatch Logs, Athena, AWS Glue, Cost Explorer, Bedrock AgentCore (interface)
- Security groups restrict traffic to HTTPS (port 443) only
- VPC Flow Logs capture network traffic to CloudWatch Logs
- Default security group denies all traffic (per AWS VPC security guidance)
Lambda functions use AWS-managed networking. All API calls still use TLS but route through public AWS endpoints.
Several IAM policies use "*" as the resource. These are AWS service limitations, not design choices:
| Service | Actions | Why "*" is Required |
|---|---|---|
| AWS Cost Explorer | ce:GetCostAndUsage, ce:GetDimensionValues, ce:GetTags, ce:GetCostForecast, ce:GetSavingsPlansCoverage, ce:GetSavingsPlansUtilization, ce:GetReservationCoverage, ce:GetReservationUtilization | Cost Explorer APIs do not support resource-level permissions (AWS docs) |
| Amazon Athena | athena:StartQueryExecution, athena:GetQueryExecution, athena:GetQueryResults, etc. | Athena APIs do not support resource-level permissions for most read operations |
| AWS Glue | glue:GetDatabase, glue:GetDatabases, glue:GetTable, glue:GetTables, glue:GetPartition, glue:GetPartitions | AWS Glue catalog read APIs do not support resource-level permissions |
S3 permissions for the CUR Analyst Lambda are scoped to the specific CUR bucket ARN (arn:aws:s3:::{cur_bucket_name} and arn:aws:s3:::{cur_bucket_name}/*).
- The agent provides read-only financial data analysis. No AI-generated actions modify AWS resources.
- MCP tools execute predefined AWS API calls (Cost Explorer, Athena queries). There is no arbitrary code execution.
- The CUR Analyst uses Amazon Bedrock with predefined SQL query templates. User-provided parameters (date ranges) are used in safe LIKE clause patterns.
- Lambda functions validate required parameters before execution.
- Athena queries are executed via the Athena API (not string concatenation into SQL). The API handles query parsing and execution.
- The Athena MCP Lambda validates that SQL queries are read-only (SELECT, SHOW, DESCRIBE) and rejects DDL/DML operations (DROP, DELETE, INSERT, etc.) as defense in depth.
- Cost Explorer parameters (dimensions, dates, granularity) are validated against allowlists before API calls.
- CUR Analyst month parameters are validated against YYYY-MM format via regex.
- Environment variables for database/table names come from Terraform configuration, not user input. They are validated at Lambda cold start for safe identifier patterns.
- Lambda functions return structured JSON responses with typed fields (amounts, dates, resource IDs).
- Error responses include only the exception message, not stack traces or internal state.
- Cost data responses contain only AWS billing data fields — no customer content or PII is included in outputs.
- Cost data: Non-PII financial data (spend amounts, service names, resource IDs). No customer content or personal data flows through the agent.
- Data retention: Data processed by the agent is not used for model training. Amazon Bedrock AgentCore does not retain customer data for training purposes.
The CFM agent aggregates and presents factual AWS cost data (spend amounts, resource counts, usage metrics). It does not make subjective recommendations, score entities, or rank individuals. Cost allocation follows AWS billing data as-is, with no model-driven weighting or interpretation that could introduce bias.
| Component | Source | Status |
|---|---|---|
| aws-api-mcp-server | AWS Marketplace | AWS first-party; MIT No Attribution license |
| Amazon Bedrock AgentCore | AWS Service | AWS managed service |
| MCP Lambda functions | This repository | Custom code; see LICENSE |
The CUR S3 bucket is managed by the deployer. Recommended security configuration:
- Block Public Access: Enable all four Block Public Access settings
- Encryption: SSE-S3 (default) or SSE-KMS
- Versioning: Enable for data protection
- Access Logging: Enable server access logging to a separate logging bucket
- Lifecycle Rules: Configure based on your data retention requirements
- TLS Enforcement: Add a bucket policy requiring
aws:SecureTransport
| Control | Configuration | Default |
|---|---|---|
| CloudWatch Log Retention | log_retention_in_days |
365 days |
| X-Ray Tracing | xray_tracing_mode |
Active |
| Lambda Concurrency | lambda_reserved_concurrent_executions |
10 |
| VPC Flow Logs | Enabled when enable_vpc = true |
N/A |
| Check | Resource | Justification |
|---|---|---|
| CKV_AWS_116 (DLQ) | All Lambda functions | Synchronous invocation by AgentCore Gateway. DLQ only applies to async invocations. |
| CKV_AWS_272 (Code Signing) | All Lambda functions | Code packaged from local source via archive_file. Code signing requires a CI/CD pipeline with a signing profile. |
| CKV_AWS_111 (IAM write without constraints) | Management account role | Cost Explorer, Athena, and AWS Glue APIs do not support resource-level permissions. Actions are read-only (Get*/List*). |
CKV_AWS_356 (IAM "*" resource) |
Management account role | Same as above. AWS service limitation documented in IAM policy comments. |
| Rule | File | Justification |
|---|---|---|
| python.lang.best-practice.arbitrary-sleep | cur_analyst/lambda_function.py | Controlled polling loop for Athena query completion. 5-second interval with 60-iteration max (5-minute timeout). Required because Athena queries are asynchronous. |
This project is sample code for demonstration and educational purposes, licensed under MIT No Attribution (see LICENSE). It is not production-ready without review. Before deploying to production:
- IAM: Review all IAM policies for your security requirements
- Network: Enable VPC mode (
enable_vpc = true) for network isolation - Encryption: Configure KMS encryption if required by your organization's policies
- S3: Ensure your CUR S3 bucket follows S3 security best practices (see S3 Security Requirements)
- State: Store Terraform state in an encrypted, access-controlled S3 backend
- Monitoring: Implement CloudWatch alarms and log analysis for your deployment
- Authentication: Configure CUSTOM_JWT with your OIDC identity provider (never deploy with
NONEin production)
If you discover a potential security issue, please report it via GitHub Issues or contact the repository maintainers directly. Do not create public issues for active security vulnerabilities.