Tag Archives: Intermediate (200)

Centralized dashboard for monitoring SES identities

Post Syndicated from Manoj Gaddam original https://aws.amazon.com/blogs/messaging-and-targeting/centralized-dashboard-for-monitoring-ses-identities/

Organizations running Amazon Simple Email Service (Amazon SES) across multiple AWS accounts face a common challenge: visibility. When you have dozens or hundreds of accounts sending email, answering basic questions becomes surprisingly difficult:

  • How many SES identities exist across my organization?
  • Which identities are verified and pending?
  • Are there stale or unused identities that should be cleaned up?
  • Which accounts are sending from which domains?
  • Is DKIM properly configured for all sending domains?

Without centralized visibility, security teams can’t audit email-sending capabilities, compliance teams can’t enforce domain policies, and operations teams discover rogue identities only after deliverability issues arise.

This post presents two solutions for building a centralized SES identity dashboard across your AWS Organization. Each collects identity data from all member accounts and presents it through Amazon Athena and Amazon Quick Sight.

  • Option 1: A single AWS Lambda function that scans all accounts sequentially, best for organizations with fewer than 50 accounts.
  • Option 2: An AWS Step Functions state machine with a Map state for parallel execution, best for organizations with 50 or more accounts.

Both solutions provide centralized visibility into your SES posture with minimal member account footprint. You can deploy the option that best suits your needs by following the guidance in this post.

Solution architecture

The following diagram shows the end-to-end scan workflow, from the scheduled trigger through cross-account collection to the Amazon Athena and Amazon Quick Sight presentation layer.

SES identity scan workflow from the scheduled trigger through cross-account collection to Athena and Amazon Quick Sight

Figure 1: End-to-end SES identity scan workflow from the scheduled trigger through cross-account collection to the Athena and Amazon Quick Sight presentation layer

For each identity, the scan collects the attributes Amazon SES reports for that identity: identity name, type (domain or email), verification status, DKIM configuration (signing enabled, status, and current key length), the custom MAIL FROM domain and its status, and whether sending is enabled.

SPF and DMARC are published as DNS records by the domain owner rather than reported by Amazon SES, so they make a natural next layer to add on top of this scan.

Option 1: Single Lambda (fewer than 50 accounts)

A single AWS Lambda function handles the entire scan sequentially, at approximately 30 seconds per account. Because a sequential scan of 50 or more accounts exceeds what one invocation can complete, Option 2 parallelizes the work across accounts.

Option 1 architecture using a single Lambda function to scan accounts sequentially

Figure 2: Option 1 architecture using a single Lambda function to scan accounts sequentially

How Option 1 works

  • Amazon EventBridge triggers the scanner Lambda on a daily schedule (configurable).
  • The Lambda calls organizations:ListAccounts to discover all active member accounts.
  • For each account, it calls sts:AssumeRole to assume the SES-Identity-Reader role. The role’s trust policy requires three conditions: the scanner role’s aws:PrincipalArn, your aws:PrincipalOrgID, and a per-deployment external ID passed as a NoEcho parameter. Generate a unique value for this deployment, and don’t reuse a fixed one.
  • Using the assumed credentials, it calls ec2:DescribeRegions to discover enabled Regions, then ses:ListEmailIdentities and ses:GetEmailIdentity in each Region.
  • Results are written to Amazon S3 as JSON Lines, both a timestamped historical path and a latest/ path for Athena.
  • If any accounts or Regions fail, the Lambda records the failures in scans/summary/latest.json and publishes an error summary to Amazon Simple Notification Service (Amazon SNS). If accounts exist but zero identities are collected, the run fails without overwriting scans/latest (the previous good scan is preserved) so the SES-Scanner-Errors alarm fires.
  • Amazon CloudWatch alarm (a missed-run alarm) fires if no successful scan occurs within 25 hours. Additional alarms cover Lambda Errors and dead-letter-queue depth.

Deploying Option 1 in your AWS account

Prerequisites for Option 1:

  • AWS account access with permissions to deploy CloudFormation stacks and StackSets.
  • Your AWS Organization ID (format: o-xxxxxxxxxx).
  • Your management account ID (12 digits).
  • From the repository, you need the ses-reader-role.yaml and ses-scanner-stack.yaml files found in /option-1/.

Step 1: Deploy the cross-account IAM role

Template: option-1-ses-reader-role.yaml

  • Enable trusted access between CloudFormation StackSets and AWS Organizations. This is a one-time prerequisite for service-managed StackSets and is a no-op if already enabled.
  • In the CloudFormation console, this is offered automatically the first time you create a service-managed StackSet. Alternatively, run the following command from the management account: aws organizations enable-aws-service-access --service-principal member.org.stacksets.amazonaws.com
  • Open the AWS CloudFormation console in your management account.
  • Navigate to StackSets and choose Create StackSet.
  • Upload the ses-reader-role.yaml file.
  • Configure the parameters:
    • ManagementAccountId : Your 12-digit management account ID.
    • OrganizationId : Your organization ID (o-xxxxxxxxxx). Restricts assumption to principals in your organization.
    • ExternalId : A unique random secret you generate for this deployment (8–128 chars). Supply the same value to the scanner stack in Step 2. Don’t reuse the sample value.
    • ScannerRoleName : Leave default (SES-Identity-Scanner-Role) unless you renamed the scanner role.
  • Choose Service-managed permissions.
  • Enable Auto-deployment (so new accounts automatically receive the role).
  • Deploy to your organization root (r-xxxx) in a single Region (IAM roles are global).
  • Wait for the operation to show SUCCEEDED for all member accounts.

This deploys a strictly read-only IAM role (SES-Identity-Reader) to all member accounts. The role grants only the SES v2 read actions it uses (ses:ListEmailIdentities, ses:GetEmailIdentity) plus ec2:DescribeRegions, and no write or other permissions.

Step 2: Deploy the scanner stack

Template: option-1-ses-scanner-stack.yaml

  • Open the AWS CloudFormation console in your management account.
  • Choose Create stack, then With new resources (standard).
  • Upload the ses-scanner-stack.yaml file.
  • Configure the parameters:
    • ScheduleExpression : Scan frequency (default: rate(1 day)).
    • OrganizationId : Your organization ID (o-xxxxxxxxxx). Scopes cross-account sts:AssumeRole through an aws:ResourceOrgID condition.
    • ExternalId : The same value you generated in Step 1.
    • AlertEmail : Email address that receives scan-error summaries and CloudWatch alarms.
  • Acknowledge IAM resource creation and choose Submit.
  • Wait for CREATE_COMPLETE.
  • After the stack is created, confirm the SNS subscription in the email sent to AlertEmail. Until you confirm, error notifications and alarms are not delivered.

The stack creates:

  • Two S3 buckets (a scan-results bucket and an access-logs bucket, both versioned, public access blocked, with lifecycle rules including noncurrent-version expiration).
  • A Lambda function (Python 3.12, 512 MB, 15-minute timeout) with an explicit CloudWatch log group (90-day retention).
  • An Amazon EventBridge rule, an SQS dead-letter queue.
  • CloudWatch alarms (missed run, Lambda Errors, DLQ depth).
  • An SNS topic with an email subscription.
  • An enforced encrypted Athena workgroup.
  • An AWS Glue database with table definition for Athena.

Note on data retention: Timestamped historical scans are written under scans/archive/, and both lifecycle rules (expiration and transition to Infrequent Access) are scoped to that prefix. Three pointers are continuously overwritten: scans/latest/, scans/summary/, and athena-results/. These are deliberately excluded from expiration, so the most recent good scan is always preserved, including when a run fails closed.

Step 3: Test the scanner

  • Open the AWS Lambda console.
  • Select the SES-Identity-Scanner-v2 function.
  • Choose Test, create a test event with payload {}, and choose Test.
  • Review the execution result, which should show accounts scanned, total identities found, and any errors.
  • Verify the JSON Lines file exists in your S3 bucket under scans/latest/.

Step 4: Query with Amazon Athena

  • Open the Amazon Athena console.
  • Select the workgroup the stack creates (<stack-name>-ses-identity). It enforces an encrypted results location inside the hardened scan-results bucket, so you do not set a query result location manually.
  • Select the ses_identity_dashboard database.
  • Run the following queries to explore your organization’s SES posture.

Organization summary:

SELECT account_id,
       COUNT(*) as total_identities,
       SUM(CASE WHEN verified THEN 1 ELSE 0 END) as verified_count,
       SUM(CASE WHEN dkim_status = 'SUCCESS' THEN 1 ELSE 0 END) as dkim_configured
FROM ses_identities
GROUP BY account_id
ORDER BY total_identities DESC;

Identities requiring action:

SELECT account_id, region, identity_name, identity_type, dkim_status
FROM ses_identities
WHERE verified = false
   OR (identity_type = 'DOMAIN' AND dkim_status != 'SUCCESS')
ORDER BY account_id;

More queries: option-1-sample-athena-queries.sql

Step 5: Visualize with Amazon Quick Sight

  • Open the Amazon Quick Sight console.
  • Create a new dataset using Amazon Athena as the data source.
  • Select the ses_identities table from the ses_identity_dashboard database.
  • Import the data into SPICE for fast dashboard performance.
  • Create visualizations: bar chart (identities by account), pie chart (verified and unverified), heat map (identities by Region and account), and a detail table with filters.
  • Schedule SPICE refresh to run after the daily scan completes.
Amazon Quick Sight dashboard showing SES identities by account and verification status Amazon Quick Sight dashboard showing SES identities by Region with a detail table

Option 2: Step Functions with parallel account scanning (50+ accounts)

AWS Step Functions with a Map state provides parallel execution. With up to 40 concurrent per-account scans, an estimated 280 accounts complete in roughly 3–5 minutes, with built-in retry logic and per-account error isolation. The template uses an inline Map, which caps concurrency at 40. To scale beyond 40, switch the ItemProcessor mode to DISTRIBUTED. This additionally requires three actions on the state machine plus S3 ItemReader/ResultWriter permissions.

  • states:StartExecution
  • states:DescribeExecution
  • states:StopExecution
Option 2 architecture using a Step Functions state machine with a Map state for parallel account scanning

Figure 3: Option 2 architecture using a Step Functions state machine with a Map state for parallel account scanning

Prerequisites for Option 2:

  • AWS Organizations enabled with a management account (or delegated administrator).
  • Permissions to deploy CloudFormation StackSets across member accounts.
  • Amazon SES configured in one or more member accounts (identities to scan).
  • Console access to AWS CloudFormation, AWS Lambda, Amazon S3, and Amazon Athena.
  • Clone the repository:
git clone https://github.com/aws-samples/sample-SES-Identity-Dashboard.git

How Option 2 works

Refer to Option 2 image for the workflow:

  • Amazon EventBridge triggers the Step Functions state machine on schedule.
  • The ListAccounts Lambda discovers all active organization accounts and returns them as a JSON array.
  • The Map state iterates over the accounts array with MaxConcurrency: 40 (inline Map. Do not raise above 40 without switching to a Distributed Map).
  • Each Per-Account Scanner Lambda receives a single account ID, assumes the reader role, scans all enabled Regions, and writes per-account results to S3. Per-region and per-identity failures are reported in-band rather than silently dropped.
  • The Consolidate Lambda merges all per-account files into a single JSON Lines file at the scans/latest/ path. It also writes latest.json and fails the execution only when zero identities were collected and at least one account, Region, or identity failed. A legitimately empty organization (every account scanned cleanly with no identities) is treated as healthy, and the previous scans/latest/ is preserved when the run fails closed.
  • On any account failure, Step Functions automatically retries (two attempts, 60-second interval, backoff rate 2.0).
  • Failed accounts are captured in the execution output without stopping the scan for other accounts.

The Map state processes up to 40 accounts concurrently. Each per-account Lambda completes in approximately 30 seconds, so 280 accounts finish in 3–5 minutes. State machine logging and AWS X-Ray tracing are enabled. A SES-Scanner-SFN-Execution-Failed alarm covers failed executions. The Step Functions console provides a visual execution history showing exactly which accounts succeeded or failed.

Deploying Option 2 in your AWS account

Template: option-2-ses-scanner-stepfunctions.yaml

  • Confirm the SES-Identity-Reader role is already deployed to all member accounts (Step 1 from Option 1).
  • Open the AWS CloudFormation console in your management account.
  • Upload the ses-scanner-stepfunctions.yaml file.
  • Configure the parameters:
    • OrganizationId : Your organization ID (o-xxxxxxxxxx). Scopes sts:AssumeRole through aws:ResourceOrgID.
    • ExternalId : The same per-deployment secret used by the reader role.
    • AlertEmail : Address subscribed to the alert SNS topic (confirm the subscription email).
    • ScheduleExpression.
    • and optionally MaxConcurrency (default 40. Bounded 1–40 for the inline Map).
  • Acknowledge IAM resource creation and choose Submit.
  • Wait for CREATE_COMPLETE.
  • The Athena table schema, sample queries, and Amazon Quick Sight dashboard configuration are the same as Option 1. Only the AWS Glue database name differs. Option 2 creates ses_identity_dashboard_sfn, while Option 1 uses ses_identity_dashboard. Point your Athena queries and Amazon Quick Sight dataset at ses_identity_dashboard_sfn. Query results are written to the enforced encrypted workgroup <stack-name>-ses-identity-sfn. Select it in the Athena console. The distinct names let both options run in the same account.

Clean up

To remove all resources created by this solution:

  • Delete the scanner CloudFormation stack from your management account.
  • In StackSets, delete all stack instances, then delete the StackSet itself.
  • If you created test SES identities, delete them from the SES console in each account.
  • Empty and delete the S3 bucket if it was not removed by the stack deletion (buckets with objects require manual emptying).

Conclusion

This solution gives your organization a single, centralized view of SES identities across all accounts, deployed in minutes with minimal member account footprint.

Start with Option 1 for immediate value. When your organization grows beyond 50 accounts, deploy Option 2. The data format, Athena queries, and Amazon Quick Sight dashboards remain unchanged. Only the orchestration layer changes.

To get started, clone the repository and deploy Option 1. For more information, see Amazon SES v2 API reference and Amazon Quick Sight User Guide.


About the authors

Manoj Gaddam

Manoj Gaddam

Manoj is a Technical Account Manager at AWS, where he helps enterprise customers architect, operate, and optimize their cloud solutions. He partners with strategic software and technology customers to help them achieve their business and technical goals on AWS. His focus spans analytics, storage, security, and modern compute and container workloads, as well as helping customers explore and adopt AI and generative AI capabilities on AWS. Outside of work, he enjoys traveling and spending time with friends.

Ajinkya Bhoite

Ajinkya Bhoite

Ajinkya is a Senior Cloud Support Engineer at AWS and a Subject Matter Expert in Amazon Simple Email Service (SES) and Amazon S3, where he helps customers untangle their trickiest SES and S3 problems. Away from the cloud, he’s a reader, a runner, and a published novelist, author of the Kindle fiction Shiva Stone: Hampi’s Hidden Treasure.

Mrinal Sorte

Mrinal Sorte

Mrinal is a Technical Account Manager at AWS, where she helps enterprise customers build secure, resilient, and compliant workloads on AWS. Passionate about AI, automation, and security, she actively contributes to AWS technical communities and enjoys sharing knowledge through workshops and enablement sessions. She works backwards from customer objectives to design and implement scalable solutions that drive innovation, reduce risk, and accelerate growth.

Sassan Hajrasooliha

Sassan Hajrasooliha

Sassan is a Senior Specialist AWS Solutions Architect with AWS. He has been working as an IT professional since 2003 and covers diverse technologies from different vendors in the industry. Throughout his career he has helped customers ranging from startups to multinational enterprises and public agencies get the best out of their IT systems for their businesses and missions. At AWS he is focused on Communication Developer Services like SES and EUM, as well as infrastructure migration and modernization.

Automate medical coding with Step Functions and Amazon Bedrock AgentCore

Post Syndicated from Sachin Doshi original https://aws.amazon.com/blogs/architecture/automate-medical-coding-with-step-functions-and-amazon-bedrock-agentcore/

With AI agents, you can automate medical coding while keeping the workflow deterministic, auditable, and subject to human approval. The key is division of labor: the workflow engine owns sequencing and approval gates, while agents reason within individual, bounded steps. Handing the entire workflow to an agent would make the runtime path a property of the model’s reasoning, which is difficult to review and replay.

Medical coding makes this concrete. Teams must translate free-text clinical terms like “migrane” (a typo) or “flutters in my chest sometimes” into standardized codes before they can analyze or report the data. The target vocabularies include controlled medical dictionaries such as MedDRA for adverse events and WHODrug for medications. This work is safety-critical because a coding error can send an adverse event to the wrong safety signal. Yet it’s mostly repetitive work, with skilled coders reviewing terms one by one. The opportunity is to code the confident cases automatically and route only the ambiguous ones to a human.

In this post, we show you how to combine deterministic orchestration with bounded agentic reasoning using AWS Step Functions and the Amazon Bedrock AgentCore harness integration, a managed capability of Amazon Bedrock AgentCore. We walk through a medical coding workflow that escalates cheapest-first: a safety block-list, then exact matching, then an AI agent only for the terms that need judgment. We also explain the design decisions that keep the model bounded. The complete sample, including infrastructure code and a seeded test dataset, is available in the aws-samples GitHub repository.

Note that MedDRA is a registered trademark of the International Federation of Pharmaceutical Manufacturers and Associations (IFPMA), managed by the MedDRA Maintenance and Support Services Organization (MSSO). WHODrug is maintained by the Uppsala Monitoring Centre (UMC). Both are proprietary, licensed dictionaries. Deploying against them requires a valid subscription. The sample dataset contains generated study identifiers and verbatims with a de minimis illustrative set of dictionary entries, and no patient data. The AWS services used in this sample, including Amazon Aurora and Amazon Bedrock, are HIPAA-eligible when used under a signed AWS Business Associate Addendum (BAA). Before processing protected health information, see the HIPAA Eligible Services Reference and the AWS HIPAA Compliance overview.

Solution overview

The design follows one principle: spend the cheapest, most deterministic mechanism that can code a term with confidence and escalate only when it can’t. A large language model (LLM) agent is powerful but expensive and non-deterministic, so it isn’t the default. Even then, the workflow, not the model, owns every routing decision. The agent might choose which medical code and how confident it is. It doesn’t decide what happens next. A compliance reviewer can read the state machine definition and understand the execution path independent of any agent’s runtime reasoning.

The solution uses AWS Step Functions to orchestrate the coding decision, AWS Lambda for the deterministic steps, and the Amazon Bedrock AgentCore harness for the agentic step. An Amazon Bedrock AgentCore Gateway, a capability of Amazon Bedrock AgentCore, exposes the agent’s tools. Amazon Aurora PostgreSQL-Compatible Edition (Serverless v2) with the pgvector extension serves as a single store for the reference data and its embeddings, so there is no separate vector store to synchronize.

Figure 1 shows the architecture.

A vertical workflow with seven numbered steps. A study term starts a Step Functions execution (1). A CheckDirect Lambda function runs a direct lookup against Aurora PostgreSQL with pgvector (2). On a miss, the workflow invokes the AgentCore harness agent (3), which calls two MCP tools through the AgentCore Gateway (4): get_study_info and search_dictionary. The tools query Aurora. The workflow routes on the agent’s score (5), and a WriteBack Lambda function writes the outcome to Aurora (6). Amazon CloudWatch records agent traces (7).

Figure 1: Medical coding workflow solution architecture

Each Step Functions execution codes one verbatim: a deterministic direct lookup, an agentic semantic search that runs only on a miss, score-threshold routing on the agent’s confidence, and a write-back onto the same study record. The state machine is deliberately a standard workflow. Executions are exactly-once, and the queryable history is a per-term audit trail. For a regulated coding process, answering “why was this term coded this way, by what, and when” is a requirement. Error paths in this sample end with the term left open, so every record gets a terminal status. In production, send errors that survive the retries to a Fail state instead. The execution then shows as failed in Amazon CloudWatch, and you can redrive it after fixing the cause.

The write-back step stores every symptom entry’s full medical family tree, not just an isolated code. For example, a symptom like ‘muscle spasms’ links all the way up to its overarching category, ‘musculoskeletal and connective tissue disorders’. Safety teams can then roll up signals at any level, not just the lowest one. Additionally, the agent logs a one-sentence explanation for its decision. This makes sure that if similar symptoms (like cramps) end up categorized differently across studies, teams can trace the exact study context that caused the variance. They don’t need to guess based on a generic confidence score.

Figure 2 shows the coding decision flow in Unified Modeling Language (UML) activity notation.

A UML activity diagram of the state machine. After CaptureInput and CheckDirect, the RouteDirect decision routes a blocked term to MarkOpen, a unique match to WriteBack, and everything else to CodingAgent. The ScoreThreshold decision routes on confidence: 0.90 and above to WriteBack, 0.70 to 0.90 to MarkForReview, and below 0.70 to MarkOpen. The terminal statuses are autocoded, approval_required, and open.

Figure 2: Medical coding decision flow with the three terminal outcomes in UML activity notation

Note: This sample is for demonstration purposes only and is not intended for clinical use. Before using this pattern in production, validate it against a known-correct dataset and measure coding precision and the incorrect autocode rate.

Walkthrough

The walkthrough follows one verbatim through the state machine, focusing on the architectural decision each stage encodes.

Prerequisites

Before you begin, make sure that you have the following:

  • An AWS account with permissions to create Step Functions state machines, Lambda functions, an Aurora PostgreSQL cluster, Amazon Bedrock AgentCore resources, and AWS Identity and Access Management (IAM) roles.
  • Access to Amazon Bedrock AgentCore in a supported AWS Region. The harness uses Claude Sonnet 5 (us.anthropic.claude-sonnet-5), and the dictionary search uses Amazon Titan Text Embeddings V2 (amazon.titan-embed-text-v2:0). Both models are enabled by default, but Anthropic models require a one-time usage form before first use, which you can submit from the Amazon Bedrock playground. For model availability by Region, see Supported models by AWS Region in Amazon Bedrock.
  • Node.js 24+, the AWS Cloud Development Kit (AWS CDK) CLI, and a bootstrapped target account.

Estimated cost

The largest cost is the Aurora Serverless v2 cluster. It keeps a minimum capacity of 0.5 Aurora Capacity Units (ACUs), so it’s billed for every hour it runs, even when idle. For current rates, see Amazon Aurora pricing. To stop the charges, delete the stack as described in Clean up.

Step 1. Resolve the deterministic cases first

The first task state invokes a Lambda function that makes two checks, in a deliberate order. It first checks a terms-not-to-autocode block-list. These are real dictionary terms, like “HAEMORRHAGE,” that are too broad to auto-assign because the code changes depending on where the bleeding occurred. A hit ends the workflow with the term left open for a human. Because this runs before any model call, it’s a hard control: a safety rule placed ahead of the model cannot be prompted away, and costs nothing to enforce.

The same function then runs a case-insensitive exact match against the dictionary and a curated synonym list (“Hypothyroid” maps to “Hypothyroidism”). A match is accepted only if it resolves to exactly one distinct code. On the sample workload, five of twelve cases finish here: one safety block and four auto codes at score 1.00, each for the cost of an indexed query. More than a third of the workload never reaches the agent at all.

Step 2. Declare the coding agent in the workflow definition

We use an agent for this step because deciding when to pull in study context is judgment, not a fixed rule. In our test run, the agent called the study-info tool only for ambiguous terms and skipped it for a clear typo and for gibberish input. That kind of judgment is hard to encode as a threshold, and the agent can take on new tools later without a workflow redesign. This is also why the agent sits last in line, after two cheaper, deterministic checks.

Only on a miss does Step Functions invoke the agent, using the AgentCore harness optimized integration. The harness resource declares only the model. The system prompt, tools, and allowed tool names are supplied per invocation from the task state. The agent’s full contract is therefore readable in the workflow definition, and there is no container to build or operate. The following abbreviated excerpt shows the shape. See the sample for the full definition. The InvokeHarness Task state has a hard 15-minute execution cap regardless of TimeoutSeconds, so a term needing many tool round-trips could hit it. The sample’s two-tool, single-turn design stays well under that limit.

CodingAgent:
  Type: Task
  Resource: arn:aws:states:::bedrockagentcore:invokeHarness
  Arguments:
    HarnessArn: "${CodingHarnessArn}"
    RuntimeSessionId: "{% $uuid() %}"
    Messages:
      - Role: user
        Content:
          - Text: >-
              {% 'Code this verbatim term: "' & $verbatim & '". Dictionary='
              & $dictionary & ' version=' & $dictionaryVersion
              & ' study=' & $study & '.' %}
    Tools:
      - Type: agentcore_gateway
        Name: gateway
        Config:
          AgentCoreGateway:
            GatewayArn: "${GatewayArn}"
    AllowedTools:
      - "@gateway/search-dictionary___search_dictionary"
      - "@gateway/study-info___get_study_info"
    SystemPrompt:
      - Text: |-
          You are a clinical medical-coding expert...

The agent has exactly two tools, exposed as Model Context Protocol (MCP) tools through an AgentCore Gateway. search_dictionary embeds the verbatim with Amazon Titan Text Embeddings V2 and runs a pgvector similarity search. It searches the verbatim exactly as given, because the misspelling is the signal. get_study_info returns the study’s free-text protocol description. It exists because similarity search can return several clinically plausible candidates it can’t separate on text alone. A human coder breaks that tie using knowledge of the study. This tool gives the agent the same context and nothing else: it returns prose only, not codes.

The clearest illustration is the same verbatim coded in two different studies. “cramps” returns an identical candidate list in both. Only the study description differs. In an oncology study that monitors muscle spasms as adverse events of special interest, the agent coded it to “muscle spasms.” In a reflux study where abdominal cramping is expected and musculoskeletal events aren’t an endpoint, it coded the same verbatim to “abdominal pain.” In this run, the oncology result scored 0.92 and was autocoded, and the gastrointestinal (GI) result scored 0.85 and went to a coder for review. In both cases the agent’s job was to bring the right candidate to the top with a reason a coder can check. That’s how a coder who knows the protocol would decide.

Step 3. Route on coding confidence, not retrieval similarity

The most important design decision is what the threshold routes on. Routing on retrieval similarity, the distance between the two embeddings, is measurably wrong. Table 1 illustrates this with an example.

Verbatim Selected term Retrieval similarity Agent confidence
migrane Migraine 0.37 0.97
flutters in my chest sometimes Cardiac flutter 0.65 0.75

Table 1. Retrieval similarity and agent confidence for sample terms (one run, Claude Sonnet 5)

Table 1 shows the problem. The typo “migrane” looks very different from “Migraine” as text, so its similarity score is low, but the right code is obvious. “Flutters in my chest sometimes” shares more words with “Cardiac flutter,” so its similarity score is higher, but the right code is less certain. Similarity ranks these two cases the wrong way round, and no threshold can fix that. So instead, the agent reports how confident it is in its own choice, using three bands set in the system prompt. It reports 0.95 to 1.00 when the match is clear, 0.70 to 0.94 when the verbatim is vague or the agent needed study context to choose between candidates, and below 0.70 when nothing fits. For example, “cramps” in the GI study could mean muscle cramps or stomach cramps. The agent checked the study description to choose, and reported 0.85, in the middle band. The bands describe the model’s confidence. The 0.90 autocode gate is a separate, fixed rule in the workflow, applied by the Choice state that comes next.

ScoreThreshold:
  Type: Choice
  Choices:
    - Condition: "{% $agentScore >= 0.9 %}"
      Next: WriteBack
    - Condition: "{% $agentScore >= 0.7 %}"
      Next: MarkForReview
  Default: MarkOpen

Note that the routing number is authored by the model. The block-list runs before any model call, and the thresholds are auditable configuration in the state machine definition. The write-back checks the code exists, and anything below the autocode gate reaches a human. Note also that the agent is instructed never to abstain. For gibberish it still returns the top candidate at low confidence, and the gate discards it. The agent scores the candidate, and the workflow decides what happens to it.

Step 4. Write back with a fabrication guard

The terminal task persists the outcome onto the same study record: status, code, hierarchy, score, and a status_changed_by marker that distinguishes machine coding from human action. Before writing, it checks that the chosen code exists in the target dictionary and version. The prompt tells the agent not to invent codes, and this check enforces it. It matters because the agent reads free-text study metadata, which is the kind of input that invites copying a plausible number out of a protocol description. A code that fails the check leaves the term safely open, with the reason recorded. This check has one limit. It can’t tell whether the code was one of the search results, because the workflow only gets the agent’s final response, not what the tools returned. To verify that, look at the agent’s tool calls in AgentCore Observability, a capability of Amazon Bedrock AgentCore.

Deploy and test the solution

The sample deploys with three commands and seeds twelve verbatims that exercise every branch:

npm install && npm run build
npx cdk deploy
npm run seed

Each verbatim runs as one Step Functions execution (the repository README shows the exact CLI calls). The following table shows a live run:

Verbatim Route Score Outcome
HAEMORRHAGE block-list n/a open (safety block)
Dislocated shoulder exact match 1.00 autocoded
Hypothyroid synonym match 1.00 autocoded
ibuprofen exact match (WHODrug) 1.00 autocoded
FULVESTRANT exact match, derivation-only 1.00 autocoded
migrane agent 0.97 autocoded (Migraine)
nexiuum agent 0.95 autocoded (NEXIUM)
cramps (oncology study) agent + study context 0.92 autocoded (Muscle spasms)
cramps (GI study) agent + study context 0.85 review (Abdominal pain)
flutters in my chest sometimes agent 0.75 review (Cardiac flutter)
heart races when I stand up agent 0.80 review (Tachycardia)
asdfghjkl agent < 0.70 open

Table 2. Score and outcome for sample terms (same run as Table 1)

Derivation-only (FULVESTRANT row): a WHODrug ingredient-level exact match, where the write-back updates only the generic-name derivation and ATC hierarchy fields. No new dictionary term is assigned.

The agent skipped the study-info tool for the unambiguous typo and the gibberish and called it only for the ambiguous cases. Every execution’s graph is inspectable in the Step Functions console, and AgentCore Observability records the agent’s tool calls and token usage in Amazon CloudWatch.

Note: Across repeated runs of identical inputs in this sample, the selected code was stable every time, but the confidence score moved within a band. Cases that land near the 0.90 autocode gate can therefore autocode on one run and route to review on another. In our testing, the oncology cramps case scored between 0.75 and 0.92 over six runs. It was autocoded twice and sent to review four times, and Muscle spasms was selected every time. Assert your tests on the selected code, not the exact score, and set your own autocode threshold with margin below 0.90 rather than directly on it.

Scaling beyond one verbatim: This walkthrough codes one verbatim per execution so each decision stays straightforward to inspect. To code a full study, wrap the workflow in a Step Functions Distributed Map state that reads verbatims from an Amazon Simple Storage Service (Amazon S3) manifest, and set MaxConcurrency based on your account’s Amazon Bedrock quota. To size MaxConcurrency, divide your model’s tokens-per-minute quota by the tokens one agent run burns (model calls * (input tokens + 10 * output tokens) for Sonnet 5). Then multiply by the runtime duration in minutes, divide by the share of verbatims that reach the agent, and apply a safety margin. In our testing, 12 concurrent executions against a cluster that had scaled to zero failed 8 of 12 while still reporting SUCCEEDED, which is why the sample keeps a 0.5 ACU minimum and retries the resume errors. Most verbatims resolve in the two deterministic steps, so only a small share ever reaches the agent. For the quotas that apply to your model, see Quotas for Amazon Bedrock.

Clean up

To avoid future charges, delete the deployed resources with npx cdk destroy. All sample data is reseedable, so nothing needs to be retained.

Recognizing the pattern

This pattern fits when the execution path must be reviewable independently of model reasoning, most of the workload is deterministic, only a judgment-heavy minority needs a model, and “escalate to a human” is a first-class outcome rather than an error. It is not limited to clinical trials: the same pattern applies to medical summaries, case narratives, registry intake, study data, and other adjudication workflows such as laboratory test coding, claims triage, and document classification.

You can extend the AgentCore harness with AgentCore Memory to carry context across invocations, Skills to provide task-specific instructions on demand, and AgentCore Gateway to connect MCP servers, knowledge bases, and other tools. Models, tools, and system prompts can also be selected per invocation from the Step Functions task state. Treat a model change like any other release. Rerun your evaluation set, compare the selected codes and confidence scores with the current model’s, and recalibrate the thresholds before you switch. Through all of this, the workflow remains responsible for sequencing and the audit trail.

This isn’t a pattern for open-ended conversations. If the agent must decide what happens next, a workflow engine that owns sequencing is the wrong boundary. Retrieval quality also constrains the agent: if a term isn’t surfaced, it can’t be chosen. In the sample, “flutters in my chest sometimes” would normally be coded to Palpitations, but vector search ranks Palpitations 19th, so the agent does not see it and selects Cardiac flutter instead. The confidence gate still sent it to a coder, but no prompt or study context can recover a term that retrieval never returns. For production dictionaries, use hybrid retrieval, such as lexical search or curated synonym expansion alongside vectors, and calibrate thresholds against your own verbatim distribution.

Conclusion

In this post, we showed how deterministic orchestration and agentic reasoning become complementary when the boundary between them is explicit. AWS Step Functions owns sequencing, thresholds, and the audit trail. The agent owns bounded judgment inside one step and reports a confidence the workflow routes on. The result is a pipeline that codes the common cases for the cost of a query, focuses skilled coders on the ambiguous minority, and routes terms below the confidence gate to a human. The execution history shows exactly which mechanism made each decision. The declarative harness integration keeps the agent inside that boundary as configuration a reviewer can read, not code inside a container.

Next steps

Deploy the sample solution with the AWS Cloud Development Kit (AWS CDK), run the twelve golden cases, then swap in your own reference data and calibrate the thresholds from measured precision. For background, see the AWS Step Functions service integrations, the Amazon Bedrock AgentCore documentation, and pgvector on Amazon Aurora PostgreSQL.

To learn more, see how Step Functions invokes an Amazon Bedrock AgentCore harness from a Task state, the AgentCore harness developer guide, and transforming data with JSONata in Step Functions. To run at study scale, see Distributed Map for large-scale parallel workloads and quotas for Amazon Bedrock. For service details, see AWS Step Functions and Amazon Bedrock AgentCore.

 


About the authors

How Dynata scaled AI across the development lifecycle with AI-DLC and Kiro IDE

Post Syndicated from Madhu Chebrolu original https://aws.amazon.com/blogs/architecture/how-dynata-scaled-ai-across-the-development-lifecycle-with-ai-dlc-and-kiro-ide/

When Dynata set out to compress multi-sprint feature delivery into a single sprint, the answer wasn’t simply faster coding. It was reimagining the entire development lifecycle with AI. The team progressed in deliberate stages. They began with AI-assisted coding, then connected AI with their knowledge ecosystem across Confluence, Jira, Lucidchart, GitHub, and Figma. Finally, they adopted the AI-Driven Development Lifecycle (AI-DLC) methodology from AWS using Kiro IDE, an AI-native development environment. In doing so, Dynata transformed how its cross-functional teams collaborate.

Delivering software across multiple teams is a collaborative process that produces great outcomes when it works well. Product, project management, engineering, design, quality assurance (QA), and deployment each brings essential perspective, and that coordination requires significant time. Multiple rounds of discussion and review before development begins is common. For complex features, it’s expected and healthy.

In this post, we show how Dynata reduced the development phase of features that typically spanned 3–4 sprints to approximately one sprint’s worth of time. We also share how you can bring AI into the full delivery process, not only individual developer output, giving every team earlier visibility and tighter alignment from the start.

About Dynata

Dynata is the world’s largest first-party data company, delivering precise, reliable insights that help businesses to make informed decisions at scale. Their platform empowers organizations to activate the right audiences, and measure impact with confidence through industry-leading data accuracy, commitment to continuous improvement, and operational excellence.

Across Dynata’s portfolio, multiple distributed engineering teams work in parallel. They build and evolve platform capabilities, integrate with partner ecosystems, and ship features on continuous delivery cycles. Coordinating product intent, design, engineering, and test engineering across teams at that scale while maintaining delivery velocity is the kind of challenge that rewards better process as much as better technology.

The challenge

Dynata’s engineering teams had established a solid foundation that included experienced developers, modern cloud infrastructure on AWS, clear product ownership, and a culture of continuous improvement. As the organization scaled across multiple product lines and distributed teams, the natural next ambition was to make the delivery process faster and more connected across every team involved, including product, engineering, test engineering, and project management.

The areas with the most headroom are familiar across enterprise engineering organizations. Requirements context was spread across Jira, Confluence, Lucid, and design documents, and bringing it all together into a shared, actionable picture before development started took meaningful time. Cross-functional alignment happened largely in sequence, which meant that input from test engineering, design, or deployment planning sometimes came later in the cycle than was ideal. And when scope shifted mid-sprint, absorbing the change required effort proportional to how far into implementation the team had progressed.

Dynata’s goal was clear. The team wanted to bring AI into the delivery process in a way that accelerated the whole cycle, not only individual developer output, and gave every team earlier visibility and tighter alignment from the start.

What followed was a structured, step-by-step approach starting at the code level, expanding to the knowledge ecosystem, and ultimately reaching across the full development lifecycle.

The journey: From code generation to full lifecycle AI

Dynata’s path to AI-driven development didn’t happen in a single leap. It unfolded in three deliberate steps, each one building on the confidence and lessons of the one before. The team started by bringing AI into everyday coding, then connected it to the tools and context that teams already relied on and finally adopted a methodology that brought AI into every phase of delivery. Here is how each step played out.

Step 1: AI-assisted coding

Dynata’s developers began their AI journey with Amazon Q Developer, the AI-powered coding assistant from AWS, using it for code generation, test writing, and implementation acceleration across day-to-day development work.

The productivity gains were immediate and visible. Developers moved faster on implementation, generated higher volumes of well-structured code, and spent less time on repetitive scaffolding. The experience built genuine AI fluency across the engineering team, establishing AI-assisted development as a standard part of the workflow rather than an occasional experiment.

As the team’s ambitions grew beyond code-level assistance, Dynata adopted Kiro IDE, an AI-native development environment from AWS built around spec-driven development, automated steering files, and native support for the AI-DLC workflow. Kiro gave the team the ability to connect AI to the full delivery process, from requirements through to implementation. The move was a natural progression, building on the AI fluency the team had already developed with Amazon Q Developer.

“The code generation results were great from the start. Developers were more productive, and the quality held up.”

Step 2: Connecting AI to the knowledge ecosystem

With a productive coding baseline in place, the team explored what AI could do when given access to broader project context. They integrated their AI tooling with Jira, Confluence, Lucid, and other knowledge sources, giving the AI visibility into tickets, specifications, architecture diagrams, and design documentation alongside the code base.

The value expanded significantly. Tech leads and architects began using AI not only for implementation, but for architectural decision support. They used it to review design choices against existing system context, identify potential conflicts with other components, and validate alignment with established patterns at a per-developer level.

“Once we connected AI with Jira, Confluence, and Lucid, we started using it not just for code but for architectural design at each developer level. The results were also great.”

Step 3: Discovering AI-DLC

With AI delivering value at the coding and architectural design stages, the team set its sights on a broader goal. They wanted AI to help with the entire delivery process, from requirements through to production, not just implementation alone.

The team discovered AI-DLC, the AI-Driven Development Lifecycle methodology from AWS, through AWS blog posts and published customer success stories. The methodology positions AI as a structured part of every phase of software delivery, spanning requirements capture and clarification, design, task planning, and implementation, with human review and approval at each stage. AI-DLC is powered by Amazon Bedrock, which provides the foundation model layer behind the AI analysis, generation, and clarification in Kiro.

“We were looking for solutions to include AI across the lifecycle. Then we saw the AWS AI-DLC posts and success stories and wanted to try it internally.”

Adoption: A staged approach that built confidence at every step

Dynata took a deliberate, staged adoption path. The team built expertise and refined the process before involving the broader organization. This turned out to be one of the most valuable decisions they made.

Stage 1: Internal pilot with interested developers

A small group of developers who were motivated to explore tried AI-DLC independently using Kiro IDE. Kiro implements the AI-DLC workflow natively through Specs (structured requirements-through-tasks pipelines), steering files (team coding standards injected into every AI session), and hooks (automated quality checks at defined workflow points).

What the pilot showed was qualitatively different from standard AI code generation. Rather than only producing code, AI-DLC was helping the process by guiding structured iterations, surfacing gaps and ambiguities proactively, and building confidence in the design before a line of implementation code was written.

“Instead of just generating code, it was helping the process – reviewing, identifying gaps, iterating- giving confidence that it would work at a large scale.”

The pilot also surfaced what needed to be in place before scaling. Steering files needed tuning to produce code at the team’s production standards. The person leading a team session needed solid familiarity with the AI-DLC workflow. And participants benefited from a shared understanding of what the process involved before sitting down together.

It’s worth noting that AI-DLC is IDE-agnostic. Although Kiro provides native support for the full workflow, teams at Dynata also ran AI-DLC sessions using Cursor with Claude and found the methodology worked effectively across environments. What matters is the structured process, and the tooling supports it.

Stage 2: Preparation before team sessions

Before running a full cross-functional workshop, three things needed to be in place:

Session leader readiness. The person guiding the session, typically a tech lead or senior developer, needed working familiarity with the AI-DLC methodology and the IDE being used. Not expert-level mastery, but enough to keep the team moving through each phase, handle questions, and recognize when to spend more time on a step.

Team orientation. All participants including product, project management, tech leads, and developers received a high-level briefing on what AI-DLC is, how the session would be structured, what to expect from each phase, and what their role was. This upfront alignment made sessions significantly more productive.

Input preparation. Steering files describing the team’s coding standards, naming conventions, and architectural patterns were prepared in advance. Jira epics, Confluence specifications, and design documents were gathered and accessible before the session began. The quality of AI output is directly connected to the completeness of the context it is given.

Stage 3: The three-day AI-DLC feature workshop

Comparison of traditional sequential delivery and AI-DLC delivery, with all teams aligned from inception through code generation

Figure 1: Traditional feature delivery compared to delivery with AI-DLC at Dynata

With preparation complete and pilot learnings incorporated, the team selected a feature and ran a structured three-day workshop. This is where the methodology demonstrated its full value.

The workshop brought together product, the project owner, tech leads, and developers working through the AI-DLC workflow in Kiro IDE together.

Day 1: Requirements and alignment

The AI-DLC workflow guided the team step by step through the Inception phase:

  • The AI analyzed all available context, including Jira epics, Confluence pages, design documents, and the existing code base, and generated a consolidated, structured requirements view.
  • The AI surfaced clarifying questions, such as gaps between sources, edge cases without defined behavior, and technology decisions that hadn’t been made explicit. The team worked through each question together, with answers captured directly in the IDE.
  • All stakeholders reviewed and validated the output together. This was often the first time the full team had seen a unified picture of what was being built, with role-specific documents giving each participant the level of detail relevant to their work.

By the end of the day, requirements were agreed upon, documented, and committed to the repository.

The difference came down to timing. Instead of blockers and ambiguities surfacing during implementation, when resolving them is most expensive, they surfaced during planning, when the cost of a correction is a conversation. A process that typically took days or weeks of iterative meetings across teams was completed in a single structured day.

Day 2: Design and task planning

With agreed requirements in hand, the team moved into design:

  • The AI generated component boundaries, service interfaces, data models, API contracts, and dependency maps based on the approved requirements.
  • Tech leads and senior developers reviewed the design artifacts, applied their judgment, and validated that the design reflected real system constraints.
  • When design questions connected back to requirements, the team updated the requirements documents and the AI kept the design documents consistent. Changes propagated through the artifact chain automatically.
  • Product and project management remained in the room throughout. For many, this was the first direct view into how engineering translates requirements into architecture. When design questions required a business decision, the right people were immediately available.
  • After design was approved, the AI generated a sequenced task list, each task mapped to design artifacts and requirements, with implementation detail for each.
  • The team reviewed and iterated on the task list, then distributed ownership so that each developer took responsibility for specific tasks.

The team also made one practical observation. Thoughtful task distribution upfront, accounting for component dependencies and parallel workstreams, reduces merge conflicts during development. This was something the team refined over the first one or two feature cycles and improved steadily.

Outcome. The team approved design and tasks by the end of the day, with every team member clear on what was being built and who owned what.

Day 3: Code generation and working software

With tasks distributed and owners assigned, developers worked through code generation:

  • The AI generated code grounded in the approved design documents, requirements, and user stories with steering files ensuring output conformed to the team’s coding standards from the start.
  • Before generating code for each unit, the AI produced a detailed code generation plan for the developer to review and approve, keeping developers in control of what was being built and why.
  • All documents and artifacts from Days 1 and 2 were committed to the repository, giving every developer a shared, versioned baseline to work from.

Progress was demoed to the team at regular intervals for directional feedback, keeping everyone aligned as the feature took shape.

“After a few features built using AI-DLC, the quality of the end-to-end results and the code quality showed great improvements.”

Steering files improved with each feature. As the team accumulated more experience describing their standards and conventions, code generation output became more consistent and required less adjustment, a compounding return on the investment in getting the inputs right.

Outcome. By the end of Day 3, the team had a working, demonstrable state of the feature, with the development phase completed and the full scope visible and validated. They also had a clear picture of what remained, including testing, edge case hardening, and release preparation. This isn’t the finish line. It’s a strong, confident starting point for the steps that follow. What it reduces is the uncertainty that normally exists at this stage, because requirements gaps, design misalignments, and implementation surprises are already behind the team, not ahead of them.

Beyond full workshops: Using AI-DLC selectively

After running full three-day workshops for several features, the team discovered something important about the methodology. AI-DLC is flexible, and not every situation calls for the full workflow. Teams began using individual phases of AI-DLC independently, based on what they were trying to achieve:

  • Requirements discussions between product and tech teams. Using only the Inception phase to structure a requirements conversation, surface ambiguities, and produce a shared document, without necessarily proceeding to design or code generation in the same session.
  • Design-only sessions. Working through the design phase for a well-understood feature to align on architecture before developers began implementation.
  • Code generation from existing documentation. Teams with well-documented requirements using only the construction phase to accelerate implementation.

The methodology works as a complete end-to-end workflow, and it works as a targeted tool for specific parts of the process. Teams adopted it wherever it added the most value for their situation, which is the right way to use it.

Observations and what accelerated progress

Throughout the journey, from AI-assisted coding through to full AI-DLC workshops, the Dynata team identified a set of practical observations that shaped how they adopted the methodology and what made the biggest difference. These aren’t theoretical recommendations. They come directly from what the team experienced across multiple features, teams, and sessions.

Observation What the team found What helped
Steering file quality Early code generation output needed adjustment to fully match team standards Iterative refinement of steering files across features. Quality improved steadily and compounded
Session leader readiness Sessions moved best when the leader had hands-on experience with the workflow Pilot developers led early team sessions. Pre-session preparation for leaders paid dividends
Input context completeness Richer input context produced better AI output Pre-session effort to gather and organize source materials consistently improved session quality
Task distribution planning Parallel development on related components required thoughtful dependency planning Spending time on task distribution during the workshop, not after, reduced rework in subsequent steps
Team orientation Participants who understood the process upfront contributed more effectively A brief pre-session briefing for all participants improved engagement and session pace
IDE flexibility Teams used both Kiro and Cursor with Claude successfully AI-DLC is IDE-agnostic. What matters is the structured process, not the specific tooling

Results

Across features where Dynata applied the full AI-DLC workflow, the results were consistent:

  • Approximately 70 percent reduction in development phase time: By Dynata’s own estimate, based on their standard sprint planning, features that would typically span about four sprints were completed in roughly 1 sprint of developer time, with working software demonstrable at the end of the structured workshop.
  • Fewer post-production iterations and bugs. By surfacing and resolving requirements gaps and design ambiguities during the planning phase, before code was written, the team saw noticeably lower post-launch rework compared to features built through traditional delivery cycles.
  • Requirements alignment in a single session. A process that previously took days to weeks of iterative meetings across teams was consistently completed on Day 1 of the structured workshop.
  • Compounding quality improvements. With each successive feature built using AI-DLC, code generation quality improved as steering files were refined, reducing the adjustment needed after generation and raising the baseline for all subsequent features.

The following table summarizes the delivery impact.

Metric Traditional delivery With AI-DLC
Development phase time Baseline ~70% reduction
Development phase for a full-stack feature 3–4 sprints Approximately 1 sprint
Time to agreed requirements Days to weeks of iterative review Day 1 of a structured session
Requirement gaps and blockers Surfaced during implementation Surfaced during planning, before design or code begins
Scope changes Absorbed mid-implementation, cost grows with progress Resolved at design level before code is written
Cross-team alignment Sequential, with each team engaged at their stage Parallel, with all teams present and aligned from Day 1
Post-production bugs and rework Higher gaps discovered post-implementation Reduced ambiguities resolved upfront during planning
Code quality consistency Dependent on individual developer conventions Consistent, enforced through steering files from the first line

The result is not only faster delivery. It is a different way of working across the full lifecycle. Alignment happens at inception and continues throughout development. Every team has visibility into what is being built, contributes feedback without waiting for a handoff, and sees progress in real time.

Conclusion

By adopting AI-DLC with Kiro IDE, Dynata reduced the development phase of full-stack features from 3–4 sprints to approximately one sprint. Dynata achieved this not by adding engineers or cutting scope, but by aligning every team on a shared plan before the first line of code is written. Requirement gaps surface during planning. Teams absorb scope changes at the design level. The repository preserves institutional knowledge as structured, traceable artifacts.

Dynata is expanding AI-DLC adoption across additional product lines and engineering teams, applying the methodology to more complex integration and infrastructure work. The team continues to refine steering files across projects and is exploring Kiro IDE hooks to automate quality enforcement at defined workflow points.

Building on the flexibility that let teams apply individual phases independently, Dynata is now planning AI-DLC V2.0. This version adds support for targeted phases, so teams can invoke a specific part of the workflow, such as Inception, design, or construction, on its own. They no longer need to step through the full end-to-end cycle. This gives teams finer-grained control to apply AI-DLC exactly where it delivers the most value for a given piece of work.

“Once the requirements, design, and tasks are all agreed upon with the full team, delivery becomes much faster, and the whole team is part of that journey, not just engineering.”

Get started

 


About the authors

Scope workloads and share resilience policies with AWS Resilience Hub

Post Syndicated from Sinisa Mikasinovic original https://aws.amazon.com/blogs/architecture/scope-workloads-and-share-resilience-policies-with-aws-resilience-hub/

A resilience plan is useful only while it reflects the application you run. After a migration or deployment, a service can acquire a new dependency while its documented recovery objectives stay the same. In a shared Kubernetes cluster, even deciding which resources belong to that service can take investigation.

On September 18, 2026, the Next generation of AWS Resilience Hub introduced three capabilities to help you keep workload scope and resilience requirements aligned with your running environment:

  • Amazon Elastic Kubernetes Service (Amazon EKS) label support for service input sources selects Kubernetes resources using the labels that already describe your workloads.
  • Generative AI-powered Dependency Insights analyzes a service’s discovered dependencies and highlights the patterns worth reviewing.
  • Resilience policy sharing through AWS Organizations lets a central team define resilience requirements once and make them available to member accounts.

In this post, you follow a team reviewing the resilience of a sample workload named checkout on Amazon EKS, representing an online store’s checkout service. The team starts by defining which Kubernetes resources belong to checkout, using labels to distinguish it from inventory in the same namespace. This establishes the resource boundary for the resilience review.

With that boundary defined, the team examines discovered dependencies and uses Dependency Insights to prioritize findings for investigation. A cross-Region dependency becomes a concrete question about whether checkout’s recovery design accounts for the services it requires to operate. The service owner then works with a central resilience team to validate the recovery requirements and apply a shared policy. Maintaining those requirements centrally gives teams a common definition to adopt across accounts, while policy events help the central team track adoption.

You perform these steps using the console or the AWS Command Line Interface (AWS CLI). The workload generates synthetic traffic to demonstrate dependency discovery.

About the application model: In the Next generation of Resilience Hub, the primary unit of assessment is a service. Services can be grouped into systems, and user journeys describe paths through them. See the key changes and the overview of the new hierarchy in the Next generation of Resilience Hub.

Prerequisites

To follow the examples, you need:

  • Access to the Next generation of AWS Resilience Hub in a supported AWS Region.
  • An Amazon EKS cluster with sample, labeled workloads and permission to configure access to the cluster.
  • If you follow the CLI examples, install or update the AWS CLI using the installation and update instructions. Your installation must support the resiliencehubv2 commands. These examples use Bash.
  • An AWS Identity and Access Management (IAM) role that Resilience Hub can assume, with the AWSResilienceHubV2AssessmentExecutionPolicy managed policy attached. The console can create this role when you create a service.
  • Kubernetes RBAC permissions and an EKS access entry that grant this role access to Kubernetes resources. The permissions guide provides the manifest and commands.
  • For policy sharing, an organization with all features enabled and access to its management account.

The examples use two accounts: a workload account containing the EKS cluster and Resilience Hub service, and a management account that owns the shared policy.

For the CLI examples, configure profiles named workload and management for the respective accounts. Replace the example cluster ARN and other placeholders with your own values, and set the Region:

export AWS_REGION=us-east-1

Each command uses --profile to select the account. Follow either the console steps or their CLI equivalents for each operation.

Amazon EKS label support for service input sources

A service input source tells Resilience Hub where to discover a service’s resources. Input sources can be AWS CloudFormation stacks, Terraform state files, resource tags, or Amazon EKS clusters. For an EKS input source, you select the namespaces to include. With this launch, you can also use Kubernetes label selectors to filter resources within those namespaces.

A namespace can contain workloads owned by different teams. With labels, you can select checkout’s resources without including inventory or reorganizing the namespace for an assessment. You use the same labels your teams already maintain.

How it works

Specify the cluster and one or more namespaces, then add label selectors to filter resources within each namespace. A selector contains a key, an operator, and values. Selectors within a namespace use AND logic: an object must match all of them. Without a selector, Resilience Hub discovers every supported object in the selected namespaces.

Checkout and inventory share the production namespace. Their resources carry app=checkout and app=inventory, respectively. Selecting production with app In checkout includes checkout’s Deployment and ReplicaSet and excludes inventory.

A label selector chooses checkout resources from a namespace shared with inventory

Figure 1: Selecting checkout resources with a Kubernetes label

The resource list also includes supporting AWS infrastructure, such as the cluster, node group, virtual private cloud (VPC), and subnets. Labels select Kubernetes objects. They don’t make that shared infrastructure exclusive to checkout.

How to use it

In the Resilience Hub console:

  1. Choose Create service and enter a name. Under Permission, choose Create default role to create an IAM role with the required AWS permissions, or choose Use another role to select an existing role.
  2. Under Service resource discovery, choose Amazon EKS cluster and enter the cluster ARN.
  3. Choose Configure namespaces, enter production, and add a label selector with key app, operator In, and value checkout.
  4. Choose Save, select Enable dependency discovery for this service, then choose Create service. Resource discovery starts automatically.
Configure namespaces dialog selecting production with app In checkout

Figure 2: Scoping the checkout service to one namespace and one label

If you prefer to use the AWS CLI instead of the console, run the following commands to create the service and add its EKS input source.

Skip this block if you already created the service using the preceding console steps.

The console displays selector operators as In, NotIn, Exists, and NotExists. In the API, use IN, NOT_IN, EXISTS, and DOES_NOT_EXIST, respectively.

In the following command, replace ResilienceHubRole with your service’s IAM role name.

# Create the checkout service with dependency discovery enabled and save its ARN
SERVICE_ARN=$(aws resiliencehubv2 create-service \
  --profile workload \
  --name checkout \
  --regions "$AWS_REGION" \
  --permission-model '{"invokerRoleName": "ResilienceHubRole"}' \
  --dependency-discovery ENABLED \
  --query 'service.serviceArn' --output text)

# Add the EKS input source, selecting resources labeled app=checkout in production
aws resiliencehubv2 create-input-source \
  --profile workload \
  --service-arn "$SERVICE_ARN" \
  --resource-configuration '{
    "eks": {
      "clusterArn": "arn:aws:eks:us-east-1:111122223333:cluster/resilience-demo",
      "namespaces": ["production"],
      "labelSelector": {
        "matchExpressions": [
          {"key": "app", "operator": "IN", "values": ["checkout"]}
        ]
      }
    }
  }'

After resource discovery completes, you can use the following command to list the discovered resource types. If you created the service on the console, first set SERVICE_ARN to that service’s ARN.

# List discovered resource types after resource discovery completes
aws resiliencehubv2 list-resources \
  --profile workload \
  --service-arn "$SERVICE_ARN" \
  --query 'serviceResources[].resource.resourceType'

Generative AI-powered Dependency Insights

Dependency Insights uses generative AI to summarize discovered dependencies and highlight patterns that warrant resilience review. It provides an overview and categorized findings.

The Resources list describes the service’s components. The Dependencies list shows endpoints discovered through DNS activity, including AWS service endpoints in other Regions and external APIs. When that list grows to hundreds of entries, Dependency Insights helps you prioritize what to investigate.

How it works

Dependency Insights builds on dependency discovery, an optional, separately priced feature. Dependency discovery analyzes DNS activity through Amazon Route 53 Resolver and does not require installing an agent in your workload. It examines up to 35 days of available history and continues monitoring for changes. The dependency list shows each endpoint’s name, location, and when it was first and last seen.

Dependency Insights analyzes this list, filters out common service activity that typically does not indicate resilience risk, and highlights patterns such as these:

Pattern What to investigate
Cross-Region dependencies Whether a call to another Region is intentional, and how it affects recovery
New dependencies Whether an endpoint first observed within the last seven days changes the documented architecture
Third-party dependencies How the service behaves when an external provider is unavailable
Uneven usage Whether activity reflects a scheduled job or a dependency used only at certain times
Discovered dependency data feeds a generated overview and findings that a service owner reviews

Figure 3: Turning dependency data into a focused review

Insights prioritize review. They don’t prescribe fixes or list every dependency. Use the full dependency list when you need detail. A new workload’s insights reflect only the history it has generated.

How to use it

In the Resilience Hub console, wait for initial dependency discovery to complete before generating insights:

  1. Open the service. On the Assessment tab, scroll to the Dependencies section to review discovered dependencies.
  2. Choose Generate insights.
  3. Read the overview and compare each finding with the intended architecture.

You can regenerate insights once every 24 hours. This limit applies whether you generate them through the console or the AWS CLI.

Generated Insights overview and categorized findings for the checkout service

Figure 4: Dependency Insights for the checkout service

If you prefer the AWS CLI, use the following commands instead of the console steps. Set SERVICE_ARN to your service’s ARN.

Enable dependency discovery only if it is not already enabled. After initial discovery completes, review the dependency list and generate insights. If you already generated insights on the console, skip start-dependency-insights and use get-dependency-insights to retrieve the existing result.

Generation can complete asynchronously. Run get-dependency-insights again if the returned status is IN_PROGRESS.

# Enable dependency discovery if it is not already enabled
aws resiliencehubv2 update-service \
  --profile workload \
  --service-arn "$SERVICE_ARN" \
  --dependency-discovery ENABLED

# Review the dependency list
aws resiliencehubv2 list-dependencies \
  --profile workload \
  --service-arn "$SERVICE_ARN" \
  --query 'dependencySummaries[].[dependencyName, location, dependencyId]' \
  --output table

# Start generating dependency insights
aws resiliencehubv2 start-dependency-insights \
  --profile workload \
  --service-arn "$SERVICE_ARN"

# Retrieve insights; if status is IN_PROGRESS, run this command again later
aws resiliencehubv2 get-dependency-insights \
  --profile workload \
  --service-arn "$SERVICE_ARN"

The checkout service produced five insights across 29 dependencies. The following response shows the generated overview and all five findings:

{
    "overview": "5 insights generated for service 'checkout' across 29 dependencies, surfacing cross-region, third-party, and uneven usage patterns.",
    "insights": [
        {
            "category": "CROSS_REGION",
            "description": "DynamoDB in us-west-2 is reached cross-region with hard criticality, coupling this service's availability to a remote region. This dependency carries medium DNS query volume with consistent usage."
        },
        {
            "category": "CROSS_REGION",
            "description": "PagerDuty (api.pagerduty.com) is reached cross-region to us-west-2, creating an availability dependency on that remote region."
        },
        {
            "category": "THIRD_PARTY",
            "description": "Three third-party dependencies — example.com, example.net, and example.org — are present at medium DNS query volume with consistent usage patterns."
        },
        {
            "category": "THIRD_PARTY",
            "description": "GitHub is a third-party dependency with low DNS query volume."
        },
        {
            "category": "UNEVEN_USAGE",
            "description": "Two in-region dependencies show highly uneven daily usage: an S3 bucket (amzn-s3-demo-bucket) and an ECR registry (602401143452.dkr.ecr.us-east-1.amazonaws.com)."
        }
    ],
    "status": "COMPLETED",
    "createdAt": "2026-10-08T14:24:27.372000-05:00"
}

The first finding identifies a cross-Region Amazon DynamoDB dependency. If checkout requires that dependency to accept orders, a disruption in the remote Region could prevent it from operating even when its Amazon EKS resources remain healthy. The service owner should verify which operations require the table and review how those operations behave when it is unavailable. That review determines whether the dependency fits the recovery design or whether the architecture needs to change. DNS activity alone cannot answer that question.

The uneven-usage finding points to container image infrastructure. Image downloads during pod startup can produce bursts of DNS activity. This is a pattern to investigate, rather than evidence that your application has an outage.

Classify a dependency as hard if the service fails without it, or soft if the service degrades but keeps running. In the sample environment, we had classified the DynamoDB dependency as hard using the following command. The comment describes an illustrative failure assumption, which requires validation through testing:

# Classify the selected dependency as hard and record the reason
aws resiliencehubv2 update-dependency \
  --profile workload \
  --service-arn "$SERVICE_ARN" \
  --dependency-id "<dependency-id>" \
  --criticality HARD \
  --comment "Checkout writes orders to this table and cannot accept orders without it."

Resilience policy sharing through AWS Organizations

After reviewing checkout’s dependencies, the service owner can validate its recovery requirements with the business and the central site reliability engineering (SRE) team. Dependency findings inform that discussion. They don’t automatically determine the policy targets.

The central SRE team can codify the agreed requirements into a resilience policy, defining availability and recovery targets for critical services. The management account or a delegated administrator can share that policy across the organization so service owners can apply the centrally maintained requirements without creating separate copies.

The policy owner can review attachment and detachment events to track adoption. If a service stops using the policy, the SRE team can engage its owner to understand why and determine whether different requirements are appropriate. Application owners remain responsible for meeting the targets.

How it works

A policy can combine four target types: availability, recovery from an Availability Zone disruption, recovery from a Regional disruption, and data recovery. The recovery time objective (RTO) specifies the targeted recovery time. The recovery point objective (RPO) specifies the tolerable period of data loss.

When sharing is on, the policy appears in every member account alongside the account’s own policies, and its ARN shows which account owns it. Each service owner chooses whether to apply it. If a requirement must apply regardless of that choice, associate the policy with a system containing the relevant services instead, as described in Applying resilience policies across accounts.

The owning account can review a timeline of the services that start or stop using the policy. Turning sharing off stops evaluating member services against the policy and removes it from the services that used it, so coordinate changes with the teams involved.

A centrally owned resilience policy is shared with service owners in two member accounts

Figure 5: One policy definition, shared with member accounts

How to use it

The organization integration is a one-time setup in the management account:

  1. In the Resilience Hub console, open AWS Organizations settings.
  2. Select Enable trusted access and create a service-linked role, then choose Create integration.

To confirm that the service-linked role exists in a member account:

# Confirm the service-linked role exists in the workload account
aws iam get-role \
  --profile workload \
  --role-name AWSServiceRoleForResilienceHub \
  --query 'Role.Arn'

Next, create the policy in the management account with sharing turned on. Policy names can contain letters, numbers, hyphens, and underscores, but no spaces. The following targets are examples. Agree on real values with the business and service owners. This example uses an availability target of 99.9 percent.

# Create an organization-shared resilience policy and save its ARN
POLICY_ARN=$(aws resiliencehubv2 create-policy \
  --profile management \
  --name tier-1-critical \
  --availability-slo '{"target": 99.9}' \
  --multi-az '{"rtoInMinutes": 5, "rpoInMinutes": 1, "disasterRecoveryApproach": "ACTIVE_ACTIVE"}' \
  --multi-region '{"rtoInMinutes": 15, "rpoInMinutes": 5, "disasterRecoveryApproach": "WARM_STANDBY"}' \
  --data-recovery '{"timeBetweenBackupsInMinutes": 60}' \
  --sharing-enabled \
  --query 'policy.policyArn' --output text)

In the workload account, find the shared policy and apply it to the service. On the console, choose Change policy on the service and select the shared policy. With the AWS CLI:

# List policies available in the workload account, including shared policies
aws resiliencehubv2 list-policies \
  --profile workload \
  --query 'policySummaries[].[name, policyArn, sharingEnabled]' \
  --output table

# Apply the shared resilience policy to the checkout service
aws resiliencehubv2 update-service \
  --profile workload \
  --service-arn "$SERVICE_ARN" \
  --policy-arn "<shared-policy-arn>"
Lifecycle panel showing Policy associated and four targets pending assessment

Figure 6: Policy targets are defined. Assessment of those targets is still pending

Back in the management account, the policy owner can confirm adoption:

# Review service attachment events for the shared policy
aws resiliencehubv2 list-policy-events \
  --profile management \
  --policy-arn "$POLICY_ARN" \
  --event-types POLICY_ATTACHED_TO_SERVICE

An attachment event confirms adoption, not compliance. After we applied the shared policy, the console changed each target from Not defined in policy to Pending failure mode assessment. A failure mode assessment and appropriate resilience testing are needed to determine whether the service meets those targets.

Things to know

  • Shared clusters. Labels decide which Kubernetes objects belong to a service. On a cluster that several workloads share, review the discovered dependencies to confirm they belong to the service, as described in the guidance for shared EKS clusters.
  • What DNS-based discovery can’t see. Connections made directly by IP address aren’t discovered, AWS Lambda functions need VPC connectivity to be covered, and very infrequent calls might not appear in initial results. See Coverage and known limitations.
  • Timing. Results reflect the history available to the service. Give a new workload time to build history before drawing conclusions from its insights. You can regenerate insights once every 24 hours.

Cleanup

If you created resources to try these capabilities, remove them when you finish. Turning sharing off also removes the policy from every member service that uses it, so only do this for a demonstration policy:

# Stop sharing the demo policy; this also removes it from member services
aws resiliencehubv2 update-policy --profile management --policy-arn "$POLICY_ARN" --no-sharing-enabled
# Delete the demo policy
aws resiliencehubv2 delete-policy --profile management --policy-arn "$POLICY_ARN"
# Delete the demo Resilience Hub service
aws resiliencehubv2 delete-service --profile workload --service-arn "$SERVICE_ARN"

Delete the EKS cluster as well if you created it only for this walkthrough.

Conclusion

Start with a service whose architecture and ownership you know. Use EKS labels to check its Kubernetes resource boundary, then review Dependency Insights against its intended behavior. If several teams maintain the same requirements separately, consider a shared policy.

You can use the capabilities together or independently. Label support applies to EKS workloads. Dependency Insights and policy sharing also support services beyond EKS. Insights focus the investigation. Your architecture review determines what to change.

Further reading


About the author

Exploring AWS Lambda Web Functions for building web applications

Post Syndicated from Julian Wood original https://aws.amazon.com/blogs/compute/exploring-aws-lambda-web-functions-for-building-web-applications/

You can now run a standard Node.js web server on AWS Lambda with Lambda Web Functions, a new type of Lambda function. Deploy it with one command or AWS CloudFormation and get a public HTTPS endpoint with no adapter layer and no servers or load balancers to manage.

Lambda Web Functions suit server-side rendered web apps, REST and GraphQL APIs, streaming endpoints, and microservices. Building on the launch announcement, this post uses a sample application to show how one execution environment handles many requests at once and how the same app serves a global audience. See how active-CPU billing keeps the cost of waiting low while your code waits, with memory and requests billed separately.

Overview

Lambda launched in 2014 with a minimal model: upload your code, and the service handles everything else. For over a decade that code ran as an event handler, invoked once per event such as a batch of queue messages or an object landing in Amazon Simple Storage Service (Amazon S3). We now call this an Event Function and it remains the right choice for event-driven work.

Web Functions are a second type of Lambda function, running on the same Firecracker-based isolation as Event Functions. Instead of a handler that receives an event, your code runs as a standard HTTP server that listens on a port, which is what lets a web framework, or in this case a plain Node.js server, run unmodified.

A Web Function is made of three resources:

  • Web Function is the top-level resource with a name and configuration.
  • Revision is an immutable snapshot of your code and configuration which Lambda creates each time you deploy code or change revision configuration, so you can roll back by routing traffic to an earlier revision.
  • Endpoint is a network entry point with a unique HTTPS domain, an authentication type, and the rules that route traffic to your revisions.

At launch, Web Functions run Node.js 24 on Arm64, with 2 vCPUs and 2 GB of memory per execution environment. Web Functions support Amazon Virtual Private Cloud (VPC) connectivity, allowing your function to access resources that exist within a VPC.

Walkthrough

Node.js frameworks that start an HTTP server, such as Express, Fastify, and Hono, run without adapters. Next.js in standalone mode needs a small index.js that sets its host and port. This post uses a plain Node.js server to stay dependency-free.

The full source is available in the Serverless Patterns collection along with other Web Functions patterns.

Prerequisites

You need an AWS account with the AWS Command Line Interface (AWS CLI) installed and configured and Node.js installed locally.

The pattern instructions show how to clone the repo and change to the pattern directory or download the pattern .zip file to run the example.

The app

The dashboard is a single Node.js HTTP server that streams live server metrics to the browser over server-sent events (SSE).

The runtime provides the address to listen on in the AWS_LAMBDA_HTTP_ENDPOINT environment variable, in host:port form. The server reads it and starts listening, exactly as it would anywhere else:

const [host, port] = (process.env.AWS_LAMBDA_HTTP_ENDPOINT || "0.0.0.0:3000").split(":");
server.listen(Number(port), host);

The runtime is nodejs24.x, and it loads index.js at the package root by default. To use a different entry file, set AWS_LAMBDA_NODEJS_ENTRYPOINT to its path.

Run locally

You can run the server locally with npm start.

Browse to http://localhost:3000/ to view the dashboard streaming from the local server.

Deploy

  1. Deploy using the single command:
    aws lambda-web deploy --name lambda-web-sse-dashboard --code .

    For this example, you can also use npm run deploy defined in the package.json file.

The AWS CLI bundles your code, creates the function and an endpoint named dev with a live HTTPS URL. It also creates an execution role and a versioned S3 bucket, if one does not exist, that all CLI-deployed Web Functions in the Region share.

  1. Press Enter to create the resources.

The AWS CLI output shows the URL in the format: https://<id>.<shard>.lambda-web.<region>.on.aws

AWS CLI terminal output from the aws lambda-web deploy command showing the created function and live HTTPS endpoint URL

Figure 1: AWS CLI showing the aws lambda-web deploy command.

Browse to the URL and the dashboard loads, already streaming. This endpoint is public and the sample has no authentication, so use it for evaluation and delete it when you finish.

Streaming dashboard in the browser showing live function information, including environment uptime and open connection count

Figure 2: The app streaming dashboard showing function information.

The dashboard shows how long the execution environment has been alive and how many streaming connections it is holding open. It shows how much CPU it has used with a button to generate real CPU load on demand. The function runs in the US East (N. Virginia) Region (us-east-1). From my laptop in the United Kingdom, latency is 90 ms.

The dashboard updates every two seconds over a single HTTP streaming response. A Web Function environment is a long-lived process, so it keeps the connection open and keeps pushing data to the browser. The long-lived process can also run background tasks outside the main request path, such as emitting telemetry events.

An Event Function environment serves one request at a time. It can stream a response during an invocation, but each open stream occupies a whole environment. Lambda freezes the environment between invocations, so background work stops.

Active-CPU billing

If the environment stays alive for minutes at a time, what are you paying for? You pay for requests, consumed memory, and the CPU time the environment consumes. For this example, compare connection uptime with CPU time. The environment keeps running but uses only a fraction of a second of CPU.

Choose Generate CPU load, and the app runs a cryptographic hashing loop for about 1.5 seconds. CPU time climbs on the chart, then falls back to near zero when the work stops. When a request waits on a database, downstream API, or AI inference, your code uses little CPU, so the wait costs less than it would under wall-clock billing.

Dashboard CPU chart showing a spike in CPU time after choosing Generate CPU load, then falling back toward zero

Figure 3: CPU chart mid-spike after choosing Generate CPU load.

For pricing details, read the AWS Lambda pricing page.

Concurrency and scale-out

With Web Functions, each execution environment handles many requests at once, not one at a time. As environments approach their concurrency limit, Lambda adds more environments and removes them again when traffic falls.

You can control how a Web Function scales. maxConcurrencyPerEnvironment sets the maximum number of requests each execution environment handles at once, up to 128, with a default of 64. On the endpoint, maxEnvironments caps the number of execution environments and rateLimit caps requests per second. You can use these two settings to protect downstream dependencies such as databases and external APIs. Both apply in each Region the endpoint serves and maxEnvironments applies to each revision. Requests above a limit receive HTTP 429 and are not queued.

Generate load on the app. Send 300 streaming requests to the /api/events endpoint, with up to 200 open at once, rather than a burst of quick responses.

curl -sN --parallel --parallel-immediate --parallel-max 200 --max-time 90 "https://<ENDPOINT>/api/events?[1-300]"
Dashboard showing many concurrent SSE connections held open by a single execution environment during the load test

Figure 4: Concurrency showing increased connections in a single execution environment.

The dashboard updates to reflect the active SSE connections and peak for the currently connected execution environment.

The runtime also writes a per-environment summary record to Amazon CloudWatch Logs every minute with the instance ID, request count, peak concurrency, and CPU time. A CloudWatch Logs Insights query aggregates them across the fleet:

QID=$(aws logs start-query \
    --log-group-name /aws/lambda/web/lambda-web-sse-dashboard \
    --start-time $(($(date +%s)-300)) --end-time $(date +%s) \
    --query-string 'filter type="platform.invokeSummary" | stats count_distinct(record.instanceId) as environments, sum(record.metrics.requests) as totalRequests, max(record.metrics.concurrentRequests) as peakConcurrency, sum(record.metrics.cpuTimeMs)/1000 as totalCpuSeconds' \
    --query queryId --output text) && sleep 6 && aws logs get-query-results --query-id "$QID"

From a sample run of 300 streaming requests with up to 200 open at once, the logs showed the following:

  • Execution environments used: 5.
  • Total requests handled: 484.
  • Peak concurrency on a single environment: 59.
  • Total CPU consumed across the whole fleet: 4.75 seconds.

One environment peaked at 59 connections, near the default limit of 64. Lambda adds environments before existing ones reach their limit, so this load used five. Event Functions serve one request per environment, so 200 open streams would need up to 200 environments. Across 484 requests, the whole fleet used about 4.8 seconds of CPU.

Each environment reports only its own connections, because Lambda routes each request independently with no session affinity.

Going multi-Region

The same app can serve each caller from the Region with the lowest latency. Because an endpoint’s type and Regions are fixed when you create it, going global means adding a new endpoint:

aws lambda-web deploy --name lambda-web-sse-dashboard --code . --endpoint-name global --endpoint-type MultiRegion --regions us-east-1,eu-west-2
AWS CLI terminal output creating a multi-Region global endpoint and confirming replication to a second Region

Figure 5: AWS CLI creating a global endpoint.

Lambda replicates your code and revision configuration, including environment variables, to each Region, and centralizes logs in the home Region. In this case, Lambda replicates to Europe (London) Region (eu-west-2). It provides a single global domain URL and uses Amazon Route 53 latency-based routing to send each request to the lowest-latency Region.

Browse to the new global URL which is now in the format: https://<domain>.lambda-web.global.on.aws

App dashboard served from the closest Region through the global endpoint, showing lower latency for the local caller

Figure 6: The app dashboard showing routing to the local Region.

From my laptop in the United Kingdom, the request routes to eu-west-2 (London) in 16 ms, where the single-Region endpoint in us-east-1 replied in 90 ms. These are single measurements from one location, not a benchmark. Each request goes to the Region with the lowest latency for the caller through the same URL. Lambda manages the DNS routing, so there are no load balancers or Route 53 records to configure.

Authentication and running behind Amazon CloudFront

Each endpoint has an authentication type that you can change without redeploying code.

With ApplicationManaged, the endpoint is public, and your code handles authentication (OAuth, JWT, a session cookie, or Amazon Cognito). Administrators can prevent public endpoints across an organization with a service control policy that uses the lambda:Request/WebFunctionAuthType condition key.

With IamAuth, Lambda validates each request with AWS Signature Version 4 (SigV4) for single-Region endpoints and SigV4a for multi-Region endpoints. Only callers with the lambda:InvokeWebFunctionEndpoint permission reach your app, which suits service-to-service APIs. Endpoints are always reachable over the public internet, and IamAuth controls who can invoke them.

Endpoints use a Lambda-provided domain. For a custom domain, edge caching, and AWS WAF, place Amazon CloudFront in front of the endpoint. Use IamAuth with origin access control so the Lambda domain cannot bypass CloudFront which still passes your application’s own Authorization header through to your code.

Build it with an agent

Because deployment is a single aws lambda-web deploy command and the code is an ordinary HTTP server, you can scaffold, build, and deploy an app from a prompt using AWS skills for coding agents. The Agent Toolkit for AWS includes Web Functions.

Clean up

To remove the default resources that the aws lambda-web deploy command created, see the cleanup instructions in the repo.

Conclusion

AWS Lambda Web Functions bring the serverless operating model to web applications without adapter layers. You write a standard Node.js HTTP server, deploy it with one command or CloudFormation, and get a scalable, pay-per-use HTTPS endpoint with global routing built in. Deploy the sample application from the Serverless Patterns collection along with other Web Functions patterns. Read the Lambda Web Functions documentation to go deeper and the Lambda roadmap.

For more serverless learning resources, visit Serverless Land.

Agentic antimicrobial resistance surveillance with AWS HealthOmics and Amazon Bedrock AgentCore

Post Syndicated from Nicolo Cosimo Albanese original https://aws.amazon.com/blogs/architecture/agentic-antimicrobial-resistance-surveillance-with-aws-healthomics-and-amazon-bedrock-agentcore/

Turn public sequencing data into plain-language answers about resistance genes, without writing bioinformatics code or SQL.

Bacterial antimicrobial resistance (AMR) is one of the top public health threats of our time. A systematic analysis published in The Lancet estimated that AMR was directly responsible for 1.27 million deaths in 2019 and associated with 4.95 million (Murray et al., 2022). Surveillance programs work to slow that trend by tracking which resistance genes circulate, in which organisms, and how the picture shifts over time.

The raw material for that work is already public. Sequencing archives such as the NCBI Sequence Read Archive (SRA) hold hundreds of thousands of bacterial whole-genome sequencing runs, and the data is mirrored on the Registry of Open Data on AWS. The constraint isn’t access to data. It’s the work of turning a raw sequencing run into an answer. That path normally means assembling a genome, screening it for resistance genes with specialized tools, loading the results into an analytical store, and writing SQL to explore them. Each step assumes bioinformatics skills, so the people who ask the epidemiological questions often depend on a specialist to answer them.

In this post, we present a reference architecture that shortens that path. A researcher signs in to a web application, submits a public SRA accession, and after the pipeline finishes asks, in plain language, “What AMR genes were found in Salmonella?” The system answers with the genes detected, the tool that found each one, and a confidence value taken from the data. The architecture is defined entirely in AWS Cloud Development Kit (AWS CDK) and deploys into an account with a single command. This is a demonstration architecture rather than a clinical system, and the accompanying code is available for you to deploy and adapt.

How this architecture differs from variant interpretation

An earlier AWS post, Accelerating genomics variant interpretation with AWS HealthOmics and Amazon Bedrock AgentCore, established a pattern that this solution shares: a genomics pipeline feeds Amazon S3 Tables, a capability of Amazon Simple Storage Service (Amazon S3), in Apache Iceberg format, Amazon Athena queries the tables, and a Strands agent on Amazon Bedrock AgentCore turns questions into SQL. We reuse that querying pattern rather than reinvent it. What’s different here is the genomics problem and the shape of the pipeline around it.

The variant interpretation solution starts from a Variant Call Format (VCF) file, where variants have already been called, and annotates them. This solution starts a step earlier, from raw sequencing reads, and performs read quality control, de novo assembly, and gene detection before results exist to store. It adds an ingestion stage that pulls reads directly from a public archive and deduplicates work across runs, and it merges detection output into Iceberg idempotently so that reprocessing an isolate updates its rows in place. Those differences, public-data ingestion, assembly-based detection, and idempotent storage, are where the engineering effort of this architecture sits.

Solution overview

The solution is a set of loosely coupled stages, and data moves in one direction from ingestion through processing and storage to a conversational query layer. The following stages map to the numbered zones in Figure 2.

Solution architecture diagram showing five stages: ingestion, genomics processing, storage, analytical store, and agent

Figure 2: Solution architecture, organized as the five stages the data passes through

  1. Ingestion: An Amazon EventBridge Scheduler rule or an on-demand REST call starts an AWS Step Functions workflow. An Amazon Elastic Container Service (Amazon ECS) task on AWS Fargate fetches and converts the sequencing run for the requested accession from the NCBI SRA mirror on the Registry of Open Data, and Amazon DynamoDB records processed isolates so that repeat runs skip work that’s already done.
  2. Genomics processing: AWS HealthOmics runs a single managed Nextflow workflow that chains read quality control with fastp, de novo assembly with SKESA, and resistance gene detection with AMRFinderPlus. Container images come from Amazon Elastic Container Registry (Amazon ECR), and the AMRFinderPlus reference database is built into the image because HealthOmics tasks run without internet access.
  3. Harmonization and storage: An AWS Lambda function normalizes the detection output into the community hAMRonization schema (a standard from PHA4GE for reporting AMR detection results), and an AWS Glue job merges it into Iceberg tables on Amazon S3 Tables. The merge is idempotent, so rerunning an isolate updates rows rather than duplicating them.
  4. Analytical store: Amazon Athena queries the Iceberg tables through the AWS Glue Data Catalog, which gives the agent a fast SQL surface over the harmonized results.
  5. Conversational agent and web application: A Strands agent hosted on AgentCore runtime answers questions through a set of Athena-backed tools, with Amazon Bedrock Guardrails keeping responses grounded. A React single-page application on Amazon S3 and Amazon CloudFront, protected by AWS WAF and authenticated with Amazon Cognito, is the front door. Amazon API Gateway serves REST calls and streams the chat over WebSocket.

Ingesting public sequencing data

Ingestion has one job: given an accession, get the reads into the account without manual downloads or credentials. Because the SRA mirror is part of the Registry of Open Data, an anonymous Amazon S3 read is enough. A Step Functions workflow drives the process. It starts a Fargate task that downloads the sequencing run for the accession from the public bucket and converts it to paired-end FASTQ in a staging bucket, then hands off to the genomics stage.

Two design choices keep the stage predictable. A DynamoDB table records every isolate that has been processed, so a scheduled weekly run or a repeated manual submission doesn’t reprocess the same accession. Fargate runs the copy as a long-running task because fetching and converting a large sequencing run can take well beyond a few minutes. The result is a loop that runs on a schedule or on demand from the web application, without an operator in the middle.

Detecting resistance genes with AWS HealthOmics

Turning reads into a list of resistance genes is the compute-heavy part of the pipeline. AWS HealthOmics runs Nextflow workflows without a cluster to provision or scale, which suits teams that do not run a bioinformatics system of their own.

The workflow is a single HealthOmics run with three tasks connected by Nextflow channels. fastp trims adapters and low-quality bases from the raw reads. SKESA assembles the trimmed reads into contigs. AMRFinderPlus screens those contigs for known resistance genes. Combining assembly and screening in one run avoids a fragile handoff between two separate runs and matches how community AMR pipelines are structured.

Running third-party bioinformatics tools on HealthOmics calls for a few adaptations worth stating plainly:

  • HealthOmics tasks have no internet access, so the AMRFinderPlus database is baked into the container image rather than downloaded at runtime.
  • Container image URIs are passed as workflow parameters instead of hardcoded, which keeps the workflow independent of account and AWS Region and lets HealthOmics verify image access before a run starts.
  • Task outputs are published to the run output path that HealthOmics exports to Amazon S3, rather than to arbitrary locations.

Each container image is built from official tool releases with pinned versions and scanned with Amazon ECR image scanning, so the pipeline doesn’t depend on a floating latest tag.

Harmonizing and storing results

AMRFinderPlus emits a tab-separated file per isolate, which is awkward to analyze across many samples. A Lambda function first normalizes that output into the hAMRonization schema. An AWS Glue job then reshapes the harmonized data into two Iceberg tables on Amazon S3 Tables: amr_profiles, with one row per detected gene, and isolate_metadata, with one row per isolate carrying its provenance and license.

The AWS Glue job writes with a MERGE keyed on the isolate and gene, so processing the same isolate twice updates the existing rows instead of appending duplicates. That idempotency matters for surveillance, where the same accession can arrive again through a scheduled run, and it keeps the tables consistent without a separate deduplication step. Storing the results as Iceberg gives the agent a columnar, transactional surface to query with low latency, and because Amazon S3 Tables registers the schema in the AWS Glue Data Catalog, Athena reads the same tables without extra setup. Converting per-sample files into a structured store is what makes conversational querying across the whole corpus possible.

Asking questions in natural language

The conversational layer uses the Strands Agents SDK and runs on Amazon Bedrock AgentCore, a platform to build, connect, and optimize agents at scale, with any framework or model. This solution uses AgentCore runtime, its serverless agent-hosting environment. It gives each user session an isolated environment, integrates with AWS Identity and Access Management (IAM), and traces the agent’s reasoning and tool calls. The team runs the agent without managing servers or scaling logic.

The agent reasons over four tools, and each one turns a natural-language request into a targeted Athena query against the Iceberg tables:

  • query_amr_profiles finds resistance genes for an organism over an optional date range.
  • get_resistance_trends summarizes how gene detections trend month over month.
  • compare_isolates compares resistance profiles across isolates.
  • lookup_gene_info returns detail on a specific resistance gene.

Grounding is enforced in two places. The system prompt requires the agent to cite the gene ID, the detecting tool, and a confidence value for every claim. It must also say plainly when no data supports an answer rather than guess. Amazon Bedrock Guardrails add a second layer, with a contextual grounding check and denied topics that keep responses tied to the returned rows and off medical or patient advice. The agent uses Anthropic Claude Opus 4.7 on Amazon Bedrock, invoked through a cross-Region inference profile, for the reasoning and synthesis those rules depend on. Model availability varies by AWS Region. See the Amazon Bedrock model support by Region documentation for current availability.

After an isolate has been processed, a researcher explores it through conversation instead of SQL. Asked “What AMR genes were found in Salmonella?”, the agent calls query_amr_profiles, retrieves the matching rows from Iceberg through Athena, and replies with the detected genes. Each gene is annotated with the detecting tool and its confidence value, along with a short reading of what the genes represent. A follow-up such as “Tell me more about mdsB” triggers lookup_gene_info without the user thinking about tables or queries.

Benefits

The architecture turns four recurring pain points into managed, repeatable steps.

Challenge How the architecture addresses it
Fetching and assembling public sequencing data by hand A Step Functions workflow pulls reads from the AWS Open Data SRA mirror and runs assembly and screening on managed HealthOmics compute.
Reconciling detection output across many samples A Lambda function harmonizes results and AWS Glue merges them idempotently into Iceberg tables on Amazon S3 Tables.
SQL and bioinformatics skills needed to ask a question A Strands agent on AgentCore turns plain-language questions into Athena queries and answers with cited evidence.
Reproducing the environment in another account The solution is defined in AWS CDK with no hardcoded identifiers, so it deploys with a single command.

Because this is a reference architecture rather than a customer deployment, its proof is that anyone can stand it up. The CDK application provisions every component described here, from the ingestion workflow to the Iceberg tables to the agent runtime and the web front end. It needs no hardcoded account identifiers or manual setup steps. Running costs depend on how many isolates you process and how often, because HealthOmics runs, AWS Glue jobs, and agent calls are billed per use. The architecture stays inexpensive when idle.

Conclusion

Pairing AWS HealthOmics for managed genomics processing with Amazon Bedrock AgentCore for grounded interpretation lets a surveillance team move from a public accession number to an answer about resistance genes in one continuous flow. Assembly, gene detection, harmonization, and storage happen without manual steps, and the analytical layer is reached through a conversation rather than a query console. The pattern reuses the S3 Tables and agent querying approach from earlier work and applies it to a different genomics problem. That problem begins at raw reads and depends on public-data ingestion and idempotent storage.

To go deeper, see the AWS HealthOmics and Amazon Bedrock AgentCore product pages, and the related post Accelerating genomics variant interpretation with AWS HealthOmics and Amazon Bedrock AgentCore.

The solution is defined entirely in AWS CDK and deploys into your own account. Explore the accompanying repository on GitHub and adapt the pattern to your own surveillance and life-sciences use cases.


About the authors

Building your AI vulnerability harness, Part 1

Post Syndicated from Nidhi Ramakant original https://aws.amazon.com/blogs/security/building-your-ai-vulnerability-harness-part-1/

Vulnerability scanners produce findings faster than manual triage can process them. Your developers ship more code with more dependencies, and the volume of candidate findings grows with it.

Many findings a scanner produces are unlikely to be exploited. The ones that matter need to reach an engineer fast, with enough evidence that they can act immediately rather than repeat the analysis. The challenge is separating signal from noise at the speed your pipeline demands.

This post shows you how to close the gap between detection and action. We built a three-layer pipeline that takes raw scanner findings and narrows them to a small, prioritized set with documented evidence of exploitability. The companion post, Configuring your AI vulnerability harness, will cover the steering file that drives the model’s behavior inside this pipeline. This post covers the layers around it.

Because the pipeline’s value comes from its filtering logic and evidence standards—not from any specific tool—you can substitute your own scanners and models without changing the architecture. Your AI provider, your infrastructure stack, and your scanner ensemble will differ from ours. The architectural decisions—what to filter at each layer, what counts as evidence, where to stop trusting the model—transfer regardless.

What a test harness is

A test harness is an automated framework that subjects a system to controlled inputs and observes whether outputs match expected behavior. Applied to vulnerability detection, the harness takes each candidate finding, constructs a test to evaluate exploitability, executes that test in a controlled environment, and records the evidence. The harness is the testing infrastructure, not the results it produces.

Traditional static application security testing (SAST) tools rely on pattern matching, with data-flow analysis varying by tool and language. They flag known-shape sinks well but struggle with multi-hop chains, business-logic flaws, context-dependent sanitization, and whether a sink is reachable from attacker-controlled input. AI-augmented analysis addresses that reasoning gap: tracing across files, inferring intent, and weighing deployment context.

The key conceptual distinction is that we don’t run a model against the codebase. The codebase is context passed to a model with a specific prompt. The model receives code and applies reasoning about how data flows, where trust boundaries exist, and whether security-relevant patterns are present.

Context scoping is one of the harder parts of this architecture because too much context can overwhelm the model’s reasoning window, while too little can produce analysis gaps that generate false negatives. For a basic AWS Lambda function, the relevant context might be a single file plus its AWS Identity and Access Management (IAM) role. For a complex microservice with shared libraries and layered infrastructure, deciding what to include requires understanding the application’s dependency graph. The model can reason about your deployment topology if you provide it; for example, by passing AWS Cloud Development Kit (AWS CDK) constructs or AWS CloudFormation templates as additional context. Starting narrow and expanding only when the model’s initial assessment is inconclusive generally produces better results than passing everything at once.

Building the pipeline

Security scanners can produce high volumes of findings each time they run. Many of these are false positives that look like vulnerabilities but aren’t real issues. To separate real problems from noise, the pipeline filters findings through three layers. Each layer applies a different type of evidence. By the end, a smaller number of prioritized issues backed by documented evidence remains.

Layer 1: Multi-scanner agreement

Plausibility comes first. Run multiple scanners independently against the same codebase: when two or more converge on the same finding, confidence increases. When only one scanner reports an issue and no other tool agrees, the pipeline flags that finding for extra scrutiny.

This approach—called multi-scanner agreement—is a low-cost way to increase confidence. It works with your existing tools and requires no new infrastructure.

Layer 2: Structural verification

The second layer verifies structure. AI-powered scanners describe how they think a vulnerability works: data enters through one function, passes through another, and reaches a point where it could cause harm. However, these descriptions can be wrong.

Before a finding moves forward, the pipeline verifies it against the actual code. It checks to see if the function that the scanner mentioned exists, that the file exists, and if data can flow along the path described by the scanner. If not, the pipeline rejects the finding.

This verification step uses the code’s abstract syntax tree (AST): a structured map of how functions, files, and data flows connect in your codebase. This step helps protect the credibility of your pipeline’s output. If unverified findings reach human reviewers, teams quickly learn to distrust the results, which reduces the security value of the tool.

Layer 3: Deployment context

The final layer adds deployment context. Vulnerabilities exist within the context of an architecture, which might include protective controls. These controls can include AWS WAF rules, network isolation, authentication requirements, and input validation.

This layer reads your infrastructure-as-code (IaC) templates—such as CloudFormation or Terraform files that define your cloud architecture—alongside your application source code. It then checks to see if an attacker can exploit a vulnerability by avoiding the controls that are in place.

Not all controls provide the same level of protection. A web application firewall rule that directly blocks the relevant attack technique provides stronger mitigation than one that addresses a different type of vulnerability. The pipeline treats control effectiveness as a spectrum, not a yes-or-no checkbox.

The result

The result is a short list of findings. Each one has cleared all three layers: it passed the plausibility check, its structure checks out against the code, and its deployment context indicates it can be taken advantage of. The number of findings decreases at each layer, and confidence in the remaining findings increases.

Principles

Four principles emerged from building this pipeline:

  • Progressive filtering with escalating evidence – Each layer demands a different kind of proof. The first layer checks that the finding is plausible. The second determines if it’s structurally real. The third analyzes if it’s exploitable in context. Using three focused layers works better than using one comprehensive layer, because each layer catches a different type of error.
  • Infrastructure context transforms prioritization – Without deployment context, you might assume that a critical-severity finding is more urgent than a medium-severity finding. However, a critical finding behind strong protective controls might be less urgent than a medium finding that’s directly exposed to the internet. To make this assessment, the pipeline needs access to your IaC templates, not only your application source code.
  • Independent agreement outperforms single-tool confidence – No single scanner reliably detects the full range of vulnerabilities. No single model produces correct results across all situations. When multiple independent tools agree on a finding, that agreement provides a stronger signal than one tool’s self-reported confidence score. This principle generally holds across tool choices.
  • AI-generated claims require verification before human review – Language models can produce outputs that sound plausible but might not be correct. Verifying AI-generated findings against the actual code structure—before a human reviews them—is what separates a useful system from one that erodes trust through confident-sounding errors.

How to apply this to your stack

You don’t need to build everything at once. Adopt them in order of immediate return:

  • Start with indexing – Build (or use) an AST-based index of your codebase that surfaces entry points, sinks, and authentication configuration. This costs nothing to run and gives you a map of your attack surface before any AI is involved.
  • Add multi-scanner triage – Take the output you already get from Semgrep, CodeQL, or equivalent tooling, and run it through the AST gate plus a large language model (LLM) exploitability classification. This delivers substantial noise reduction for a modest investment: you’re filtering output from tools you already run.
  • Add hypothesis generation – After triage is stable, add AI-driven discovery of vulnerabilities scanners miss: multi-hop chains, business-logic flaws, and cross-package data flows. This is the step where steering quality matters a great deal; see Configuring your AI vulnerability harness.
  • Add infrastructure context – Parse your CDK or CloudFormation, map controls to vulnerability classes, and apply the multipliers. This is where your prioritization becomes more targeted.
  • Add live verification – Run proofs of concept (PoCs) against a deployed pre-production target with structured success criteria. This step requires the most operational overhead, so run it last and only for findings that have already passed your confidence threshold.

Treat the pipeline as something you tune over time. Validate it against known true and false positives, adjust the weights, and rerun. Your results will change.

What this doesn’t solve

The harness is detection infrastructure. It helps you see which findings are likely real and which deserve attention first. It doesn’t:

  • Fix the code – Remediation is a separate problem. The harness produces findings with enough evidence that a human or agent can act on them; the act of remediation requires its own tooling and process.
  • Replace human review for final action – Even after three layers of filtering, the output is a prioritized list of likely-exploitable findings, not proven exploits. Priority 0 (P0) findings still warrant review by an engineer before they drive code changes.
  • Catch vulnerability classes outside the scanner ensemble’s detection profile – If none of your scanners look for a particular vulnerability category, the harness won’t surface it. AI-augmented hypothesis generation closes some of this gap, but novel or business-logic flaws still depend on what the model can reason about from code alone.
  • Verify live infrastructure state by default – Parsing IaC tells you what was defined. It doesn’t tell you whether a WAF rule got disabled last week, or whether a security group was modified after deployment. Live verification is an additional integration, not a property of the layers themselves.

Each of these limits points to where the architecture extends, not where it breaks. The harness is the first floor of a multi-story system, not the whole building.

What comes next

In this post, we showed how a three-layer pipeline—multi-scanner agreement, structural verification, and deployment context—narrows scanner output to a small, higher-confidence set of findings that your team can act on. The architecture is tool-agnostic; what transfers is where to filter, what counts as evidence, and when to stop trusting the model.

The architecture produces consistent results when the model receives consistent instructions. Our companion post, Configuring your AI vulnerability harness, covers the steering file that encodes this methodology as machine-executable instructions, including the confidence formula, the infrastructure control multipliers, and the verification gates that help prevent hallucinated findings from reaching your engineers. The same model that produces rigorous, evidence-grounded assessments with well-crafted instructions can produce inflated severity ratings and fabricated attack chains without those instructions. Configuration is what turns capability into judgment.

The window between disclosure and exploitation is narrowing. Embedding automated detection into your development workflows helps your team keep pace. We built this pipeline through trial and error and are sharing it so you can skip some of the mistakes we made.

For more information about the services mentioned in this post, see the AWS Lambda, AWS WAF, and AWS CloudFormation documentation.

If you have feedback about this post, leave a comment in the Comments section below.


nidhi ramakant

Nidhi Ramakant

Nidhi is a Software Development Manager at AWS with two decades of experience in security and enterprise architecture. She’s obsessed with helping customers secure their environments and building creative solutions that make security decisions easier. She’s equally invested in developing the talent around her. Outside work, she’s either binging shows or vibe coding apps for secure local LLMs so her kids can explore and learn safely.

Ievgeniia Ieromenko

Ievgeniia Ieromenko

Ievgeniia is a Software Development Engineer at AWS, where she builds tooling and agentic AI systems designed to strengthen security posture without slowing delivery. Outside of work, she enjoys traveling, volunteering, and outdoor adventures with her very good dog, Pickle.

justin kontny

Justin Kontny

Justin is a Senior Software Engineer, Security, at AWS who blends a passion for software development with deep expertise in cloud security. He focuses on building agentic security solutions using AI-Driven Development Lifecycle (AIDLC) practices, transforming security from a barrier into a business enabler. Outside of work, Justin enjoys spending time with his children and staying active outdoors.

Introducing EC2 AMI Tag Sharing: Share EC2 tags across AWS accounts

Post Syndicated from Gena Gizzi original https://aws.amazon.com/blogs/compute/introducing-ec2-ami-tag-sharing-share-ec2-tags-across-aws-accounts/

Tags are an important and versatile tool on AWS. FinOps teams track costs with them, security teams enforce compliance, and governance teams gate access through attribute-based access control (ABAC).

Tags become especially important as infrastructure management grows more complex across multiple accounts in an organization. Because compute is a common shared resource, many organizations tag their AMIs to track approval status, OS versions, and team. However, when you share an AMI with another AWS account, the tags you attached to it stay behind. The other account sees the image, but tag metadata used to describe it could not be shared.

Until today, the only way to solve this was to build a custom workflow to copy tags into every account the AMI is shared with. Typically created with services such as AWS Simple Notification Service (Amazon SNS) and AWS Lambda, this caused operational burden.

Today, we’re introducing EC2 Tag Sharing, a new feature that AMI owners can use to share tags with any account that has access to the image. Whether an AMI is shared with an account, across an organization, or made public, a shared tag is automatically shared alongside the image by adding the ec2:SharedTag/ prefix to the tag key. Updates also propagate automatically without any need for custom replication workflows. In this post, we walk through how tag sharing works, demonstrate a common use case, and cover considerations and best practices.

How it works

Any tag whose key starts with ec2:SharedTag/ is visible to all AWS accounts the AMI is shared with, and also when the AMI is shared publicly. Tags without the prefix remain private to the account that created them, exactly as they do today.

Only the resource owner can create, modify, or delete tags with the ec2:SharedTag/ prefix. Accounts you share the AMI with can view these tags but cannot change them. They can continue to add their own private tags to a shared AMI.

Shared tags count against the resource owner’s 50-tag-per-resource quota for both shared and private tags. They do not count against the quota of accounts you share the image with. When you copy an AMI that has shared tags, AWS Elastic Compute Cloud (AWS EC2) copies the shared tags to the new image when the --copy-image-tags parameter is included. At launch, tag sharing is supported specifically for AMI resources.

Creating a shared tag (CLI)

To share a tag, create it with the ec2:SharedTag/ prefix on the key name. For example, to share a tag indicating the AMI’s approval status:

aws ec2 create-tags \
    --resources ami-0abcdef1234567890 \
    --tags Key=ec2:SharedTag/status,Value=approved

You can add multiple shared tags alongside your private tags. In the following example, status and os-version are shared with other accounts. The team tag remains private to the owner.

aws ec2 create-tags \
    --resources ami-0abcdef1234567890 \
    --tags \
    Key=ec2:SharedTag/status,Value=approved \
    Key=ec2:SharedTag/os-version,Value=al2023-2026.09 \
    Key=team,Value=platform-engineering

You can also apply shared tags when you create an AMI from the start, using the --tag-specifications parameter. For example:

aws ec2 create-image \
    --instance-id i-1234567890abcdef0 \
    --name "My server" \
    --tag-specifications "ResourceType=image,Tags=[{Key=ec2:SharedTag/publicTag,Value=text}]"

Listing all resources with shared tags (CLI)

To see which of your AMIs have shared tags, you can run the following CLI command:

aws ec2 describe-tags \
    --filters "Name=key,Values=ec2:SharedTag/*"

And get an output such as:

{
    "Tags": [
        {
            "Key": "ec2:SharedTag/status",
            "ResourceId": "ami-0abcdef1234567890",
            "ResourceType": "image",
            "Value": "approved"
        },
        {
            "Key": "ec2:SharedTag/os-version",
            "ResourceId": "ami-0abcdef1234567890",
            "ResourceType": "image",
            "Value": "al2023-2026.09"
        }
    ]
}

You can also receive a similar output using the describe-images command as shown in the following:

aws ec2 describe-images --filters "Name=tag-key,Values=ec2:SharedTag/*"

Stopping tag sharing

To stop sharing a specific tag, delete the tag with the ec2:SharedTag/ prefix:

# Remove the shared tag
aws ec2 delete-tags \
    --resources ami-0abcdef1234567890 \
    --tags Key=ec2:SharedTag/status

If you want to keep the tag private, recreate it without the prefix:

# Optionally, recreate as a private tag
aws ec2 create-tags \
    --resources ami-0abcdef1234567890 \
    --tags Key=status,Value=approved

Viewing shared tags as a recipient

When an AMI is shared with your account, you can see the shared tags alongside any tags you’ve added yourself. Figure 1 shows an example of an AMI shared from the original account with both private and shared tags. Figure 2 shows that AMI in the recipient account. The ec2:SharedTag/ prefix in the key name makes it clear which tags came from the owner.

Amazon EC2 console in the owner account showing an AMI with both private tags and ec2:SharedTag/ prefixed shared tags

Figure 1: AMI tags in the originating account, showing both private and shared tags

Amazon EC2 console in the recipient account showing only the shared tags on the same AMI, with the private tags absent

Figure 2: The same AMI in the recipient account, where only the shared tags are visible

As you can see in the preceding figures, the shared tags are visible across both accounts. However, the private tags created in the original account are only visible in the original account.

A common use case: Golden AMIs

Many organizations maintain a central build factory account that produces hardened, security-approved AMIs. These golden images can be shared with hundreds or thousands of workload accounts across an organization. Tags like status=approved or patch-date=2026-09-18 are commonly used by recipient accounts to enforce policies that only allow launches from approved images.

Before tag sharing, the build factory had to replicate tags to every account the AMI is shared with after each AMI publish. A typical workflow looked like this:

  1. The build factory creates a new AMI and tags it.
  2. An SNS notification triggers a Lambda function in each account.
  3. Each Lambda function reads the tag values from the AWS SNS message and calls CreateTags on the shared AMI in its own account.

Because this runs for every AMI, in every Region, across every account, the number of CreateTags API calls can grow rapidly in concentrated bursts. At that scale, teams risk throttling, failed Lambda invocations, and tag drift when calls silently fail.

With tag sharing, the build factory tags the AMI with the ec2:SharedTag/ prefix. Every account the AMI is shared with sees these tags, without the need for additional pipelines to replicate the tags. When the build factory updates a tag (for example, marking an older image as deprecated), the change is visible across all accounts without any additional API calls.

Considerations

Shared tags are visible to every account with access. We recommend that you do not include personally identifying, confidential, or sensitive information in these tags.

If consuming accounts have ABAC policies that evaluate tags, those policies will also apply to the shared AMI. For example, if a recipient account has a policy that allows ec2:RunInstances only when the AMI has the tag status=approved, and you share that tag by using ec2:SharedTag/status=approved, the policy will deny it.

When you adopt the shared tags model, any action that depends on those tags is controlled by the AMI owner, not by the recipient account. This includes tag-gated instance launches, AWS Identity and Access Management (IAM) and ABAC policy evaluations, and any downstream automation that reads tag values. Because only the owner can create, modify, or delete shared tags, the recipient account cannot change the values its own policies depend on. Before you rely on a shared tag in a policy or workflow, make sure you trust the owner account to set and maintain that value.

As a best practice, apply account-level governance so that you only consume AMIs from providers you trust. With Allowed AMIs, you can define criteria for which AMIs are allowed in your account, for example a list of trusted AMI provider account IDs. Only AMIs that meet the criteria are discoverable and available to launch in your account. To preview the impact before enforcing, start in audit mode.

Conclusion

EC2 Tag Sharing removes the need to build and maintain cross-account tag replication workflows. For organizations that share AMIs, this means less undifferentiated heavy lifting. To get started, add a tag with the ec2:SharedTag/ prefix to a shared AMI in the AWS EC2 console. To learn more, see Tag your AWS EC2 resources in the Amazon EC2 User Guide.

Restart EC2 and on-premises fleets faster with AWS CodeDeploy RESTART deployment mode

Post Syndicated from Iskandar Anvarov original https://aws.amazon.com/blogs/devops/restart-ec2-and-on-premises-fleets-faster-with-aws-codedeploy-restart-deployment-mode/

Operators often restart fleets when they need to pick up runtime configuration changes, recover unhealthy processes, or return hosts to a known state. Before RESTART, AWS CodeDeploy customers either redeployed the current revision, repeating completed work, or ran custom scripts outside of CodeDeploy production safeguards. Now there’s a purpose-built option: RESTART deployment mode. It keeps the operation inside CodeDeploy, so you get the same batch sizing, health checks, alarm monitoring, and rollback behavior as a standard deployment.

This new option reapplies the last successful revision to Amazon Elastic Compute Cloud (Amazon EC2) and on-premises in-place deployments. It retains your deployment configurations, lifecycle hooks, Amazon CloudWatch alarm monitoring, rollback settings, and deployment history.

In testing, a fleet restart completed up to 6.1x faster than a standard deployment, turning a multi-minute rollout into tens of seconds.

In this post, we explain how RESTART works, walk through starting a restart deployment from the command line and the CodeDeploy console, and review the safety controls that carry over from a standard deployment.

Why use a CodeDeploy restart?

A restart changes production even when the application revision stays the same. Hosts stop and start, failures reduce fleet capacity, and external configuration errors spread across restarted hosts. Fleet scripts must recreate batch sizing, minimum healthy capacity, validation, alarm monitoring, and audit history. One customer experienced this firsthand. Their restart script restarted hosts in batches with no health checks in between. When a bad configuration left the first batch unable to start, the script never noticed and continued. What should have been a routine restart took down their fleet.

This new deployment mode keeps the operation in CodeDeploy. You configure how the deployment runs with the familiar CreateDeployment API. CodeDeploy still runs your lifecycle hooks, validates the result, and stops after a health or alarm breach. A failed restart stays within the active batch instead of continuing through the fleet.

RESTART also works well as a building block for automated remediation. Point a CloudWatch alarm on a memory or resource-utilization metric at an Amazon EventBridge rule, and have that rule invoke an AWS Lambda function that calls CreateDeployment with deploymentMode: RESTART. Long-running or stateful workloads benefit from periodic recycling. Examples include self-managed Kafka brokers and JVM services with memory growth. This turns that recycling into a managed, self-healing loop instead of a cron job or a manual restart. The following diagram shows the flow:

CloudWatch alarm triggering an Amazon EventBridge rule that invokes a Lambda function to call CreateDeployment in RESTART mode

Figure 1: Automated remediation loop using an Amazon CloudWatch alarm, an Amazon EventBridge rule, and an AWS Lambda function to trigger a RESTART deployment

How RESTART works

Set deploymentMode to RESTART on CreateDeployment and provide no revision. CodeDeploy resolves the deployment group’s last successful revision and creates a deployment record.

Each selected host runs:

ApplicationStop → DownloadBundle → BeforeInstall → Install → AfterInstall → ApplicationStart → ValidateService

CodeDeploy agents that support local reuse (from version 2.1.0 onward) use the previous deployment’s archive. DownloadBundle remains in the signed workflow. If the archive is unavailable or invalid, the agent downloads the same pinned revision, covering added or replaced hosts.

Install reapplies the revision and corrects drift in managed files. BeforeInstall, AfterInstall, and ValidateService run as defined in the AppSpec file. Local reuse removes the network transfer, and end-to-end savings vary with revision size, agent version, hooks, fleet size, and deployment configuration.

Performance in feature testing

Feature tests peaked at 6.14x. Tests used m5.large instances, a 1 GB revision, and 10, 50, and 100-host fleets with and without an Application Load Balancer (ALB). Each scenario ran seven times. We discarded the minimum and maximum and report the median of five. Results combine local reuse with omitted traffic control and are not predictive of results for other applications.

At 75 percent minimum healthy, four or five waves saved ALB-backed fleets 397.1 to 424.8 seconds. The 50-host fleet reached 6.14x.

Host Count ALB in Front? Rollout Waves Standard Deployment Time Restart Deployment Time Speedup Time Saved
10 Yes 5 526.2s 129.1s 4.08x 397.1s
50 Yes 5 507.4s 82.6s 6.14x 424.8s
100 Yes 4 544.5s 144.7s 3.76x 399.8s
10 No 5 192.4s 167.8s 1.15x 24.6s
50 No 5 190.5s 117.2s 1.63x 73.3s
100 No 4 260.8s 139.6s 1.87x 121.2s

With two-wave HalfAtATime, ALB-backed fleets saved 168.6–185.7 seconds. No-ALB rows isolate local reuse.

Host Count ALB in Front? Standard Deployment Time Restart Deployment Time Speedup Time Saved
10 Yes 230.2s 61.6s 3.74x 168.6s
50 Yes 276.2s 90.5s 3.05x 185.7s
100 Yes 275.2s 103.9s 2.65x 171.3s
10 No 133.8s 37.0s 3.62x 96.8s
50 No 152.4s 86.7s 1.76x 65.7s
100 No 162.7s 109.1s 1.49x 53.6s

Multi-wave ALB deployments repeated traffic control and saved the most time. These results carry a couple of safety implications to keep in mind.

The safety controls remain familiar

A RESTART deployment uses the controls already configured for the deployment group:

  • Deployment configuration: Use CodeDeployDefault.OneAtATime, CodeDeployDefault.HalfAtATime, CodeDeployDefault.AllAtOnce, or a custom minimum healthy host setting to bound concurrent restarts.
  • Lifecycle validation: CodeDeploy runs ValidateService on each host before it considers that host healthy.
  • CloudWatch alarms: CodeDeploy polls the alarms configured on the deployment group and stops the deployment when an alarm enters ALARM.
  • Automatic rollback: CodeDeploy applies the deployment group’s automatic rollback configuration for qualifying failures. A rollback can’t reverse an external configuration change, so correct the underlying configuration before retrying.
  • Deployment history: GetDeployment and ListDeployments expose the restart’s status, timestamps, revision, and result for monitoring and audit.

Traffic-control behavior

RESTART doesn’t run the load balancer BlockTraffic and AllowTraffic steps. The host remains registered while its application stops and starts. Treat this as an operational constraint, not as the reason to use RESTART.

For request-serving fleets, make ApplicationStop stop accepting new work and drain in-flight work before the process exits. Select a deployment configuration that preserves enough healthy capacity for the expected restart duration. Pull-based workers stop receiving work when the process stops, but their hooks still need to handle in-flight jobs safely.

Walk through a restart deployment

This section walks through starting a restart deployment from the command line and the CodeDeploy console, and then monitoring its progress.

Prerequisites

  • An existing Amazon EC2 or on-premises application and deployment group with at least one successful deployment.
  • An AWS Command Line Interface (AWS CLI) or SDK version that supports the deploymentMode request field.
  • CodeDeploy agent version 2.1.0 or newer on target hosts to benefit from local revision reuse. Earlier agents work but fall back to downloading the revision.

Start a restart deployment with the AWS CLI

The following command restarts the fleet with the deployment group’s default deployment configuration:

aws deploy create-deployment \
    --application-name MyApp \
    --deployment-group-name MyDeploymentGroup \
    --deployment-mode RESTART \
    --description "Restart processes after a runtime configuration update"

CodeDeploy returns a deployment ID:

{
  "deploymentId": "d-EXAMPLE123"
}

To restart one host at a time, provide a deployment configuration in the request:

aws deploy create-deployment \
    --application-name MyApp \
    --deployment-group-name MyDeploymentGroup \
    --deployment-mode RESTART \
    --deployment-config-name CodeDeployDefault.OneAtATime \
    --description "Restart one host at a time"

Omit --deployment-config-name to use the deployment group’s configured default.

Start a restart deployment on the console

You can also use this feature in the CodeDeploy console. Under Applications, select the deployment group you want to restart and choose Create deployment.

CodeDeploy console Applications page with a deployment group selected and the Create deployment button

Figure 2: Choosing Create deployment for a deployment group in the CodeDeploy console

For Deployment mode, select Restart, and configure any other settings or overrides on the page (the same options you would set with the AWS CLI).

CodeDeploy Create deployment page with Restart selected as the deployment mode

Figure 3: Selecting Restart as the deployment mode on the Create deployment page

Monitor the restart

You can see the status of the deployment on the console, or use the deployment ID with the GetDeployment API or CLI command. For example:

aws deploy get-deployment --deployment-id d-EXAMPLE123

The deployment moves through the standard Created, InProgress, and terminal states. If a lifecycle hook fails, the deployment breaches its minimum healthy host requirement, or a configured alarm enters ALARM, CodeDeploy stops the restart and applies the configured failure behavior.

You can distinguish restart deployments by the deploymentMode field in the GetDeployment API, or visually on the console under Deployment details.

CodeDeploy console Deployment details showing the deploymentMode field set to RESTART

Figure 4: Deployment details showing the deploymentMode field set to RESTART

Handle alarms during incident recovery

If an alarm is already in ALARM state, CreateDeployment still creates the deployment. CodeDeploy then stops it when the deployment workflow observes that alarm during polling. The ignorePollAlarmFailure setting does not ignore an alarm in ALARM. It only controls behavior when CodeDeploy cannot retrieve alarm state.

If an approved incident runbook requires a restart despite the current alarm state, override alarm monitoring for that deployment:

aws deploy create-deployment \
    --application-name MyApp \
    --deployment-group-name MyDeploymentGroup \
    --deployment-mode RESTART \
    --override-alarm-configuration enabled=false \
    --description "Restart under an approved incident runbook"

This override disables all deployment-group alarms for that deployment. It requires codedeploy:UpdateDeploymentGroup in addition to the permission to create a deployment. Use it only when your incident process provides another health signal and explicitly authorizes the override. The deployment configuration and lifecycle validation continue to apply.

Request constraints

RESTART has the following constraints:

  1. It supports EC2 and on-premises in-place deployment groups. It doesn’t support Amazon Elastic Container Service (Amazon ECS) or AWS Lambda deployment groups.
  2. The deployment group must have a successful revision for CodeDeploy to reapply.
  3. Don’t provide revision, s3Location, gitHubLocation, or deploymentRevisions. CodeDeploy resolves the revision from deployment history.
  4. Don’t combine RESTART with updateOutdatedInstancesOnly. That option selects hosts that aren’t running the target revision, which conflicts with restarting hosts on the current successful revision.

For exact request validation and error types, see the CreateDeployment API reference.

Conclusion

Restarting a fleet is routine, but it still changes production availability and exposes configuration or process failures. RESTART gives the operation a first-class CodeDeploy path instead of requiring a separate fleet script.

Set deploymentMode to RESTART to reapply the deployment group’s last successful revision. CodeDeploy controls the batch size, runs the lifecycle hooks, validates each host, monitors configured alarms, records the result, and reuses the local revision archive when possible. The operation is faster when the archive is already present, while hosts that need to download it use the normal fallback path.

To get started, review the AWS CodeDeploy documentation and create-deployment CLI reference.

 


About the authors

Iskandar Anvarov

Iskandar Anvarov

Iskandar is a software engineer with over 10 years of experience across enterprise software and cloud computing. For the past 4 years, he’s been with Amazon Web Services, building developer tools and cloud infrastructure that support customers at scale. He holds a bachelor’s degree in Business Information Systems from Westminster University in Tashkent.

Filip Danić

Filip Danić

Filip is a software developer with 12 years of experience across service agencies, startups, and enterprise. For the past 5 years he’s been at Amazon, currently a Software Developer Engineer on AWS CodeDeploy. He previously led an internal finance systems team supporting compliance and reporting at scale. Filip holds a bachelor’s degree in Computer Science from the School of Computing (Računarski Fakultet) in Belgrade.

Monika Awasthi

Monika Awasthi

Monika is a Technical Product Manager with over 10 years of experience launching and scaling products across countries and functions. At Amazon Web Services, she builds developer tools that help customers ship software safely and efficiently, drawing on a technical background spanning networking, data analytics, and product management. She holds an engineering degree in Electronics and Communication and an MBA from INSEAD, France.

Daz Akbarov

Daz Akbarov

Daz is a Solutions Architect at Amazon Web Services, based in Munich. For the past 2 years at AWS, he has worked with customers across Central Asia and the Caucasus in Financial Services Industry, helping them migrate to and build on AWS. He focuses on modernization to cloud-native architectures, building serverless applications and enhancing developers’ experience. He has over 7 years of experience in enterprises and startups, including launching (and failing) his own one.

Deploy open source Regional availability tools in your VPC

Post Syndicated from Stefan Janjic original https://aws.amazon.com/blogs/architecture/deploy-open-source-regional-availability-tools-in-your-vpc/

When you build multi-Region architectures on AWS, one question comes up early: “What services are available in each AWS Region?” The answer shapes architecture decisions, from which AWS Regions to expand into, to how you design for resilience. For teams navigating data residency requirements and compliance reporting, getting the answer wrong has real consequences: deployment failures, compliance gaps, and delayed launches.

AWS publishes Regional availability data covering services, features, APIs, and AWS CloudFormation resource types across all AWS Regions. You can explore this data on the AWS Capabilities by Region page, access it through Amazon Simple Storage Service (Amazon S3) for pipeline integration, or query it using the AWS Knowledge MCP server. These options work well for exploration and automation. However, teams told us they need this data deployed as infrastructure they own, refreshing on their schedule, inside their network, filtered to their workload. That means running availability data the same way you run the rest of your stack: in your Amazon Virtual Private Cloud (Amazon VPC), under your governance.

In this post, we introduce two open source solutions that deliver that ownership. Capability Insights for AWS deploys a Regional availability dashboard into your VPC that auto-refreshes every 24 hours. The dashboard, its API, and availability data all run in your own account, and the scheduled refresh makes the only call that leaves your account when it reads the dataset that AWS publishes. Workload Analysis scans your AWS CloudTrail logs and CloudFormation stacks, then narrows 200+ services to the 20–30 your account actually runs, significantly reducing the scope of a Regional gap analysis.

Together with the Capabilities by Region S3 Access Point, you now have three levels of control: consume from Amazon S3, deploy into your VPC, or filter to your workload. We walk through how to deploy each solution, run a workload analysis, and view personalized results during your Regional expansion planning. Whether you’re building multi-Region recovery strategies, standardizing compliance reporting, or accelerating expansion timelines, these solutions put the data and the decisions it drives inside your perimeter.

Figure 1 shows how Capability Insights for AWS integrates with the VPC and subnets in your existing AWS account. A client in the public subnet accesses the dashboard through an S3 gateway endpoint and calls the private API through an API Gateway VPC endpoint.

Capability Insights for AWS architecture showing a client in the public subnet reaching the dashboard through an S3 gateway endpoint and the private API through an API Gateway VPC endpoint

Figure 1: Capability Insights for AWS architecture, including private dashboard access and scheduled Regional availability data refreshes.

An Amazon EventBridge schedule invokes the data-fetch Lambda function every 24 hours. The data-fetch function runs outside your VPC, so it reads the S3 access point published by AWS over the AWS network. The function pulls Regional availability data from the Capabilities by Region S3 bucket published by AWS and writes it to the website bucket within your account. The dashboard application accesses the data within the website through the S3 gateway endpoint from your VPC. The API Lambda can also invoke the data-fetch function on demand through the Lambda VPC endpoint. The deployment assets bucket supplies the Lambda code during deployment only.

Prerequisites

To follow along, you need:

  • An AWS account with permissions to deploy the CloudFormation stacks, including permission to create named AWS Identity and Access Management (IAM) roles. At runtime, the roles created by the stacks use scoped permissions for Amazon S3 object read and write access, AWS CloudFormation read operations, Amazon Athena queries, AWS Glue and AWS Lake Formation catalog access, AWS Lambda invocation, and AWS Step Functions execution. For starting-point deployment policies, see the documentation folder in the repository.
  • AWS Command Line Interface (AWS CLI) installed and configured.
  • A VPC with DNS resolution and DNS hostnames enabled, one public subnet (routed to an internet gateway) for dashboard and API access, and one private subnet (no internet route) for the in-VPC Lambda function. Both subnets need a route to Amazon S3 through a gateway VPC endpoint.
  • Node.js v24.18.0 (includes npm and npx) for automated deployment.
  • An S3 bucket for deployment assets.
  • An active CloudTrail configuration where you store logs in an S3 bucket (required for Part 2).

Part 1: Deploy a self-hosted dashboard (Capability Insights for AWS)

Capability Insights for AWS deploys a searchable Regional availability dashboard into your own AWS account. The solution pulls data from the AWS Capabilities by Region S3 bucket, stores it inside your VPC, and serves it through a static website backed by Amazon API Gateway, AWS Lambda, and Amazon EventBridge.

If your organization has access to additional data sources beyond the public dataset, the solution incorporates those as well, giving you a unified view across all AWS partitions you have access to. To learn more about accessing additional data, work with your AWS representative.

The dashboard covers:

  • Services and features: availability status, expected launch dates, and expansion plans per Region.
  • API operations: individual API action availability per Region for each AWS service.
  • CloudFormation resource types: which resource types each Region supports.

You provide your own VPC, subnets, and S3 bucket so the solution integrates with your existing infrastructure and security controls.

Clone and install

git clone https://github.com/aws/capability-insights-for-aws.git
cd capability-insights-for-aws
npm install

Create a deployment assets bucket

Create an S3 bucket with public access blocked to store the Lambda code package during deployment. We recommend naming it capability-insights-assets-<ACCOUNT_ID>-<REGION>.

Deploy the stack

Automated deployment:

npm run deploy

The script builds all assets, prompts for parameters, deploys the CloudFormation stack, uploads the website, and triggers an initial data sync. You will be prompted for SourceFolders, a comma-separated list of data sources to pull from. The default is public.

To skip interactive prompts, pass all parameters as flags:

npm run deploy -- \
  --private-vpc-id vpc-0abc123 \
  --backend-subnet-id subnet-0abc123 \
  --api-access-subnet-id subnet-0def456 \
  --deployment-assets-bucket-name my-deploy-bucket \
  --source-access-point-arn arn:aws:s3:us-east-1:686591367145:accesspoint/aws-capabilities-public \
  --source-folders public
Flag Description
–private-vpc-id VPC ID with DNS resolution and DNS hostnames enabled
–backend-subnet-id Private subnet with no internet route. Needs a route to Amazon S3 through a gateway VPC endpoint.
–api-access-subnet-id Public subnet routed to an internet gateway (user access, API Gateway VPC endpoint)
–deployment-assets-bucket-name S3 bucket for deployment assets
–source-access-point-arn

Public Capabilities by Region S3 access point ARN published by AWS. Account ID 686591367145 identifies the account managed by AWS that hosts the public dataset. Use the ARN as shown.

For details on the public dataset, see Building automated AWS Regional availability checks with Amazon S3.

–source-folders Comma-separated data sources (default: public)

Manual deployment:

If your organization requires deploying with native AWS tooling only, download build-assets.zip from the latest release and follow these steps:

# Upload Lambda code
aws s3 cp lambda/lambdaAssets.zip s3://<DEPLOYMENT_ASSETS_BUCKET>/lambdaAssets.zip

# Deploy the stack
aws cloudformation deploy \
  --template-file template/capability-insights.template.json \
  --stack-name CapabilityInsightsForAWS \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
  --parameter-overrides \
  PrivateVpcId=<VPC_ID> \
  BackendSubnetId=<BACKEND_SUBNET_ID> \
  ApiAccessSubnetId=<API_ACCESS_SUBNET_ID> \
  DeploymentAssetsBucketName=<DEPLOYMENT_ASSETS_BUCKET> \
  DeploymentAssetsBucketApiLambdaFunctionCodeZipPath=lambdaAssets.zip \
  SourceAccessPointArn=arn:aws:s3:us-east-1:686591367145:accesspoint/aws-capabilities-public \
  SourceFolders=public

# Upload website assets
aws s3 sync website/ s3://capability-insights-website-<ACCOUNT_ID>-<REGION>/

# Trigger initial data sync
aws lambda invoke \
  --function-name CapabilityInsightsDataFetchLambda \
  --invocation-type Event /dev/null

Access the dashboard

The solution hosts the website on S3, accessible only from within your VPC:

http://capability-insights-website-<ACCOUNT_ID>-<REGION>.s3-website-<REGION>.amazonaws.com

Because the solution you deploy hosts the website without public access, you need connectivity to the VPC. Common options:

  • Existing VPN or AWS Direct Connect: use your organization’s existing connectivity.
  • AWS Client VPN: set up a Client VPN endpoint in the VPC.
  • EC2 instance with SOCKS proxy: SSH into an instance in the VPC and proxy browser traffic through it.

Explore the dashboard

Once connected, the dashboard provides:

  • Search and filter: find services by name across all Regions.
  • Expandable service details: select any service to see individual feature availability per Region.
  • Status indicators: Available, Planning, Not Expanding, and projected dates (for example, “2026 Q3”)
  • Export: download the current view as JSON or CSV for sharing or further analysis.
  • Settings: view last sync time and trigger manual data refreshes.

Figure 2 shows the Capability Insights for AWS dashboard and its controls for comparing service availability across Regions.

Capability Insights for AWS dashboard listing services with per-Region availability status indicators, summary counts, and search, filter, and export controls

Figure 2: Dashboard overview showing service availability across Regions with status indicators and export options.

Summary counts show coverage for services and features, API operations, CloudFormation resources, and Regions. Tabs switch between catalog views, while filtering, Region columns, status values, and the expand and export controls help you inspect and download availability data.

At this point you have a working dashboard inside your VPC, refreshing daily, with no external dependencies during normal operation.

Part 2 adds personalization by filtering this catalog to the specific services your account runs. You can view the workload analysis results through the dashboard or access them through dedicated API endpoints.

Part 2: Personalize the catalog with Workload Analysis

The catalog covers 200+ services across 35+ Regions. When you plan expansion to a new Region, you don’t need to evaluate all of them. You need to evaluate the 20–30 your account runs. Without that filter, a gap analysis is a multi-week project. With it, it’s a 30-minute review.

Workload Analysis deploys as an additive CloudFormation stack alongside the Capability Insights stack. The pipeline runs as an AWS Step Functions state machine with three stages:

  1. Parallel analyzers (two branches run in parallel):
    1. CloudTrail Analyzer: Creates an AWS Glue Data Catalog table pointing at your CloudTrail log bucket, then runs an Amazon Athena query that extracts distinct service/API/Region/account combinations from the last N days (configurable, default 30). This identifies services your account has called.
    2. CloudFormation Analyzer: Calls ListStacks and GetTemplate for every active stack in the account. Extracts AWS:: resource types and scalar property values (strings, numbers, booleans), maps them to service names, and records which stack contributed each resource. This identifies what your account deploys, not only what it calls.
  2. Usage Decorator runs after both analyzers complete. It reads the primary capability catalogs (products.json, apis.json, cfn_resources.json) from the website bucket, intersects them with the analyzer outputs, and writes personalized files back to the bucket for the dashboard to consume.
  3. Scheduled execution through an Amazon EventBridge rule triggers the state machine daily (configurable schedule expression), keeping personalized data fresh without manual intervention.

Figure 3 shows how the Workload Analysis components coordinate scheduled and on-demand analysis.

Workload Analysis architecture where an EventBridge schedule or API request starts a Step Functions state machine running the CloudTrail and CloudFormation analyzers in parallel, then a Usage Decorator writes personalized data to the website bucket

Figure 3: Workload Analysis infrastructure components.

An Amazon EventBridge schedule or an on-demand API request starts the AWS Step Functions state machine. The workflow runs two branches in parallel: the CloudTrail analyzer uses Amazon Athena with AWS Glue Data Catalog and AWS Lake Formation catalog access to query activity, while the CloudFormation analyzer inspects active stacks. After both branches complete, the Usage Decorator combines their results with the primary capability catalogs and writes personalized data to the Capability Insights website bucket for the dashboard and API to consume.

Deploy Workload Analysis

Verify that CloudTrail is active and your logs land in an S3 bucket. Then deploy with the usage analysis flag:

npm run deploy -- \
  --private-vpc-id vpc-0abc123 \
  --backend-subnet-id subnet-0abc123 \
  --api-access-subnet-id subnet-0def456 \
  --deployment-assets-bucket-name my-deploy-bucket \
  --source-access-point-arn arn:aws:s3:us-east-1:686591367145:accesspoint/aws-capabilities-public \
  --source-folders public \
  --enable-usage-analysis \
  --cloudtrail-bucket my-cloudtrail-logs-bucket

This deploys both the core Insights stack and the Usage Analysis stack, then wires them together so the API Lambda can trigger analysis and serve personalized results.

Additional flag Description
--enable-usage-analysis Enables the Workload Analysis pipeline
--cloudtrail-bucket S3 bucket containing your CloudTrail logs

A typical first run completes in 2–5 minutes depending on account size and the number of active CloudFormation stacks.

Retrieve API endpoint

Use the following command to retrieve the API base URL from the API configuration file on the dashboard bucket:

aws s3 cp "s3://capability-insights-website-$(aws sts get-caller-identity --query Account --output text)-$(aws configure get region)/api-config.json" - --no-progress

The command returns output similar to the following:

{"apiBaseUrl": "https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod"}

Use apiBaseUrl as the base URL for the dedicated API endpoints. You must run API requests from within the VPC because the API Gateway endpoint is private.

Run an analysis

Start an analysis by calling POST /analysis through the API Gateway endpoint:

{
  "scope": "account",
  "analyzers": ["cloudtrail", "cloudformation"],
  "analyzerParams": {
    "cloudtrail": {
      "bucket": "my-cloudtrail-logs-bucket",
      "daysToScan": 30
    }
  }
}

The Amazon EventBridge rule triggers subsequent runs daily. You can also trigger ad-hoc runs through the dashboard’s Settings page.

View personalized results

After an analysis completes, the dashboard toggles between the full catalog and your personalized My Stuff view, showing only services and resources your account uses.

Through the API:

GET /capabilities?usageFilter=combined&scope=account

The response contains filtered products, APIs, and CloudFormation resources with usage attribution, including which stacks deploy each resource type and which property configurations you use:

{
  "products": [
    {
      "productId": "amzn1.prod.abc123",
      "productName": "Amazon DynamoDB",
      "childProducts": [...],
      "regionalAvailability": { "us-east-1": "Available", "eu-west-1": "Available" }
    }
  ],
  "cfnResources": [
    {
      "serviceName": "DynamoDB",
      "resourceTypes": [
        {
          "resourceTypeName": "Table",
          "regionalAvailability": { ... },
          "usage": {
            "stacks": ["MyAppStack", "DataPipelineStack"],
            "properties": { "BillingMode": ["PAY_PER_REQUEST"] },
            "count": 2
          }
        }
      ]
    }
  ],
  "lastAnalyzedAt": "2026-05-19T08:00:00.000Z"
}

Practical example

Consider an account running a typical web application. Without Workload Analysis, the dashboard shows all 200+ services across 35 Regions. With the combined filter applied:

  • Before: 200+ services, thousands of features, hundreds of CloudFormation resource types.
  • After: 28 services your account uses, with per-stack attribution showing which CloudFormation stacks deploy each resource type.

The CloudFormation resource view goes deeper. For each resource type your stacks deploy, you can see which property configurations are in use and which stacks contributed them. For example, your AWS::EC2::Instance resources might show InstanceType: t3.medium from your application stack and InstanceType: m5.xlarge from your data processing stack, each traced back to its source.

This targeted view means your Regional expansion gap analysis focuses on the 28 services you care about rather than the full catalog. For example, if you plan to deploy into the Europe (Zurich) Region (eu-central-2), the dashboard shows which 28 services are available there, which have planned launch dates, and which have no roadmap entry yet. These results highlight service-availability gaps to consider during Regional expansion. From here, you can evaluate your options: wait for planned launches, architect around unavailable features, or evaluate a different target Region. You’re not evaluating 200+ services against a Region matrix. You’re scanning a personalized list where every row is something your stacks deploy.

Clean up

To remove the resources created in this walkthrough:

Workload Analysis (if deployed): Delete the Usage Analysis stack first:

aws cloudformation delete-stack --stack-name CapabilityInsightsUsageAnalysis
aws cloudformation wait stack-delete-complete --stack-name CapabilityInsightsUsageAnalysis

Self-hosted dashboard: Empty the website bucket (static assets, capability data, and usage analysis output), then delete the stack:

aws s3 rm s3://capability-insights-website-<ACCOUNT_ID>-<REGION> --recursive
aws cloudformation delete-stack --stack-name CapabilityInsightsForAWS

Optionally delete the deployment assets bucket you created during setup. This solution uses standard AWS service pricing for Lambda, S3, API Gateway, Athena, and Step Functions. There is no additional charge for the solution itself.

Automated cleanup: Run npm run teardown to remove both stacks and empty the website bucket in one step.

Conclusion

Knowing which services are available in your target Region is the first step in designing for resilience. You can’t build a multi-Region recovery strategy for services that aren’t there yet. With Capability Insights for AWS and Workload Analysis, you own that data inside your VPC, filtered to what your account runs, refreshing on your schedule.

Start here:

 


About the authors

Integrate AWS DevOps Agent with third-party tools using Amazon EventBridge

Post Syndicated from Toshihiro Furuno original https://aws.amazon.com/blogs/devops/integrate-aws-devops-agent-with-third-party-tools-using-amazon-eventbridge/

AWS DevOps Agent investigates operational issues and proposes likely root causes. Many teams, though, want to follow an investigation from where they already work: a ticket in Jira or ServiceNow, or a notification in PagerDuty. When an investigation stays inside the AWS DevOps Agent console, engineers move between tools, and the history of the work is spread across them. This makes the work harder to piece together later.

In this post, I use the AWS Cloud Development Kit (AWS CDK) to build a solution for this. It receives AWS DevOps Agent investigation events through Amazon EventBridge and processes them with AWS Lambda. The solution then creates and updates issues in Jira Cloud. I use Jira as the example, but the same pattern applies to other tools with an API.

Solution overview

This solution is an event-driven integration that starts from the investigation events that AWS DevOps Agent emits. When AWS DevOps Agent creates an investigation, it sends an event with the source aws.aidevops to Amazon EventBridge. An Amazon EventBridge rule matches detail-types that begin with Investigation (a prefix match) and invokes an AWS Lambda function. The Lambda function calls the Jira Cloud REST API: on Investigation Created it creates a new issue, and on every other investigation event it adds a comment to the existing issue.

To link an issue to an investigation, the solution uses Amazon DynamoDB. On Investigation Created, it stores the mapping between the investigation task_id and the Jira issue key in DynamoDB. For later events, it looks up the issue key from that mapping and appends a comment to the same issue. Jira connection details (base URL, user, API token, and project key) are stored in AWS Secrets Manager, so they stay out of the Lambda function’s environment variables and code.

With this design, you can add the integration without changing AWS DevOps Agent itself. Amazon EventBridge handles event delivery, so you add a rule and a target when you want another destination. The processing lives in Lambda, so replacing Jira with another tool keeps the change inside the function code.

Architecture diagram

The following diagram shows the path an investigation event takes from AWS DevOps Agent to Jira Cloud. The flow runs from the event to the created or updated issue without a manual step.

Event flow from AWS DevOps Agent through an Amazon EventBridge rule to an AWS Lambda function that calls the Jira Cloud REST API, using AWS Secrets Manager for credentials and Amazon DynamoDB for the task-to-issue mapping

Figure 1: Event flow from AWS DevOps Agent through Amazon EventBridge and AWS Lambda to Jira Cloud

The flow works as follows. AWS DevOps Agent emits an investigation event, and an Amazon EventBridge rule (prefix match on Investigation) captures it and invokes the AWS Lambda function (Jira Issue Creator). The Lambda function reads the Jira credentials from AWS Secrets Manager and stores or reads the mapping between the task_id and the issue key in Amazon DynamoDB. It then calls the Jira Cloud REST API v3 to create an issue or add a comment.

Walkthrough

The following steps deploy the sample and confirm the behavior. The code is available on GitHub (sample-aws-devops-agent-eventbridge-integration).

Prerequisites

Before you start, prepare the following. First, an AWS account with permissions to create Lambda, Amazon EventBridge, DynamoDB, Secrets Manager, AWS Identity and Access Management (IAM), and AWS CloudFormation resources. Second, a local development environment with the AWS Command Line Interface (AWS CLI) with configured credentials, the AWS CDK, and Node.js 18 or later. You also need an Agent Space in AWS DevOps Agent and a target Jira Cloud project for issue creation.

On the Jira side, create one API token. Create the token from your Atlassian account settings, and note the Jira base URL, the email address tied to the token, and the project key where issues are created. You store these values in Secrets Manager in the next step.

Step 1: Store the Jira credentials in Secrets Manager

First, store the Jira connection details in AWS Secrets Manager. The Lambda function reads the credentials from here, so no secret stays in the code or in a parameter. The following command stores the four values as a single secret.

export JIRA_BASE_URL="https://your-domain.atlassian.net"
export JIRA_USER_EMAIL="[email protected]"
export JIRA_API_TOKEN="your-api-token"
export JIRA_PROJECT_KEY="PROJ"
export SECRET_NAME="devops-agent-jira-credentials"

aws secretsmanager create-secret \
  --name ${SECRET_NAME} \
  --description "Jira credentials for DevOps Agent EventBridge integration" \
  --secret-string "{\"jiraBaseUrl\":\"${JIRA_BASE_URL}\",\"jiraUserEmail\":\"${JIRA_USER_EMAIL}\",\"jiraApiToken\":\"${JIRA_API_TOKEN}\",\"jiraProjectKey\":\"${JIRA_PROJECT_KEY}\"}"

When the command succeeds, it returns the Amazon Resource Name (ARN) of the secret. Note this ARN. You use it in the next step.

Step 2: Deploy with the AWS CDK

Get the repository and deploy the stack with the CDK. If this is the first time you use the CDK in this Region, run cdk bootstrap first. When you deploy, pass the ARN of the secret from Step 1 as a parameter.

cd cdk
npm install
npm run build
cdk deploy --parameters SecretArn=arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:devops-agent-jira-credentials-AbCdEf

This stack creates the Amazon EventBridge rule, the Lambda function, the DynamoDB table, and the related IAM roles. The Lambda function receives only the permission to read the specified secret and to read from and write to the DynamoDB table.

Step 3: Understand the investigation event structure

Knowing what the Lambda function receives makes it more straightforward to adapt the solution to other tools. AWS DevOps Agent sends an event each time the state of an investigation changes. The state moves from PENDING_START to IN_PROGRESS to COMPLETED, and arrives with the detail-types Investigation Created, Investigation In Progress, and Investigation Completed. Alongside these three, you handle Investigation Failed, Investigation Timed Out, Investigation Cancelled, and Investigation Priority Updated through the same mechanism.

The following is part of an Investigation Created event. The detail.metadata.task_id value uniquely identifies the investigation, and the solution uses it as the DynamoDB key.

{
  "detail-type": "Investigation Created",
  "source": "aws.aidevops",
  "detail": {
    "metadata": {
      "agent_space_id": "a1b2c3d4-...",
      "task_id": "f1e2d3c4-...",
      "execution_id": "exe-ops1-..."
    },
    "data": {
      "task_type": "INVESTIGATION",
      "priority": "MEDIUM",
      "status": "PENDING_START"
    }
  }
}

The Amazon EventBridge event does not include the investigation title or description. To fill in the summary and body of the issue, the Lambda function calls the AWS DevOps Agent GetBacklogTask API. The Investigation Completed event includes data.summary_record_id, which you use to retrieve the investigation summary.

Step 4: Confirm the behavior

When you start an investigation in AWS DevOps Agent, it emits an Investigation Created event, and the Lambda function creates an issue in Jira. The following screen shows an investigation starting in the AWS DevOps Agent console. The Investigation timeline shows the first event, where an Amazon CloudWatch alarm entered the ALARM state.

AWS DevOps Agent console showing an investigation starting, with the investigation timeline’s first event indicating an Amazon CloudWatch alarm in the ALARM state

Figure 2: An investigation starting in the AWS DevOps Agent console

At this point, a new issue is created on the Jira side. The following screen shows the Jira board, where a single issue created by AWS DevOps Agent (its summary begins with [DevOps Agent]) appears in the To Do column.

Jira board with a single issue created by AWS DevOps Agent in the To Do column

Figure 3: The new Jira issue in the To Do column

When you open the issue, the detail looks like the following. The description holds the investigation title and the alarm details, along with metadata such as task_id, execution_id, agent_space_id, and the status. The Lambda function assembles these from the Amazon EventBridge event and the GetBacklogTask API.

Jira issue detail showing the investigation title, alarm details, and metadata including task_id, execution_id, and agent_space_id

Figure 4: The Jira issue detail with investigation metadata

As the investigation progresses and the state changes, the Lambda function looks up the issue key in DynamoDB and adds a comment to the same issue. When the investigation completes, AWS DevOps Agent presents a root cause. The following screen shows the cause summarized as an intentionally failing Lambda function.

AWS DevOps Agent console showing a completed investigation with the root cause identified as an intentionally failing Lambda function

Figure 5: The completed investigation and its root cause in the AWS DevOps Agent console

At the same time, a comment with the investigation result is appended to the Jira issue. The comment in the following screen includes the Investigation Completed status and an Investigation Summary that covers the symptoms, findings, and root cause.

Jira issue comment showing the Investigation Completed status and an investigation summary of symptoms, findings, and root cause

Figure 6: The investigation result appended as a Jira comment

Through this flow, an engineer follows the investigation from start to finish by looking at Jira alone, without switching between the AWS DevOps Agent console and Jira.

Adapting to other tools

The same pattern applies to other tools with an API. What you change is mainly the body of the Lambda function. For ServiceNow, you replace the issue-creation call with the incident-creation API. For PagerDuty, you call its notification API instead of creating or updating an issue. The Amazon EventBridge rule and the DynamoDB mapping stay the same, so you don’t rebuild them for each destination. Storing credentials in Secrets Manager is also common across tools.

Clean up

When you finish testing, delete the resources you no longer need to avoid future charges. First, delete the CDK stack.

cd cdk
cdk destroy

Next, delete the Jira credentials stored in Secrets Manager.

aws secretsmanager delete-secret \
  --secret-id devops-agent-jira-credentials \
  --force-delete-without-recovery

The DynamoDB table and the Lambda function are created as part of the CDK stack, so deleting the stack removes them as well.

Conclusion

In this post, I used the AWS CDK to build a solution for connecting AWS DevOps Agent to Jira Cloud. It receives investigation events through Amazon EventBridge, processes them with AWS Lambda, and creates and updates issues in Jira. Because you follow the investigation from the Jira side, engineers keep working inside the tools they already use. By storing credentials in AWS Secrets Manager and mapping investigations to issues in Amazon DynamoDB, you keep each state change on the same issue. The same design applies to other tools with an API, such as ServiceNow and PagerDuty.

Deploy the GitHub sample (sample-aws-devops-agent-eventbridge-integration) to your own account and try it until an investigation event reaches Jira. To learn more about AWS DevOps Agent itself, see the service detail page and the launch post Announcing General Availability of AWS DevOps Agent. To learn more about the Amazon EventBridge integration, see the AWS documentation (Integrating AWS DevOps Agent with Amazon EventBridge and AWS DevOps Agent events detail reference).

 


About the author

Toshihiro Furuno

Toshihiro Furuno

Toshihiro is a Senior Cloud Support Engineer on the AWS Deployment Support team. Toshihiro is passionate about helping customers use containers and continuous integration and continuous delivery (CI/CD). Outside of work, Toshihiro enjoys spending time with family.

How Mirelo AI brought sound design to the IDE with MCP and Kiro powers

Post Syndicated from Florian Breton original https://aws.amazon.com/blogs/devops/how-mirelo-ai-brought-sound-design-to-the-ide-with-mcp-and-kiro-powers/

Mirelo AI set out to fix how sound design works in the integrated development environment (IDE). For most developers, sound design has always meant leaving the IDE: opening a browser, digging through stock libraries, and trimming and syncing clips by hand. Sound is the last creative layer most developers reach, and the one they most often get wrong without specialist help. For teams building games, apps, and interactive products, digital audio workstations (DAWs) live outside the development workflow, so most ship with placeholder audio or nothing at all.

Mirelo AI, a Europe-based generative AI lab, builds models that turn a text prompt or a video clip into production-ready sound effects, synced to the picture when video is provided. Feed a video clip to the model and it returns audio matched to the action on screen. Mirelo built a hosted server on the Model Context Protocol (MCP), the open standard that lets AI assistants discover and call external tools. With Mirelo’s hosted MCP server, developers can reach those models from the tools they already use.

In this post, we describe how that MCP server became a power in Kiro, the agentic development environment from AWS. We also cover how Mirelo’s AWS Enterprise Support account team helped bring it to the Kiro powers marketplace. The result: Developers using Kiro can generate sound design from a natural-language prompt without leaving their editor.

Solution overview

With a Kiro power, you get Mirelo’s hosted MCP server bundled with Agent Skills that tell the AI assistant when and how to use it. When a developer’s prompt mentions sound, audio, or effects, Kiro activates the power, connects to Mirelo’s server, and loads its tools into the conversation. The developer describes the sound they want. Kiro calls Mirelo’s models and returns a finished audio file into the project.

The design has three parts:

  • Mirelo’s audio models, exposed through their existing HTTP API.
  • The hosted MCP server (https://mcp.mirelo.ai/mcp), which wraps that API as callable tools.
  • The Kiro power, which bundles the MCP configuration with an Agent Skill and publishes it to the marketplace for one-action install.

The problem: Sound is the last mile of creative development

Visual assets have mature tooling inside IDEs and design systems. Audio does not. A game developer prototyping a level generates textures, writes shaders, and tests physics in the editor. But the moment they need a matching footstep sound, the flow breaks. They open a browser, search a stock library, download candidates, trim them to length, and manually sync timing.

Content creators working with generative video hit the same wall from the other side. AI models now produce visual content in seconds, but each clip ships silent, so adding sound means switching tools, breaking flow, and spending more time on audio than the video itself took to create.

Mirelo’s thesis: Sound generation should live where developers already work, not in a separate application.

From API to agent tool: The Mirelo MCP server

Mirelo already had an API powering their Studio product. The question was how to make those capabilities reachable inside AI-powered development environments without asking developers to write integration code.

MCP answers that. By wrapping their API as an MCP server, Mirelo exposed their full audio generation pipeline to any compatible AI assistant. The Mirelo MCP is hosted and remote: developers add a single URL, authenticate through their browser, and start generating audio from conversation. There’s no local install and no API key to manage.

The server covers the full sound design loop:

  • Generate from text: describe a sound effect in natural language and receive a finished audio file.
  • Generate from video: pass a video clip and get synced sound effects for every action.
  • Extend: lengthen an audio clip that is too short for the scene.
  • Inpaint: replace a selected region of a clip while leaving the rest untouched.

A preflight tool estimates credits and runtime before generation runs, which matters when an agent works through a batch of files rather than a single effect.

Comparing direct API access with a Kiro power

Without the power, a developer who wants a sound effect works against the API directly. For anything longer than a short clip, that means the asynchronous path: submit the job, get a job ID back, poll for status, then download the result once it completes. A minimal version looks like this:

# 1. Submit the job
# The code samples in this post are provided for demonstration and educational purposes only and
# are not intended for production use without additional security review and testing. In
# particular, store API keys in a secrets manager rather than inline, and add error handling and
# retry limits before deploying.
JOB=$(curl -s https://api.mirelo.ai/v2/text-to-sfx/v1.6/jobs \
  --request POST \
  --header 'Authorization: Bearer sk-<your-api-key>' \
  --header 'Content-Type: application/json' \
  --data '{
    "prompt": "Heavy rain on a metal roof with distant thunder",
    "duration_ms": 45000,
    "output_format": "mp3"
  }' | jq -r '.job_id')

# 2. Poll until the job leaves the "processing" state
while true; do
  STATUS=$(curl -s "https://api.mirelo.ai/v2/text-to-sfx/v1.6/jobs/$JOB" \
    --header 'Authorization: Bearer sk-<your-api-key>' | jq -r '.status')
  [ "$STATUS" = "succeeded" ] && break
  [ "$STATUS" = "failed" ] && echo "generation failed" && exit 1
  sleep 3
done

# 3. Fetch the result URL and download the clip
curl -s "https://api.mirelo.ai/v2/text-to-sfx/v1.6/jobs/$JOB" \
  --header 'Authorization: Bearer sk-<your-api-key>' \
  | jq -r '.result_urls[0]' \
  | xargs curl -o rain.mp3

This works, but the developer owns every step. They store the API key, choose the sync endpoint for short clips and the async endpoint for longer ones, and call preflight to estimate credits. They also run the poll loop with sensible backoff, handle the failure state, download the result, and retry on transient errors. Each surface, whether a web app, a game editor, or a batch script, reimplements the same glue.

With the power, the developer describes the sound and the agent assembles that same request. Kiro authenticates through browser sign-in and reads the tool schema Mirelo published. It fills in prompt, duration_ms, and output_format from the conversation, runs the preflight tool when the job is large, and waits on the async job when generation runs long. The developer writes:

Generate 45 seconds of heavy rain on a metal roof with distant thunder, as an mp3.

The file is added to the project. The underlying API call is the same. What changes is who assembles and operates it.

The following table compares the two paths:

Concern Direct API Kiro power/MCP tool call
Authentication Store and send Bearer sk-… on every call Browser sign-in, managed by Kiro
Cost check Call /preflight yourself Agent calls the preflight tool when the job warrants it
Sync compared to async You choose the endpoint and implement polling Agent selects based on job size
Response handling Parse result_urls and download Agent returns the file into the project
Reuse across tools Reimplement the glue per surface One power, available in any Kiro session

Why package it as a Kiro power

Mirelo’s MCP server already worked in several AI assistants. With a Kiro power, you get discoverability, so you find the integration while browsing the marketplace, and automatic activation, so there is no URL to paste or configuration to write.

A power bundles an MCP server with Agent Skills, the structured instructions that guide an AI assistant through a specific workflow. The AI assistant learns what tools exist and when to reach for them. Just as important is how a power loads. A traditional MCP setup registers every tool definition upfront. Connecting a handful of servers can burn tens of thousands of tokens, a large share of the context window, before your first prompt. Kiro powers load dynamically instead. Installed powers sit dormant until your conversation mentions relevant keywords, at which point Kiro activates only that power’s tools and skills and deactivates them when you move on. Skills load the same way, on-demand, so the AI assistant pulls in a specific workflow’s instructions only when it’s working on that task. The result is near-zero baseline context cost and a Mirelo integration that surfaces its sound-design tools exactly when they’re needed, without crowding out the rest of your work.

The structure of a power is small. The following layout shows the three files that define it:

example-power/
|-- plugin.json          # Manifest: name, keywords, and metadata
|-- mcp.json             # Remote MCP server configuration
+-- skills/
    +-- sound-design/
        +-- SKILL.md     # Guides the agent through audio workflows

The plugin.json manifest declares the keywords that trigger activation. The mcp.json file points to the provider’s hosted server. The skill teaches the agent the difference between generating a one-shot effect and sound-designing an entire sequence.

The path from idea to marketplace

The Kiro powers connection came from Mirelo’s AWS Enterprise Support account team. The team spotted the fit between Mirelo’s MCP server and the powers marketplace. They built a proof-of-concept power to show how the integration would work and connected Mirelo with the submission process. Mirelo then packaged their official hosted server as the published power.

From the first conversation to a live power took about two weeks, most of it marketplace review. The engineering itself fit into a single afternoon. The impact is easiest to see in the developer’s workflow. Finding a single sound effect that matches the video is slow, manual work. That includes searching a stock library, auditioning candidates, trimming, and syncing. With the power, a single prompt returns a usable, synced clip, replacing a lengthy manual workflow with one step.

What developers can do with it

After the Mirelo power is active, sound design becomes part of the conversation. A developer polishing a web app might ask:

Generate a soft, satisfying click for this submit button. Short, no metallic ring.

On a game prototype, the request could be:

Here’s my gameplay clip. Generate footstep and impact sounds that match the character’s movement.

For a video project that needs a longer bed:

Extend this forest ambience to forty-five seconds so it covers the full scene transition.

And to fix a single moment:

The glass-break sound at 0:03 is too harsh. Inpaint that region with something more subtle, like thin crystal.

Each request calls Mirelo’s models and returns audio ready to use, without the developer leaving the editor.

A distribution channel for AI model companies

For Mirelo, the Kiro powers marketplace is a new kind of distribution. API businesses have historically reached developers through documentation sites, SDKs, and marketing. A power puts the capability inside the tool developers already use. Developers reach it by intent rather than by integration work.

The model fits AI services that augment creative workflows. Developers don’t plan to use a sound API the way they plan to use a database. They need sound the moment they realize their project is silent. A power meets them at that point of intent.

Conclusion

In this post, we described how Mirelo AI turned a hosted MCP server into a Kiro power, and how AWS Enterprise Support helped move it into the marketplace. For developers, sound design is now a prompt away inside Kiro. For AI model companies, the same path turns an existing MCP server into a distribution channel that reaches developers at the point of intent.

To get started:


About the authors

Florian Breton

Florian Breton

Florian is a Technical Account Manager (TAM) at AWS Enterprise Support based in EMEA, where he helps generative AI startups run and scale their workloads on AWS. Outside of direct customer work, he builds internal AWS tooling and contributes to open source projects that improve the AWS customer experience.

David Kernert

David Kernert

David is a former engineer at AWS, now working at Mirelo AI to scale the training and serving infrastructure behind Mirelo’s generative audio models.

Tofig Hasanov

Tofig Hasanov

Tofig is former Amazon software engineer, now working at Mirelo AI where he is working on building user facing products that expose Mirelo model capabilities, including the MCP.

Simplify AMI discovery with Amazon EC2 and SSM Parameter Store

Post Syndicated from Ashwani Tyagi original https://aws.amazon.com/blogs/compute/simplify-ami-discovery-with-amazon-ec2-and-ssm-parameter-store/

If you manage Amazon Elastic Compute Cloud (Amazon EC2) infrastructure at scale, you have likely encountered the following situation. You release an infrastructure change with the correct Region, the correct instance type, and a launch template that has operated reliably for months. The deployment nevertheless comes up on an Amazon Machine Image (AMI) that is several patch cycles out of date, because the AMI ID hardcoded in the template had become stale weeks earlier. The condition goes unnoticed until a security scan flags the instance, at which point you must reconcile AMI IDs across Regions rather than close out the week.

That scenario is rarely a one-time event. It is one example of a broader pattern that quietly taxes teams running Amazon EC2 at scale: stale AMI IDs, manual parameter lookups, inconsistent Region mappings, and pipelines that silently fail to update. The following section examines four variations of this pattern in detail.

The common thread across all of these is the same. Locating the correct image is not the hard part. The difficulty lies in wiring that image into your infrastructure as code (IaC) in a manner that remains current. You identify the appropriate AMI on the console, then search AWS Systems Manager (SSM) Parameter Store paths to obtain the dynamic reference that maps to it. The workflow spans two tools and two mental models, with a gap in between where errors accumulate. Because the authoritative link between an AMI and its SSM parameter lived outside the API, teams had to reconstruct it by hand, and hands make mistakes.

A recent enhancement to the Amazon EC2 DescribeImages API closes that gap. When you call DescribeImages on a public AMI, the response now contains a PublicSsmParameterName field: the SSM parameter that resolves to the latest AMI in that lineage. A single API call replaces manual correlation.

In this post, we examine the operational friction that makes AMI management harder than it should be and show how this enhancement addresses it. We walk through practical examples using the AWS Command Line Interface (AWS CLI), AWS CloudFormation, Terraform, and Amazon EC2 Auto Scaling launch templates. We conclude with best practices for golden AMI pipelines, including operational considerations to review before adopting the feature in production.

Prerequisites

To follow the examples in this post, you will need the following:

  • An AWS account.
  • The AWS CLI v2 installed and configured with appropriate permissions (ec2:DescribeImages, ssm:GetParameters).
  • Basic familiarity with AMIs, SSM Parameter Store, and at least one IaC tool (CloudFormation or Terraform).

Understanding the operational challenges

Before addressing the solution, it is worth examining the problem in detail, because the problem seldom manifests as a single, dramatic failure. It is instead a gradual accumulation of minor frictions that, in aggregate, impose a measurable cost on teams responsible for compute.

AMI IDs are Region-specific, version-specific, and change frequently. The workflow of finding an AMI, locating its SSM parameter, and referencing it in templates spans multiple tools, and the boundaries between steps are where errors accumulate.

Challenge 1: Silent image aging

Scenario: An engineer copies an AMI ID into a Terraform module as an interim measure. Several months later, that identifier is embedded across four environments. New instances launch on an image that predates numerous patches. There is no error and no alert, only drift that remains invisible until an audit or a review brings it to light.

Impact: Hardcoded AMI IDs do not fail conspicuously. They fail quietly, by launching a prior image at a later date. The distance between “this was correct when written” and “this remains correct” widens continuously, and no owner is assigned to monitor it.

Challenge 2: The multi-region maintenance burden

Scenario: An application operates across three Regions. The same logical image (for example, the latest Amazon Linux 2023) carries a different AMI ID in each Region. Templates therefore accrue region-to-AMI mapping blocks, lookup logic, or both. Each additional Region introduces another entry to maintain, and each AMI refresh requires updating all of them.

Impact: The team ends up maintaining a translation table that AWS already maintains on its behalf. The mapping logic becomes load-bearing infrastructure in its own right, and a single stale entry in one Region produces inconsistent fleets that are difficult to diagnose.

Challenge 3: Barriers to onboarding

Scenario: A new engineer joins the team and poses a reasonable question: which SSM parameter corresponds to a given AMI? The answer resides in an internal knowledge-base page that was accurate eighteen months earlier. The engineer copies a path that appears correct, deploys, and inadvertently references the wrong lineage.

Impact: When the relationship between an AMI and its parameter is not discoverable from the API, it must be documented manually. Manually maintained mappings degrade over time. Each new team member re-learns the same institutional knowledge, and each instance of degradation introduces an opportunity to reference an incorrect value.

Challenge 4: Uncertainty about update success

Scenario: A golden AMI pipeline completes a build and updates a parameter. The command returns a success response, and the team assumes the new image is in effect. However, for certain parameter data types, a success response does not always indicate that the value was accepted. This specific behavior is examined in the best-practices section, as it is particularly relevant to golden AMI pipelines.

Impact: Confidence without confirmation carries substantial risk. A pipeline that presumes success can propagate a stale image across a fleet before the discrepancy is identified.

Considered individually, none of these situations constitutes a crisis. Considered collectively, they explain why “launch the latest image” is never, in fact, a single step. The common root cause is consistent across all four: the authoritative link between an AMI and its SSM parameter existed outside the API, requiring teams to reconstruct it manually, a process inherently prone to error.

What’s new: DescribeImages returns the associated SSM parameter

The new feature addresses precisely this boundary.

As of July 16, 2026, the Amazon EC2 DescribeImages API response includes a new field, PublicSsmParameterName, for public AMIs that have an associated SSM parameter. This capability is available at no additional cost in supported AWS Regions, including AWS GovCloud (US) Regions and the China Regions.

In place of the previous three-step correlation exercise, the workflow reduces to a single call:

Before After
Find AMI → manually search SSM paths → confirm the correct match Find AMI → PublicSsmParameterName returns the SSM path immediately
Two separate API calls or console workflows A single DescribeImages call provides the complete mapping
Prone to mapping an incorrect parameter to an AMI Authoritative mapping obtained directly from the API

The change introduces neither a new service nor a new pricing dimension. It relocates information that previously lived in knowledge bases into the API response.

In addition, you can now use the public-ssm-parameter-name filter in DescribeImages to identify all AMIs associated with a specific SSM parameter, making the relationship queryable in either direction.

How it works: API response walkthrough

Call DescribeImages on a public AMI with an associated SSM parameter. The response includes PublicSsmParameterName:

{
  "Images": [
    {
      "ImageId": "ami-0abcdef1234567890",
      "Name": "al2023-ami-2023.7.20260601.0-kernel-6.1-arm64",
      "Description": "Amazon Linux 2023 AMI 2023.7.20260601.0 arm64 HVM kernel-6.1",
      "Architecture": "arm64",
      "PlatformDetails": "Linux/UNIX",
      "State": "available",
      "Public": true,
      "OwnerId": "111122223333",
      "ImageType": "machine",
      "RootDeviceType": "ebs",
      "VirtualizationType": "hvm",
      "EnaSupport": true,
      "PublicSsmParameterName": "aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
    }
  ]
}

The PublicSsmParameterName value (in this case, aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64) identifies the SSM parameter associated with this AMI lineage.

Tip: The field is returned under the aws/service/ namespace without a leading slash. When using this value in SSM API calls, resolve:ssm: references, or CloudFormation dynamic references, prepend a forward slash. For example, use /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64. The SSM parameter is intended to resolve to the latest AMI in the lineage, which can help you keep infrastructure current.

Note: Not every public AMI has an associated parameter. The field is present only for lineages for which AWS publishes parameters. The field is also populated only for public AMIs. If you query one of your own private AMIs and observe an empty field, this is expected behavior rather than a defect.

Practical examples

The following four examples show how to use the new PublicSsmParameterName field across common IaC tools.

Example 1: Discover the SSM parameter for an AMI using the AWS CLI

Suppose you have identified an AMI on the console and wish to determine its SSM parameter path for use in your templates:

aws ec2 describe-images \
  --image-ids ami-0abcdef1234567890 \
  --query "Images[0].PublicSsmParameterName" \
  --output text

Output:

aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64

You can also perform the inverse operation and determine which AMI a given SSM parameter currently references:

# Linux example
aws ssm get-parameter \
  --name "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64" \
  --query "Parameter.Value" \
  --output text

# Windows example
aws ssm get-parameter \
  --name "/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" \
  --query "Parameter.Value" \
  --output text

Tip: Public SSM parameters are available for both Linux (/aws/service/ami-amazon-linux-latest) and Windows (/aws/service/ami-windows-latest) AMIs. You can list all available parameters under these paths using aws ssm get-parameters-by-path –path .

Alternatively, you can use the new filter to identify AMIs by their SSM parameter name:

Note: The public-ssm-parameter-name filter returns all AMIs that have ever been associated with the specified parameter, including previous versions. Use sorting or additional filters (such as –query with CreationDate) to identify the most recent AMI.

aws ec2 describe-images \
  --filters "Name=public-ssm-parameter-name,Values=/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64" \
  --query "Images[].{ImageId:ImageId,Name:Name,CreationDate:CreationDate}" \
  --output table

Example 2: CloudFormation with dynamic SSM references

Once the SSM parameter path is known from DescribeImages, you can use CloudFormation dynamic references to resolve to the latest AMI at deployment time:

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance using SSM parameter for latest Amazon Linux 2023 AMI

Parameters:
  InstanceType:
    Type: String
    Default: t4g.micro

Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64}}'
      Tags:
        - Key: Name
          Value: MyLatestAL2023Instance

Alternatively, you can use the AWS::SSM::Parameter::Value parameter type to permit users to override the SSM path at stack creation time:

AWSTemplateFormatVersion: '2010-09-09'
Description: EC2 instance with configurable SSM-based AMI lookup

Parameters:
  AmiSsmParameter:
    Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64'
    Description: SSM parameter path for the AMI (discovered via DescribeImages)
  InstanceType:
    Type: String
    Default: t4g.micro

Resources:
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: !Ref AmiSsmParameter
      Tags:
        - Key: Name
          Value: DynamicAMIInstance

CloudFormation resolves the AMI ID at deployment time, so the template never contains a hardcoded AMI ID, and any stack update adopts the latest AMI automatically. Two considerations warrant attention before relying on this approach. First, running instances are not affected. A stack update is required to roll out a newer AMI. Second, CloudFormation does not support drift detection on dynamic references, so if the underlying SSM parameter value changes between deployments, CloudFormation will not report it as drift. For ssm dynamic references in which a version has not been pinned, AWS recommends performing a stack update whenever the parameter changes, so that the stack retrieves the current value.

Example 3: Terraform with SSM parameter data source

Use the aws_ssm_parameter data source to resolve the SSM path to the latest AMI ID:

# Use the SSM parameter path discovered from DescribeImages
data "aws_ssm_parameter" "latest_al2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64"
}

resource "aws_instance" "web" {
  ami           = data.aws_ssm_parameter.latest_al2023.value
  instance_type = "t4g.micro"

  tags = {
    Name = "LatestAL2023-Instance"
  }
}

Important: In Terraform, ami is a replacement-forcing argument on aws_instance. When the SSM parameter changes, Terraform proposes to destroy and recreate the instance. For stateful workloads, add lifecycle { ignore_changes = [ami] } or use launch templates with Auto Scaling (Example 4) instead.

Example 4: Auto Scaling launch templates with SSM parameters

For Auto Scaling groups, you can reference the SSM parameter directly in the launch template using the resolve:ssm: prefix:

# Create a launch template that uses the SSM parameter
aws ec2 create-launch-template \
  --launch-template-name al2023-auto-scaling \
  --launch-template-data '{
  "ImageId": "resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64",
  "InstanceType": "t4g.micro"
}'

When EC2 Auto Scaling launches a new instance, it resolves the SSM parameter at launch time to obtain the current AMI ID. You can verify the AMI ID to which a launch template resolves:

aws ec2 describe-launch-template-versions \
  --launch-template-name al2023-auto-scaling \
  --versions '$Latest' \
  --resolve-alias

The response shows the resolved ImageId:

{
  "LaunchTemplateVersions": [
    {
      "LaunchTemplateId": "lt-089c023a30example",
      "LaunchTemplateName": "al2023-auto-scaling",
      "VersionNumber": 1,
      "LaunchTemplateData": {
        "ImageId": "ami-0ac394d6a3example",
        "InstanceType": "t4g.micro"
      }
    }
  ]
}

The parameter is stored in the launch template. When the Auto Scaling group scales out or replaces an instance, it uses the launch template to resolve the SSM parameter and determine the AMI to launch. This is the most direct of the four patterns: the parameter serves as the single source of truth, and the Auto Scaling group’s normal instance lifecycle effects the rollout.

Before-and-after workflow comparison

The following table summarizes how this feature improves common workflows, and relates each entry to the challenges described earlier.

Workflow Before After
Discover the SSM path for a known AMI Search SSM parameter namespaces manually. Test multiple paths. Confirm a correct match A single DescribeImages call returns PublicSsmParameterName
Validate that an SSM parameter maps to the expected AMI Call GetParameter, then call DescribeImages on the returned ID to verify Use the public-ssm-parameter-name filter to view all associated AMIs directly
Set up IaC templates Find AMI → search for SSM path → copy path to template → verify correctness over time Find AMI → read PublicSsmParameterName from the response → use directly in the template
Onboard new team members Document AMI-to-parameter mappings in knowledge bases, which become stale New members self-discover using standard API calls
Audit AMI usage across teams Cross-reference AMI IDs with SSM parameters in separate calls A single API call provides the complete picture

Best practices: Using SSM parameters for golden AMI pipelines

The following recommendations describe how to derive the greatest benefit from this feature, with operational considerations identified where they are material.

1. Discontinue hardcoding AMI IDs

With PublicSsmParameterName removing the discovery barrier, switch all templates to SSM parameter references. Use {{resolve:ssm:}} in CloudFormation, the aws_ssm_parameter data source in Terraform (note the replacement behavior in Example 3), or the resolve:ssm: prefix in launch templates.

2. Create custom SSM parameters for your golden AMIs

For internally built golden AMIs, create your own SSM parameters using the aws:ec2:image data type:

aws ssm put-parameter \
  --name "/my-org/golden-ami/amazon-linux-hardened" \
  --type "String" \
  --data-type "aws:ec2:image" \
  --value "ami-0abcdef1234567890"

When the pipeline produces a new golden AMI, update the parameter:

aws ssm put-parameter \
  --name "/my-org/golden-ami/amazon-linux-hardened" \
  --type "String" \
  --data-type "aws:ec2:image" \
  --value "ami-0fedcba9876543210" \
  --overwrite

Stacks, launch templates, or Terraform configurations that reference this parameter can adopt the new AMI on their next deployment, with no template edits required in most cases.

Operational consideration: Because PutParameter validates aws:ec2:image values asynchronously, an HTTP 200 does not confirm the value was accepted. Subscribe to Parameter Store change events in Amazon EventBridge and confirm the operation succeeded before considering the rollout complete.

3. Use parameter versions and labels for controlled rollouts

SSM Parameter Store supports versioning and labels, which provide control over rollouts:

# Label the current production version
aws ssm label-parameter-version \
  --name "/my-org/golden-ami/amazon-linux-hardened" \
  --parameter-version 5 \
  --labels "prod"

Production launch templates reference the labeled version:

ImageId: resolve:ssm:/my-org/golden-ami/amazon-linux-hardened:prod

With this approach, you can update the parameter with a new AMI without immediately affecting production. Promotion to production is accomplished by moving the prod label, a deliberate, auditable action rather than an automatic side effect.

4. Combine with Amazon EC2 Image Builder for end-to-end automation

Use Amazon EC2 Image Builder to automate AMI creation, then configure the distribution settings to update your SSM parameter automatically when a new AMI is built. Combined with the new discovery feature, this establishes a closed loop:

  • Image Builder creates a new AMI on a schedule.
  • Distribution settings update the SSM parameter to point to the new AMI.
  • Auto Scaling and IaC resolve the parameter to the latest AMI at launch time.
  • With DescribeImages, any authorized party can determine which SSM parameter an AMI maps to.

5. Scope IAM permissions appropriately

Two permission requirements apply.

To launch instances by using SSM-referenced AMIs, the launching principal requires ssm:GetParameters on the relevant parameter paths:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:GetParameters",
      "Resource": "arn:aws:ssm:*:*:parameter/aws/service/ami-amazon-linux-latest/*"
    }
  ]
}

Scope the Resource element to the paths actually in use. If you reference Windows parameters (/aws/service/ami-windows-latest/) or your own golden AMI paths (/my-org/golden-ami/), include those ARNs as well. Otherwise, launches will fail with an AccessDenied error.

To create a custom aws:ec2:image parameter, the pipeline principal also requires ssm:PutParameter and ec2:DescribeImages:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:PutParameter",
      "Resource": "arn:aws:ssm:*:*:parameter/my-org/golden-ami/*"
    },
    {
      "Effect": "Allow",
      "Action": "ec2:DescribeImages",
      "Resource": "*"
    }
  ]
}

For broader guidance on keeping infrastructure current and automating operational processes, see the Operational Excellence Pillar of the AWS Well-Architected Framework.

Clean up

The examples in this post use read-only API calls (DescribeImages, GetParameter) and do not create billable resources. If you created a launch template while following Example 4, you can delete it as follows:

aws ec2 delete-launch-template --launch-template-name al2023-auto-scaling

Conclusion

The difficulty of AMI management was never attributable to any single failure. It arose from the steady accumulation of stale identifiers, region-mapping tables, stale documentation, and pipelines that presumed success, all of which are minor frictions that together produced significant operational effort and risk. The common thread was that the authoritative link between an AMI and its SSM parameter existed outside the API, requiring teams to reconstruct it manually.

The new PublicSsmParameterName field in the Amazon EC2 DescribeImages API relocates that link into the response, where it appropriately belongs. With a single API call, you can determine the SSM parameter for any public AMI. You can then reference it directly in CloudFormation templates, Terraform configurations, or Auto Scaling launch templates for automatic AMI updates.

To begin, call DescribeImages on any public AMI and examine the PublicSsmParameterName field. For further detail, see Reference the latest AMIs using Systems Manager public parameters in the Amazon EC2 User Guide.

For additional learning resources on AMI management and IaC on AWS, explore Amazon EC2, AWS Systems Manager Parameter Store, and Amazon EC2 Image Builder.

Improving Lambda function latency with scalable network bandwidth

Post Syndicated from Rahul Shandilya original https://aws.amazon.com/blogs/compute/improving-lambda-function-latency-with-scalable-network-bandwidth/

AWS Lambda now supports scalable network bandwidth for functions configured with 2,048 MB of memory or more, running outside of a virtual private cloud (VPC). Previously, sustained network throughput was capped at 625 Mbps regardless of your function’s memory configuration. Now, sustained throughput scales proportionally from 625 Mbps at configurations below 2,048 MB up to 3,000 Mbps at 10,240 MB, increasing the rate at which data moves to and from your execution environment.

In this post, you learn how to apply this new capability to latency-sensitive data processing workloads, helping reduce function execution times and per-invocation costs while improving the end-user experience through reduced latency. You also walk through a deployable implementation that demonstrates the performance improvements this capability unlocks.

Latency-sensitive data processing

Latency-sensitive data processing applications are data processing workloads that must be completed in a defined period of time. They often experience bursty, ad hoc traffic patterns while being required to download gigabytes or even terabytes of data from a data store, process it in a compute environment, and return a result to a waiting end user.

Latency-sensitive data processing is often highly parallelizable. Data can be divided into smaller pieces with each piece being individually processed before combining them together to obtain a result.

These workloads can be found in multiple industries and verticals. Examples include:

  • Log querying engines – An end user initiates an on-demand search across terabytes of log data and expects results within seconds.
  • Insurance underwriting – A prospective customer submits an application, triggering real-time evaluation of historical claims and risk data. The underwriting process determines what coverage and premiums to offer to the prospective customer.
  • Financial ETL pipelines – An economic announcement triggers an unexpected burst of market data that must be ingested, transformed, and made available to downstream trading systems before the next market tick.
  • Genomics platforms – A clinician orders a diagnostic test, requiring gigabytes of DNA or RNA sequencing data to pass through a bioinformatics pipeline and be compared against a reference genome while the patient awaits results.

These workloads are challenging to build on traditional compute clusters. Their spiky and unpredictable nature forces you to choose between under-provisioning compute to optimize costs (and risk missing your SLA) or over-provisioning and paying for idle capacity.

Why Lambda fits latency-sensitive data processing

Lambda eliminates this tradeoff. Instead of pre-provisioning a compute cluster, Lambda scales compute capacity in response to incoming requests, matching processing power to unpredictable traffic patterns. Because latency-sensitive data processing is highly parallelizable, the ability of Lambda to rapidly scale out execution environments makes it a natural fit. You can fan out across thousands of concurrent functions to process data in parallel, paying only for the compute you use.

However, as data volume and performance requirements grow, network bandwidth to and from the compute environment can become the limiting factor in minimizing workload latency.

Scalable network bandwidth directly addresses this limitation by raising the per-environment network throughput ceiling, improving the rate at which data can be transferred to and from the execution environment. Each execution environment can now drive up to 3,000 Mbps of sustained throughput when configured with 10,240 MB of memory, a 4.8x increase from the previous ceiling of 625 Mbps. Combined with the ability of Lambda to scale out at a rate of 1,000 execution environments every 10 seconds, you can download more than 3 TB of data in under 10 seconds.

New network throughput behavior for Lambda functions

Scalable network bandwidth applies to both data ingress to and egress from an execution environment for functions outside of a VPC. For functions configured with 2 GB of memory or more, network bandwidth scales by approximately 280 Mbps increments for every 1 GB of additional memory allocated.

The following table shows the maximum sustained bandwidth available to each execution environment at each memory configuration.

Memory Configuration Max Sustained Bandwidth
Less than 2,048 MB 625 Mbps
2,048 MB 765 Mbps
3,072 MB 1,044 Mbps
4,096 MB 1,324 Mbps
5,120 MB 1,603 Mbps
6,144 MB 1,883 Mbps
7,168 MB 2,162 Mbps
8,192 MB 2,441 Mbps
9,216 MB 2,721 Mbps
10,240 MB 3,000 Mbps (4.8x increase)

Table 1. Lambda sustained network bandwidth by memory configuration. Bandwidth scales at ~280 Mbps per additional GB of memory above 2 GB.

In the following section, you learn how scalable network bandwidth improves end-user latency by building an ETL pipeline that demonstrates it. You can find the source code in the GitHub repository.

Solution overview

Consider a SaaS analytics platform where users submit ad hoc queries against a data store. The application must extract the relevant data, apply a filter or transformation, and return an aggregate result while the user waits. In this example, the result needs to be returned in 8 seconds or less.

The following diagram illustrates the architecture of the solution.

ETL fan-out architecture: a client calls an orchestrator Lambda function, which fans out to multiple worker Lambda functions that read data in parallel from Amazon S3, with bandwidth scaling callouts for each memory tier.

Figure 1. ETL fan-out pattern: an orchestrator Lambda function distributes work to multiple worker Lambda functions that read from Amazon S3 in parallel, with bandwidth scaling callouts per memory tier.

A client initiates an ad hoc query by calling the orchestrator Lambda function through the Lambda API. The orchestrator function determines how to split the work. To process the data in parallel, the orchestrator function uses a ThreadPoolExecutor to issue synchronous invoke requests to the Lambda worker function, fanning out the worker across multiple execution environments at the same time.

Each Lambda worker function is configured with 10,240 MB of memory, so it has access to up to 3,000 Mbps of sustained network throughput. After the data is processed, the aggregated result is returned to the client.

Prerequisites

Before you start the deployment process, make sure that you have completed the following steps:

  1. Install the AWS SAM CLI on your computer and confirm that you are running Python 3.12 or later.
  2. Have your AWS account credentials ready.
  3. Submit a request to AWS Service Quotas to turn on scalable network bandwidth for your Lambda functions. This quota is listed under Network bandwidth per execution environment.

Clone the source code from the GitHub repo and deploy the application within your AWS account. Creating the 10 GB test dataset and running the benchmark can incur charges to your AWS account.

git clone https://github.com/aws-samples/sample-lambda-enhanced-bandwidth
cd sample-lambda-enhanced-bandwidth
sam build
sam deploy --guided

After the AWS CloudFormation stack is deployed, record the DataBucketName and orchestrator function name from the stack outputs to use in subsequent commands.

To simulate data for the end user to query, the GitHub repo has a script that creates 10 GB of synthetic data and uploads it to your S3 bucket.

Mode 1: Processing pre-partitioned data

In Mode 1, the 10 GB of synthetic data is pre-partitioned. Pre-partitioned data is typically produced incrementally by many sources over a period of time, which can be the case with IoT data or access logs. The following command creates 10 GB of data divided into 20 partitions that are 512 MB each.

python scripts/generate_data.py \
    --bucket <DATA_BUCKET_NAME> \
    --total-gb 10 \
    --chunk-mb 512

Turning on scalable network bandwidth does not, on its own, make your downloads faster. A single download request only opens one connection to Amazon S3, and one connection does not move data fast enough to fill all the bandwidth now available to your Lambda function. To actually use your full allotment of network bandwidth, the execution environment has to pull the data over several connections at once. It does this by preferring the AWS Common Runtime (CRT) transfer client, a high-performance download engine built into Boto3. When the worker calls download_fileobj, the CRT client automatically breaks the 512 MB object into smaller parts and downloads them in parallel across multiple Amazon S3 requests. Those parallel downloads are what let a single Lambda worker take advantage of its full network bandwidth.

The following command runs the benchmark on the pre-partitioned data.

python scripts/run_fanout_benchmark.py \
    --orchestrator-name <STACK_NAME>-orchestrator \
    --bucket <DATA_BUCKET_NAME> \
    --iterations 5

Mode 2: Processing single large objects

Mode 2 generates 10 GB of data in one large object. This arrangement is more common when data is produced or delivered as one complete unit, such as database backups or genomic datasets. The following command creates 10 GB of data in a single large object.

python scripts/generate_data.py \
    --bucket <DATA_BUCKET_NAME> \
    --single-object-gb 10 \
    --key large-object/large-file.bin

In Mode 1, the CRT preference applies to Boto3 managed transfer methods such as download_file and download_fileobj. Mode 2 takes a different approach. Each worker reads a specific byte range of a single large object using get_object. The CRT preference setting has no effect on these calls. Instead, you can control concurrency by explicitly tuning the number of Lambda workers and using a bounded ThreadPoolExecutor to issue multiple byte-range requests at the same time.

When you run the following command, the orchestrator takes the single large object and divides it into consecutive byte ranges of 512 MB each. Each of the individual ranges is then processed by a Lambda worker execution environment in parallel.

python scripts/run_fanout_benchmark.py \
    --orchestrator-name <STACK_NAME>-orchestrator \
    --bucket <DATA_BUCKET_NAME> \
    --key large-object/large-file.bin \
    --slice-size-mb 512 \
    --iterations 5

The benchmark reports wall-clock duration, client-observed duration, aggregate throughput across workers, worker completion counts, and target compliance. When comparing memory configurations, keep the code, dataset, AWS Region, partition count, warm-up policy, and measurement count identical. You should run the benchmark multiple times in your account because placement, cold starts, concurrency, S3 behavior, and execution-environment reuse could affect results.

Results

To compare results, we ran the benchmark using a baseline configuration where the worker Lambda function is configured with only 1,024 MB of memory, well below the 2,048 MB threshold required for scalable network bandwidth to take effect. The 1,024 MB configuration limits network throughput to the previous sustained ceiling of 625 Mbps.

In our baseline test run, a worker downloaded and processed a single 512 MB partition with a 6.61-second download time at a 649.8 Mbps throughput (at p50). The 649.8 Mbps throughput exceeds the 625 Mbps ceiling because Lambda is capable of bursts in network throughput over a short period of time. Across twenty measured fan-out queries, the complete 10 GB query was completed with a 7.113-second wall-clock at p50. This fits within the 8-second SLA but leaves very little headroom.

To run our scalable network bandwidth benchmark, we re-deployed our worker Lambda function with a 10,240 MB memory configuration and re-ran the application. At a 10,240 MB memory configuration, each execution environment can now access up to 3,000 Mbps in sustained throughput. Direct 512 MB downloads achieved a 1.70-second download time and 2,521.3 Mbps throughput (both at p50). The complete 10 GB query was completed with a 2.640-second wall-clock at p50. That is 2.69 times faster, or 62.9% lower median latency, than the 1,024 MB configuration.

Table 2 summarizes the direct worker and end-to-end fan-out measurements for the same 10 GB dataset and 20 × 512 MB orchestration pattern. Aggregate throughput is the total data transfer rate across all twenty execution environments spun up to run the benchmark.

Memory Configuration Single 512 MB partition download time and throughput (p50) 10 GB fan-out wall time (p50) Aggregate throughput p50
1,024 MB baseline tier (sustained 625 Mbps) 6.61s / 649.8 Mbps 7.113s 12.08 Gbps
10,240 MB scalable tier (up to 3,000 Mbps) 1.70s / 2,521.3 Mbps 2.640s 32.54 Gbps

Table 2. Measured 1,024 MB baseline tier and 10,240 MB scalable bandwidth performance for a 10 GB fan-out ETL query.

Using scalable network bandwidth, the customer’s SLA headroom has improved by nearly 5 seconds. The Lambda function can now handle larger partitions within the same SLA window, reducing costs while still remaining comfortably within the customer’s SLA.

Clean up

To clean up the resources you created for the benchmark test, run the following commands:

aws s3 rm s3://<DATA_BUCKET_NAME> --recursive
sam delete --stack-name <STACK_NAME>

Best practices

After scalable network bandwidth is turned on for your AWS account, the following practices help you get the most out of it.

Profiling and planning

  • Test before you tune. Not every function is network-bound. Before increasing memory, profile your function to confirm that network I/O is the primary contributor to invocation duration and not CPU or application logic. Use Amazon CloudWatch Lambda Insights to inspect rx_bytes, tx_bytes, and duration. Functions where network I/O dominates invocation time are prime candidates for tuning.
  • Design for parallelism. Break your data into parallelizable chunks that can be processed independently in a fan-out pattern across multiple execution environments. You can use Amazon S3 byte-range reads to split large files into independently downloadable partitions. For implementation details, see Downloading an object with part numbers in the Amazon S3 User Guide.
  • Run AWS Lambda Power Tuning. Lambda Power Tuning is a state machine that helps you optimize your Lambda functions for cost and performance. Use Power Tuning to sweep memory configurations from 1,024 MB to 10,240 MB and identify the optimal cost-vs-latency point for your workload.

Implementation

  • Check upstream and downstream limits. Check the throughput limits of your data sources. For example, a Lambda function running at 3,000 Mbps can exceed the throughput capacity of a single S3 prefix, which supports up to 5,500 GET requests per second. When this happens, you will see HTTP 503 (Slow Down) errors in your application logs. Distribute your S3 objects across multiple prefixes to parallelize reads and avoid per-prefix throttling.
  • Balance bandwidth and CPU. Lambda allocates CPU proportionally to memory. For example, at a 1.7 GB memory configuration you are allocated 1 vCPU while a 10 GB memory configuration is allocated up to 6 vCPU. If your function processes data in parallel threads, the higher memory tiers give you both more network bandwidth and more CPU to process it. Use the concurrent.futures module in Python or worker_threads in Node.js to process data across parallel threads and maximize both CPU and network utilization.
  • Turn on Amazon S3 CRT for Boto3. If your function uses the Python runtime, initialize your Amazon S3 client with preferred_transfer_client: 'crt' to maximize single-connection throughput. The AWS Common Runtime automatically parallelizes requests across multiple TCP connections, which matters because individual TCP connections have a throughput ceiling.
  • Use SnapStart for JVM workloads. If you use Lambda SnapStart for Java functions, scalable network bandwidth reduces afterRestore hook latency. Network activity that occurs during function restore, such as pre-warming connections or pre-fetching configuration data, can complete faster.

Conclusion

Scalable network bandwidth raises the per-environment sustained throughput ceiling of AWS Lambda from 625 Mbps to 3,000 Mbps, directly reducing end-to-end latency for data-intensive workloads. Combined with the Lambda scaling rate, you can now move terabytes of data in seconds, without provisioning or managing infrastructure.

To get started, request the Network bandwidth per execution environment quota increase through AWS Service Quotas and deploy the sample application from the GitHub repository to see the improvement firsthand.

How Moeve standardized dbt runs across data lakes with Amazon Athena

Post Syndicated from Rubén Romero Córdoba original https://aws.amazon.com/blogs/big-data/how-moeve-standardized-dbt-runs-across-data-lakes-with-amazon-athena/

As organizations grow, data processing often becomes fragmented across teams, environments, and orchestration tools. This fragmentation leads to inconsistent patterns, duplicated logic, limited cost visibility, and operational overhead.

At Moeve we were no exception. Our analytics teams build their transformations with dbt, an open source tool that defines transformations as SQL models, resolves the references between them, and works out the order in which they run. dbt describes what to transform, but it does not define where or how a project runs. We left that decision to each team, and as the number of projects grew we found fragmented pipelines, inconsistent compute engines, and limited cost visibility slowing every project down. Standardizing our dbt runs on Amazon Athena was how we worked our way out of that.

This post describes the architecture of the centralized, serverless solution we built on Athena, which reduced onboarding for a new dbt project from days to about 15 minutes. The solution centralizes how dbt runs across our data lakes while staying loosely coupled from orchestration. It uses Amazon Athena as the default processing engine, a centralized dbt launcher, and a shared event bus for downstream orchestration.

In the sections that follow we explain how we decoupled dbt runs from orchestration using AWS Step Functions and AWS Fargate, why Athena fits our workloads from a cost and operational perspective, how storing run parameters in Amazon DynamoDB rather than in pipeline code removed the infrastructure deployment step from onboarding, and how publishing results to Amazon EventBridge lets our run and orchestration layers evolve independently.

The challenge of running dbt at scale

Before the dbt launcher, our dbt runs had grown in different directions. Run logic was embedded in project-specific pipelines, orchestration and processing were tightly coupled, teams selected different compute engines for comparable workloads, and we had no consistent governance over run parameters and retries.

As the number of dbt projects increased, this made it difficult to enforce consistent standards and to evolve the solution without touching every pipeline. We needed a way to standardize dbt runs across our data lakes, decouple running a project from deciding what to run, improve cost control and observability, and deliver faster and safer continuous integration and continuous delivery (CI/CD) iterations.

Why Amazon Athena as the default dbt engine

Choosing the processing engine for dbt is a foundational architectural decision, so we made it first.

Serverless processing

Athena is fully serverless. There are no clusters to provision, scale, or maintain. Our teams run their queries with the default Athena pricing, which charges for the data a query scans and gives us elasticity with no capacity planning.

Because each data lake lives in its own account, each team makes its own decision about Athena payment. A team whose workload grows into continuous, high-concurrency usage can move to Athena capacity reservations with no change to the launcher, to their dbt profiles, or to their project configuration. None of our teams have needed to do so yet, and the architecture keeps that choice independent per team.

Our workload is predictable but not continuous. Each dbt project runs for a few minutes when its schedule fires or when its upstream data lands, then stays idle until the next trigger. That shape is what made serverless the right fit for us.

Why Athena fit our solution

For Moeve, the decision to standardize on dbt and Amazon Athena was driven by our goal of creating a common transformation solution that could be adopted across multiple teams and AWS accounts while keeping operations lightweight.

Our data was already stored in Amazon S3 and registered in the AWS Glue Data Catalog, making Athena a natural processing layer. Athena allowed us to run transformations without managing clusters, capacity, or infrastructure, which was particularly important for a small central team supporting multiple domains.

We evaluated alternative processing engines, but for our workload profile and data volumes, Athena provided the best balance between scalability, operational simplicity, and maintainability. Adapter maturity was another important factor. The dbt Athena adapter offered strong integration with testing, CI/CD workflows, and the broader dbt ecosystem, reducing the operational risk of maintaining custom solutions.

Athena also aligned naturally with our architecture. Transformations run in the AWS account that owns the data through cross-account role assumption, while orchestration remains centralized. As a result, we standardized how projects run across teams while keeping compute close to the data.

Finally, the Apache Iceberg support in Athena underpins the idempotent incremental processing model described in this post, so incremental loads and historical reprocessing follow the same path with minimal operational overhead.

Optimized incremental processing

Our largest cost was not reading source data. It was merging into it.

Our fact tables are Apache Iceberg tables in the data lake, and most of our models are incremental. Each run brings in new or corrected records and merges them into a target that can hold several years of history. A merge has to locate the rows it is about to update, and without a predicate on the target the query reads far more of the table than the incoming data can affect. The common approach is a static filter such as the last 30 days, which is wrong in both directions: too wide for an ordinary daily load, and too narrow as soon as a correction arrives for an older partition.

Instead of a fixed window, the platform derives the predicate from the data. Before the merge runs, it reads the distinct values of the partition column present in the incoming dataset and builds the target predicate from them. For a single partition it applies an equality predicate, for a small set an IN list, and for a larger set a bounded range. The merge then reads only the partitions the incoming data can affect.

The same principle applies on the source side. The launcher builds the source filter from the parameters given for that run: an explicit range, an arbitrary SQL condition, or, when neither is supplied, a default window taken from the project configuration. Input is therefore bounded to the subset each run needs.

Two results mattered to us. Because Athena charges for the data a query scans, narrowing both ends of the merge reduces cost without any team hand-tuning individual models. More importantly, a daily load and a full historical reprocess became the same operation with different inputs. Every run is idempotent, so the same input always produces the same result regardless of how many times it runs. That removed the distinction between processing and reprocessing from our runbooks and simplified incident response.

Table design is what makes this pruning possible. Partitioning, columnar formats, and compression all contribute, and the AWS Big Data Blog post Top 10 performance tuning tips for Amazon Athena covers the general techniques. We have deliberately not published a before and after figure here, because the saving depends so heavily on partition design and data distribution that a single number would mislead without extensive context.

Architecture overview

Moeve built a centralized dbt launcher that runs dbt jobs in a uniform way, regardless of the project or the target data lake.

Architecture diagram

Architecture of the centralized dbt launcher running cross-account dbt jobs on Step Functions, Fargate, and Amazon Athena

Figure 1: Centralized dbt launcher and cross-account run flow

At a high level, the architecture consists of:

  • AWS Step Functions to control the run lifecycle.
  • AWS Fargate to run dbt in an isolated, ephemeral container.
  • Amazon Athena as the default dbt processing engine.
  • Amazon DynamoDB to store dbt project configuration.
  • Amazon EventBridge to publish run results.

Every dbt run follows the same contract, which gives us consistency and reduces the operational surface we must maintain.

Cross-account processing model

The solution operates in a centralized account while running transformations in domain-specific data lake accounts: corporate, marketing, and manufacturing. The Fargate container assumes a dbt-child IAM role in the target account, so the container processes data where it lives while governance stays centralized.

Each data lake account keeps control of its own IAM permissions and manages its own storage and catalog without affecting the solution. This also puts costs in the right account. We could have attributed Athena spend using Athena workgroups, but Athena is only part of what a query costs. The Amazon Simple Storage Service (Amazon S3) requests it makes and the AWS Key Management Service (AWS KMS) operations it triggers are real costs as well, and running in the owning account attributes all of them to the team that owns the data, per project and per run.

Centralizing dbt runs with the dbt launcher

To stop every team inventing its own way of running dbt, we built a single launcher that all of them go through. Instead of dbt logic living inside multiple pipelines, every run is triggered through one well-defined path.

Run lifecycle

The launcher is an AWS Step Functions state machine. It receives a run request with its parameters, starts an AWS Fargate task from our dbt container image, and the container assumes the IAM role of the target data lake account. dbt then runs its SQL transformations in Athena, reading the source tables and materializing the targets. Alongside the run, Elementary, an open source dbt package, records model-level results and data quality test outcomes. When the run finishes, the launcher publishes a completion event to Amazon EventBridge.

The state machine can be started in different ways depending on the scenario. Some projects run on a schedule, others are triggered when upstream data lands, and teams can request a run on demand. Those decisions are made by our orchestration layer, which submits a standardized run request to the launcher. The launcher therefore stays focused on running dbt projects, regardless of how the run was initiated.

This sequence is identical for all projects and environments. The launcher is responsible only for running the project it was asked to run. It doesn’t decide what should run next, and that responsibility is intentionally delegated to downstream consumers through Amazon EventBridge.

Configuration-driven runs with Amazon DynamoDB

We had to decide where a project’s run parameters would live. In the pipeline definition, changing a timeout would be a code change, a review, a build, and a deployment, for a value we sometimes need to change while an incident is open. We put the parameters in DynamoDB instead, keyed by project, and the launcher reads them at the start of every run. That lets us decouple code deployment from run behavior, update parameters without redeploying services, and enforce consistent defaults across all dbt projects.

The parameters themselves are modest. They cover the target environment and AWS Identity and Access Management (IAM) role, the default processing engine, how long to allow a run to take, how many times to retry on failure, and how many days of data to process by default. These values change for operational reasons rather than logical ones, which is why we did not want them coupled to a release cycle.

The effect on onboarding was larger than we expected. Deploying a new dbt project is now a merge of the dbt models and one configuration entry. There is no Terraform change, no infrastructure review, and nothing to provision, because the compute the project needs already exists and is shared. What used to be a multi-step infrastructure pipeline is now a single CI workflow that validates the project’s SQL and lineage locally, then merges and registers it. We run that local validation with DuckDB, which returns feedback in under two minutes without consuming cloud resources.

Governance did not weaken as a result. Who may change a configuration entry is controlled the same way as any other production change. What changed is that the change no longer has to travel through an infrastructure deployment to take effect.

CI/CD pipeline diagram

A pull request triggers local validation of the project’s SQL and lineage. On merge, the dbt models are deployed and the project’s configuration entry is registered in DynamoDB, after which the launcher can run the project.

Figure 2: CI workflow for onboarding and updating a dbt project

Athena remains the engine of record. Local validation catches Jinja errors, unresolved references, and obvious SQL mistakes, but Athena-specific behavior, cross-account permissions, and AWS Glue Data Catalog interactions are only proven in the target environment. It’s important to be explicit about that boundary with the teams, so that a green CI run is not read as a guarantee.

Publishing run results with Amazon EventBridge

After a dbt run finishes, the launcher publishes a structured event to a central Amazon EventBridge bus recording whether the run succeeded, which project and source it covered, when it started and finished, how long it took, and which datasets it updated. The launcher does not know which consumers are subscribed.

This event-driven approach gives us loose coupling between running a project and orchestrating what comes next, multiple downstream consumers for the same signal, and independent evolution of both layers.

At Moeve the main consumer is our orchestration layer, which models the dependencies between datasets as a graph. Each completion event tells it that a node is now up to date, so it can determine which downstream projects have all their inputs ready and start them. That consumer has no special status. An AWS Lambda function, a monitoring dashboard, or a notification integration can subscribe to the same events without any change to the launcher.

Validation and observability

Because every project follows the same lifecycle, we get validation and observability in one place instead of per pipeline. Step Functions shows the state of any run and the step at which it failed, and error handling and retries are defined once. Fargate logs carry the container runtime detail. dbt and Elementary report model-level results and data quality test outcomes. The completion event on the bus is the auditable record that a project finished and what it produced. Together these layers give us operational visibility without coupling the components to each other.

Cost control and resource cleanup

The platform is serverless end to end, which keeps idle cost close to zero and removes a class of operational mistake. Fargate tasks are created for a run and destroyed when it ends, so no long-running container needs maintenance. Athena has no persistent compute. An idle Step Functions state machine costs nothing. Nothing is left running unintentionally, which matters when the number of projects on the platform keeps growing.

Results

The metrics in the following table are the ones our teams notice day to day, and the reason the solution is maintainable by a small central team.

Metric Before After
Onboarding time for a new dbt project Days, including pipeline and infrastructure setup About 15 minutes, configuration only
Run consistency Varied by team Same lifecycle and contract for every project
CI feedback time 8 to 10 minutes on Jenkins Under 2 minutes with local validation
Cost visibility Per-account aggregate Per-project and per-run attribution
Operational overhead One pipeline per project One solution for all projects

Conclusion

Standardizing how we run dbt turned out to depend less on dbt than on defining two boundaries clearly.

The first is the contract of the launcher: parameters in, event out. We defined that interface before building the internals, and it has stayed stable while the implementation changed several times. The second is the separation between running a project and deciding what to run next. Making the launcher publish events without knowing its consumers is why our orchestration layer could be rebuilt while the launcher stayed as it was, and the launcher has never been modified to accommodate a new orchestration requirement.

Two smaller decisions carried more weight than we expected. Keeping run parameters in DynamoDB rather than in pipeline definitions means timeouts, retries, and engine selection can be changed without a deployment, which is valuable during incident response. Making every run idempotent removed the distinction between processing and reprocessing, so a daily load and a full historical reprocess are the same operation with different inputs.

Athena is serverless, so our teams did not need to set up infrastructure of their own. That is what made it practical for everyone to run dbt in a standardized way, and why onboarding a new project went from days to about 15 minutes.

If your organization runs dbt across multiple accounts and teams, pair Amazon Athena with a clear contract for the component that runs your projects: fixed parameters in, a published event out.

Resources


About the authors

Rubén Romero Córdoba

Rubén Romero Córdoba

Rubén is a Cloud and Data Architect at Keepler with 10+ years spanning AI research, software engineering, and AWS data architecture. He designs secure, scalable, and maintainable data platforms focused on lakehouse architectures, governance, and operational efficiency. Curious and pragmatic, he studies how systems work, explores emerging tech, shares knowledge, and favors simple solutions that deliver real value without unnecessary complexity.

Ricardo Bravo Panes

Ricardo Bravo Panes

Ricardo is a Data Architect at Moeve, where he designs cloud-native data solutions on AWS, turning complex challenges into scalable and sustainable solutions. He is passionate about data, distributed systems, and simplifying complexity to help teams move faster.

Álvaro Ponce Cabrera

Álvaro Ponce Cabrera

Álvaro is a Data Engineer and Data Platform Lead at Moeve, focused on building scalable data products and cloud-native solutions that connect industrial and business data. His interests span data architecture, governance, AI, and developer experience, always seeking pragmatic solutions that maximize business value while keeping complexity under control.

Gonzalo Guerrero Leon

Gonzalo Guerrero Leon

Gonzalo is a TAM at AWS who empowers enterprise customers through strategic technical guidance. Throughout his 10-year tenure at Amazon, he’s contributed to multiple cornerstone divisions, including HR, IT, Alexa, Amazon Business, and AWS, gaining insight into the technology landscape. Outside of work, Gonzalo enjoys playing volleyball with his wife and exploring the world alongside their adventurous Boston Terrier, Tigre.

Achieving CNIL/EU ePrivacy compliance for email tracking with Amazon SES

Post Syndicated from Toni Pivcevic original https://aws.amazon.com/blogs/messaging-and-targeting/achieving-cnil-eu-eprivacy-compliance-for-email-tracking-with-amazon-ses/

Open and click tracking have long been foundational email metrics. Multiple data protection authorities, including in EU and Canada, have issued guidance requiring explicit opt-in consent before deploying open tracking pixels or click link wrapping in email. As privacy expectations evolve, a growing number of jurisdictions now require senders to obtain explicit consent before tracking whether a recipient opened an email or clicked a link. In this post, you learn how to use Amazon Simple Email Service (Amazon SES) configuration set overrides to control open and click tracking per request.

How Amazon SES tracking works

Amazon SES enables open and click tracking only when explicitly configured by you. Amazon SES does not inject tracking pixels or wrap links by default.

Open tracking — When you add an event destination publishing OPEN events to a configuration set, Amazon SES inserts a 1×1 tracking pixel (served from awstrack.me) into HTML email sent using that configuration set. When a recipient opens the email and their client loads images, Amazon SES records the open event and publishes it to the configured destination.

Click tracking — When you include CLICK events in a configuration set’s event destination, Amazon SES rewrites links in HTML email to redirect through awstrack.me, recording click events before sending the user to the original URL.

Configuration sets — Configuration sets are the control surface for both features. Tracking activates only when an event destination explicitly includes OPEN or CLICK in its matching event types. You can have multiple configuration sets with different tracking configurations and select the appropriate one at send time.

Per-request tracking overrides — Amazon SES now supports open and click tracking override parameters directly in the SendEmail and SendBulkEmail APIs through the ConfigurationOverrides object. You can enable or disable open tracking and click tracking on an individual API call, without maintaining separate configuration sets. The override takes precedence over the tracking behavior defined in the associated configuration set, giving you fine-grained, per-recipient control at send time. This is the most direct way to honor recipient-level consent choices.

Event publishing and metrics — Open and click events are published to the destinations you configure: Amazon CloudWatch, Amazon Data Firehose, or Amazon EventBridge. Plan for reduced fidelity in open-rate dashboards for recipients in jurisdictions where you cannot track without consent.

Note on Amazon SES Contact Lists: Contact Lists manage topic-level subscription preferences (for example, marketing versus transactional) and control whether Amazon SES delivers to a contact. They do not control tracking behavior. There is no native mapping between a contact’s subscription status and tracking pixel injection. Tracking consent must be managed separately in your application.

Prerequisites

To follow the steps in this post, you should be familiar with Amazon SES configuration sets and basic email sending concepts. You also need the following:

  • An AWS account with Amazon SES out of sandbox mode.
  • AWS Identity and Access Management (IAM) permissions to create and manage Amazon SES configuration sets (ses:CreateConfigurationSet, ses:CreateConfigurationSetEventDestination, ses:DeleteConfigurationSet).
  • The AWS Command Line Interface (AWS CLI) version 2 installed and configured, or access to the AWS SDK for Python (Boto3).

The most direct approach to consent-based tracking uses the ConfigurationOverrides parameter in the SendEmail API. This approach requires only a single configuration set with tracking-enabled event destinations. At send time, you override the tracking behavior based on each recipient’s consent status.

AWS SDK:

import boto3

ses_client = boto3.client("sesv2", region_name="us-east-1")

def send_campaign_email(subscriber: dict, subject: str, html_body: str):
    # Determine tracking behavior based on recipient consent
    tracking_consent = subscriber.get("tracking_consent", False)

    # Use ConfigurationOverrides.Tracking to control per-request behavior
    ses_client.send_email(
        FromEmailAddress="[email protected]",
        Destination={"ToAddresses": [subscriber["email"]]},
        Content={
            "Simple": {
                "Subject": {"Data": subject},
                "Body": {"Html": {"Data": html_body}},
            }
        },
        ConfigurationSetName="my-config-set",
        ConfigurationOverrides={
            "Tracking": {
                "OpenTrackingEnabled": tracking_consent,
                "ClickTrackingEnabled": tracking_consent,
            }
        },
    )

The ConfigurationOverrides.Tracking object takes precedence over the configuration set’s event destination settings for that individual send. If the recipient has not consented, Amazon SES does not inject the tracking pixel or wrap links, regardless of whether the configuration set has OPEN and CLICK events enabled.

Advantages of per-request overrides:

  • No need to maintain separate configuration sets for tracked versus untracked sends.
  • A single configuration set can handle all recipients, which simplifies event destination management, suppression, and DomainKeys Identified Mail (DKIM) and domain settings.
  • Per-recipient control without branching logic for configuration set selection.
  • Works with SendBulkEmail as well, setting tracking overrides per recipient in the bulk request.

SMTP interface — If you send through SMTP, per-request overrides are not available. Use Option B (separate configuration sets) instead.

Option B: Use separate configuration sets

If you send through SMTP or prefer to separate tracking behavior at the configuration set level, create two configuration sets: one with tracking enabled for consented recipients, and one with no tracking for non-consented recipients.

Console: In the Amazon SES console, choose Configuration sets, and then choose Create configuration set. To enable tracking on the first set, add an event destination and include OPEN and CLICK in the matching event types. Create a second set with no OPEN or CLICK event destinations, or omit event destinations entirely.

AWS CLI:

# Configuration set with tracking enabled (for consented recipients)
aws sesv2 create-configuration-set \
    --configuration-set-name tracking-enabled

aws sesv2 create-configuration-set-event-destination \
    --configuration-set-name tracking-enabled \
    --event-destination-name open-click-destination \
    --event-destination '{
    "Enabled": true,
    "MatchingEventTypes": ["SEND","DELIVERY","BOUNCE","COMPLAINT","OPEN","CLICK"],
    "CloudWatchDestination": { ... }
}'

# Configuration set with no tracking (for non-consented recipients)
aws sesv2 create-configuration-set \
    --configuration-set-name no-tracking

Replace { … } with your CloudWatch destination configuration. For the full parameter structure, see Managing Amazon SES event destinations.

No event destination for OPEN or CLICK means no pixel is injected and no links are wrapped.

Send-time selection:

Your application selects the configuration set for each message based on the recipient’s consent status.

import boto3

ses_client = boto3.client("sesv2", region_name="us-east-1")

def send_campaign_email(subscriber: dict, subject: str, html_body: str):
    config_set = (
        "tracking-enabled"
        if subscriber.get("tracking_consent")
        else "no-tracking"
    )
    ses_client.send_email(
        FromEmailAddress="[email protected]",
        Destination={"ToAddresses": [subscriber["email"]]},
        Content={
            "Simple": {
                "Subject": {"Data": subject},
                "Body": {"Html": {"Data": html_body}},
            }
        },
        ConfigurationSetName=config_set,
    )

SMTP interface — Set the configuration set through the X-SES-CONFIGURATION-SET header:

X-SES-CONFIGURATION-SET: no-tracking

To disable click tracking on individual links within a tracked email (for example, your unsubscribe link), use the ses:no-track attribute:

<a ses:no-track href="https://anycompany.example.com/unsubscribe">Unsubscribe</a>

Amazon SES strips the ses:no-track attribute before delivery, so recipients never see it. This works with both Option A and Option B.

Regardless of which option you choose, Amazon SES provides the mechanisms to enable or disable tracking at send time. You are responsible for:

  • Maintaining a consent database recording each recipient’s tracking consent status.
  • Determining at send time whether a recipient has consented to open and click tracking.
  • Passing the correct override parameter (Option A) or selecting the appropriate configuration set (Option B) based on that determination.

Important: Once an email is delivered with a tracking pixel, it cannot be retroactively deactivated.

Capture consent at sign-up. France’s data protection authority (CNIL) recommends collecting tracking consent at the point of email address collection. Include a clearly labeled, unchecked checkbox. For example: “I agree to allow AnyCompany to track whether I open or click email to improve future communications.” This is a separate checkbox from consent to receive marketing email.

Store consent with proof. You must be able to demonstrate valid consent for each individual. Store a consent timestamp and source alongside the subscriber record:

import boto3
from datetime import datetime, timezone

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Subscribers")

def save_subscriber(email: str, marketing_consent: bool, tracking_consent: bool):
    table.put_item(
        Item={
            "email": email,
            "marketing_consent": marketing_consent,
            "tracking_consent": tracking_consent,
            "tracking_consent_timestamp": datetime.now(timezone.utc).isoformat(),
            "tracking_consent_source": "sign-up-form-v2",
        }
    )

Make refusal as straightforward as acceptance. Do not use pre-checked boxes or dark patterns that make opting out harder than opting in.

Provide withdrawal at any time. Every email sent with tracking enabled must include a link that recipients can use to withdraw tracking consent independently of unsubscribing. A “Manage email preferences” link in the footer, separate from the unsubscribe link, satisfies this requirement:

<p style="font-size:12px; color:#666;">
  <a href="https://anycompany.example.com/email-preferences?token={SUBSCRIBER_TOKEN}">Manage email preferences</a>
  &nbsp;|&nbsp;
  <a href="{UNSUBSCRIBE_URL}">Unsubscribe</a>
</p>

Replace {SUBSCRIBER_TOKEN} with a signed token that identifies the subscriber’s record in your consent database. Replace {UNSUBSCRIBE_URL} with your Amazon SES one-click unsubscribe URL or list-unsubscribe endpoint.

Monitoring and governance

Audit event volumes. Use Amazon SES event publishing to monitor open and click event counts. If you use Option B, compare volumes between tracking-enabled and no-tracking. If you use Option A, monitor the ratio of sends with tracking enabled versus disabled. A sudden drop in consented opens might indicate an issue with your consent capture flow.

Review data retention. If you rely on the deliverability-only exemption for any segment, verify that your data pipeline retains only the date of last open, not the time, IP address, or user-agent string. Collecting those fields, even temporarily, voids the exemption per CNIL guidance.

Document your consent architecture. Maintain a record of how and when each subscriber provided consent, which form version was in use, and where the consent data is stored.

Clean up

If you created test configuration sets while following this post and do not intend to use them, delete them to avoid unintended configuration being applied to future sends.

aws sesv2 delete-configuration-set --configuration-set-name tracking-enabled
aws sesv2 delete-configuration-set --configuration-set-name no-tracking

Conclusion

Amazon SES gives you the tools to obtain prior consent before tracking opens and clicks. With per-request tracking overrides, you can honor consent decisions inline with each API call, with no additional configuration sets required. For senders using SMTP, separate configuration sets achieve the same outcome. Combined with consent capture at sign-up and a preferences management link in every tracked email, you can maintain meaningful analytics for consented recipients while respecting the privacy of those who have not opted in.

For more information, see the following resources in the Amazon SES Developer Guide:


About the authors

Connect Amazon SageMaker Unified Studio to Microsoft Power BI – Part 1: IAM Identity Center (IDC)-based domains

Post Syndicated from Ramesh H Singh original https://aws.amazon.com/blogs/big-data/connect-amazon-sagemaker-unified-studio-to-microsoft-power-bi-part-1-iam-identity-center-idc-based-domains/

Connecting Power BI to your Amazon SageMaker Unified Studio data catalogs typically required third-party bridges. These bridges added complexity and licensing costs. In this post, you create a direct connection using new authentication modes in the Amazon Athena ODBC driver, removing those dependencies entirely. If your organization uses Power BI as its business intelligence (BI) tool, your analysts can configure access to governed data in Amazon SageMaker Unified Studio without changing their tools or workflows. As an AWS alternative, Amazon Quick Sight provides serverless BI integration with Amazon SageMaker Unified Studio at pay-per-session pricing.

A previous post showed the connection method using a third-party ODBC-JDBC bridge. The Amazon Athena ODBC driver (version 2.2.0 and later) now supports Amazon SageMaker Unified Studio authentication directly, eliminating the need for customers to configure third-party bridge components previously required for this connection. This bridge also created additional components and required ongoing maintenance. The native connection simplifies the architecture by reducing these requirements.

UC Irvine, a top-ten U.S. public research university, consolidates student data from systems across multiple departments into a single governed repository that supports reporting, research, and analytics for decision-making at the strategic, tactical, and operational levels. Many of their analysts rely on Power BI to explore and visualize this governed data.

“Our users rely on Power BI for data visualization and reporting, but connecting to governed data in AWS previously required workarounds. The ODBC connection feature gives a direct path from Power BI into our SageMaker Unified Studio projects—no bridge software, no extra licensing, just a connection string and we’re ready to go.”

— Bernadette Theologidy, Manager, Student Analytics, UC Irvine

The Athena ODBC driver introduces two new authentication modes for SageMaker Unified Studio:

  1. SageMakerBrowserIdc (for IDC-based domains): The driver opens a browser window and authenticates through AWS IAM Identity Center (and your external identity provider, if configured). No local AWS credentials are needed.
  2. SageMakerIam (for AWS Identity and Access Management (IAM)-based and IDC-based domains): The driver uses AWS credentials from the default credential provider chain. For this walkthrough, we use AWS IAM Identity Center to provide those credentials.

You connect Microsoft Power BI to Amazon SageMaker Unified Studio through Athena. The Athena ODBC driver supports using two connection methods that use these authentication modes:

Method 1: DSN-based (Athena Power BI connector): You configure an ODBC Data Source Name (DSN) and use the Athena connector in Power BI. This method supports DirectQuery and Import mode with both SageMakerBrowserIdc and SageMakerIam authentication.

Method 2: DSN-less (Power BI ODBC connector): You use the Power BI ODBC connector with a connection string, requiring no DSN configuration. This method supports Import mode only with SageMakerIam authentication. DirectQuery isn’t available because the Power BI ODBC connector doesn’t support it. The connection string in Power BI Desktop must match exactly the one on Power BI Service. Because the gateway runs as a Windows service without interactive browser access, both ends must use SageMakerIam.

Feature Method 1: DSN-based Method 2: DSN-less
Power BI Connector Amazon Athena connector ODBC connector
Data connectivity mode DirectQuery and Import Import only
Requires DSN configuration Yes No
Data freshness Real-time (DirectQuery) or scheduled (Import) Scheduled refresh only
Authentication types SageMakerIam and SageMakerBrowserIdc SageMakerIam only
Domain types supported IAM-based and IDC-based IAM-based and IDC-based
Best for Dashboards requiring live data Scenarios where DSN management is not possible or scheduled refresh is acceptable

This is Part 1 of a two-part series. This post covers IDC-based domains using both connection methods. Part 2 covers IAM-based domains.

Solution overview

In this walkthrough, you take the role of a data analyst at an energy company. You need to understand the current state and future direction of the U.S. power generation fleet using the Public Utility Data Liberation Project, available on the Registry of Open Data on AWS. Our goal is to analyze generation capacity and identify where new investment is flowing. We connect Power BI to Athena through Amazon SageMaker Unified Studio and query the EIA-860 generators dataset directly from our data catalog. The result is a single visualization that reveals the energy transition.

The following diagram illustrates the solution architecture for connecting Power BI to Amazon SageMaker Unified Studio through Amazon Athena.

Architecture diagram showing Power BI connecting to Amazon Athena through Amazon SageMaker Unified Studio, with a Microsoft on-premises data gateway on Amazon EC2

Figure 1: Architecture diagram

The following architecture demonstrates a six-step workflow.

  1. Data engineers and analysts connect Power BI Desktop to Athena as a data source.
  2. They build their reports locally.
  3. They then publish them to the Power BI Service.
  4. Microsoft On-Premises Data Gateway on an Amazon Elastic Compute Cloud (Amazon EC2) instance connects to Athena using the instance’s attached IAM role.
  5. The Power BI Service then uses this gateway connection.
  6. Report viewers access the published reports through Power BI Service to make data-driven decisions.

On the AWS side, Athena queries the data catalog managed by AWS Glue Data Catalog. The catalog references data stored in Amazon Simple Storage Service (Amazon S3). An Amazon SageMaker Unified Studio project governs all access.

In an IDC-based domain (covered in this post), Power BI Desktop uses SageMakerBrowserIdc for Method 1 and SageMakerIam for Method 2. Power BI Desktop can run on-premises or on an EC2 instance. The gateway always uses SageMakerIam (it runs as a Windows service without browser access) and authenticates using instance profile credentials, which rotate automatically. The gateway can only query data within projects where its IAM role has been added as a member. For IAM-based domains, see Part 2.

Prerequisites

Before connecting Power BI to Amazon SageMaker Unified Studio, verify that your environment meets these requirements:

  • Athena ODBC driver – The latest Amazon Athena ODBC driver (version 2.2.0 or more recent) for Windows 64-bit.
  • Microsoft Power BI Desktop – The latest version installed on your Windows machine.
  • Microsoft Power BI Pro License – Required for publishing reports and configuring the on-premises data gateway.
  • Microsoft Power BI on-premises data gateway – The latest version installed on the EC2 instance.
  • Amazon SageMaker Unified Studio – An Amazon SageMaker Unified Studio IDC-based domain.

You need an Amazon SageMaker Unified Studio project with data assets. For detailed instructions, refer to the Amazon SageMaker Unified Studio User Guide.

The following screenshot shows the Amazon SageMaker Unified Studio project Query Editor interface, which runs a preview query against the EIA-860 generators dataset.

SageMaker Unified Studio Query Editor previewing the EIA-860 generators dataset

Figure 2: SageMaker Unified Studio project with the EIA-860 generators dataset available in the data catalog

Method 1: DSN-based connection (Athena Power BI connector)

This method uses the Amazon Athena Power BI connector with an ODBC Data Source Name (DSN), supporting DirectQuery and Import mode.

You configure Power BI Desktop to connect to your data assets in Amazon SageMaker Unified Studio using the SageMakerBrowserIdc authentication mode. The driver opens a browser window and authenticates through IAM Identity Center (and your external identity provider, if configured).

Add your SSO user as a member of your SageMaker Unified Studio project

Your single sign-on (SSO) user needs project-level access to query data with Athena. Verify your user is listed as a project member or add it by following Add project members in the Amazon SageMaker Unified Studio User Guide.

The following screenshot shows the SageMaker Unified Studio project user management page, where project owners can add or remove project users and roles.

SageMaker Unified Studio project members page listing users and roles

Figure 3: Members of a SageMaker Unified Studio project

Gather configuration values to configure your Amazon Athena ODBC DSN

Gather the following values from your Amazon SageMaker Unified Studio project:

  1. Open your Amazon SageMaker Unified Studio project.
  2. In the top right, select the three dots.
  3. Choose Project details.
  4. Select JDBC and ODBC details.
  5. Under ODBC connection details copy the following information: IDC issuer URL, domain ID, project ID, Athena workgroup name and AWS Region.

The following screenshot shows the Amazon SageMaker Unified Studio project overview page, where you can copy these details.

SageMaker Unified Studio project overview showing ODBC connection details

Figure 4: ODBC connection details

Configure the ODBC DSN

Create a System DSN using the Amazon Athena ODBC driver. For the general DSN creation steps, see Configuring a data source name on Windows in the Amazon Athena User Guide.

Enter the following values:

Field Value
Data Source Name Name your datasource (for example, pbi-idcdomain)
Region The AWS Region where your Amazon SageMaker domain is provisioned (for example, us-east-1)
Catalog AwsDataCatalog
Database default
Workgroup Your Athena workgroup name (for example, workgroup-abcdefghij-klmexample)

In the Authentication Options, configure the following values:

Field Value
Authentication Type SageMakerBrowserIdc
SSO Start URL IAM Identity Center entry point (for example, https://identitycenter.amazonaws.com/ssoins-0example)
SSO Region Region of IAM Identity Center (for example, us-east-1)
SageMaker Domain ID dzd-123456example
SageMaker Project ID abcd12example
SageMaker Domain Region Region of your Amazon SageMaker Unified Studio project (for example, us-east-1)

Choose OK, then Test to verify the connection. Choose Allow Access when prompted by the browser.

The following screenshot shows the consent prompt.

Browser consent prompt requesting access approval during authentication

Figure 5: Browser consent prompt

The following screenshot shows the successful connection test.

ODBC DSN configuration showing a successful connection test with SageMakerBrowserIdc

Figure 6: Successful connection test in the ODBC DSN configuration with SageMakerBrowserIdc authentication

Connect Power BI Desktop to your data

With the DSN configured, you can connect Power BI Desktop to your data catalog and load the generators dataset.

  1. Open Power BI Desktop.
  2. Open the Get Data menu and select More.
  3. Search for and select Amazon Athena and choose Connect.
  4. For Data Source Name (DSN), enter pbi-idcdomain.
  5. Select DirectQuery.
  6. Choose OK.
  7. Choose Use Data Source Configuration and then Connect.
  8. In the AwsDataCatalog folder, navigate to your database.
  9. Select the core_eia860__scd_generators table.
  10. Choose Load.

The following screenshot shows Power BI Desktop successfully connected to the AWS data catalog.

Power BI Desktop connected to the data catalog with the generators table loaded

Figure 7: Power BI Desktop connected to the data catalog with the generators table loaded using SageMakerBrowserIdc authentication

Create your dashboard and publish it

You can create a dashboard to visualize U.S. power generation data. To create a visualization, complete the following steps:

  1. In the Visualizations pane, choose the Stacked bar chart.
  2. Assign the Y-Axis: Drag technology_description to the Y-Axis.
  3. Assign the X-Axis (Values): Drag capacity_mw to the X-Axis (automatically summed).
  4. Assign the Legend (Stack): Drag operational_status to the Legend field.
  5. Choose Publish.
  6. Give your report a name (for example, generation-idcdomain) and choose Save.
  7. Sign in and choose a destination workspace.
Power BI Desktop stacked bar chart of generation capacity by technology and operational status

Figure 8: Power BI Desktop report using the EIA-860 generators dataset

After publishing, the report structure is available on Power BI Service.

Method 2: DSN-less connection (Power BI ODBC connector)

In this method, you use the Power BI ODBC connector with a connection string (no DSN required). This method supports Import mode only and SageMakerIam authentication. Because the gateway cannot perform browser authentication, both Desktop and gateway must use SageMakerIam. If your workflow requires SageMakerBrowserIdc, use Method 1.

If your machine already has AWS credentials through another method in the default credential provider chain, skip the following setup.

Administrator setup

Create a custom permission set named SageMakerDataAnalyst in IAM Identity Center with the following inline policy. For detailed steps, see Create a permission set in the AWS IAM Identity Center User Guide.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SageMakerAccess",
            "Effect": "Allow",
            "Action": [
                "datazone:GetConnection",
                "datazone:ListConnections",
                "datazone:GetDomain",
                "datazone:GetProject"
            ],
            "Resource": "*"
        },
        {
            "Sid": "STSForDriver",
            "Effect": "Allow",
            "Action": [
                "sts:GetCallerIdentity"
            ],
            "Resource": "*"
        }
    ]
}

Assign your user to this permission set for the AWS account containing your SageMaker Unified Studio domain. Then configure your AWS Command Line Interface (AWS CLI) SSO profile by running aws configure sso. For the full CLI configuration walkthrough with detailed steps, see Part 2. After your profile is configured, run aws sso login to authenticate.

Add the IAM identity as a member of SageMaker Unified Studio project

The IAM identity providing credentials needs both domain-level and project-level access to query data through Athena.

  1. Add AWSReservedSSO_SageMakerDataAnalyst_1234example as a domain IAM user: see Managing users in the Amazon SageMaker Unified Studio Admin Guide. Choose Current account.
SageMaker Unified Studio domain users list including the IAM identity

Figure 9: List of users of your SageMaker Unified Studio domain including the IAM identity

  1. Add AWSReservedSSO_SageMakerDataAnalyst_1234example as a project member: see Add project members in the Amazon SageMaker Unified Studio User Guide.
SageMaker Unified Studio project members list including the IAM identity

Figure 10: Members of a SageMaker Unified Studio project including the IAM identity

Gather configuration values

Gather the following connection values from your Amazon SageMaker Unified Studio project:

  1. Open your Amazon SageMaker Unified Studio Project.
  2. On the navigation pane, choose Overview.
  3. Select JDBC and ODBC details.
  4. Select the Using IAM auth toggle.
  5. Copy the ODBC connection string.
SageMaker Unified Studio project overview showing the ODBC connection string for IAM auth

Figure 11: ODBC connection string on the SageMaker Unified Studio project overview

Connect Power BI Desktop to your data and publish

With the configuration parameters of your project, you can connect Power BI Desktop to your data catalog and load the generators dataset.

  1. Open Power BI Desktop.
  2. Open the Get Data menu and select More.
  3. Search for and select ODBC and choose Connect.
  4. For Data Source Name (DSN), select (None).
  5. Expand Advanced Options.
  6. In the Connection string field, enter your connection string. For example, Driver={Amazon Athena ODBC (x64)};AwsRegion=us-east-1;Catalog=AwsDataCatalog;Schema=default;Workgroup=workgroup-abcdefghij-klmexample;SageMakerDomainId= dzd-123456example;SageMakerProjectId= abcd12example;SageMakerDomainRegion=us-east-1;AuthenticationType=SageMakerIam;
  7. Choose OK.
  8. Choose Default or Custom and then Connect.
  9. In the AwsDataCatalog folder, navigate to your database.
  10. Select the core_eia860__scd_generators table.
  11. Choose Load.

When publishing, name your report generation-idcdomain-dsnless.

Configure the on-premises data gateway and view your report on Power BI Service

After creating your reports in Power BI Desktop, configure the on-premises data gateway to view your report on Power BI Service.

You can configure the gateway using either a DSN or a DSN-less connection string, matching the method you used in Power BI Desktop.

Create and attach an IAM role to the Power BI Gateway EC2 instance

Create an IAM role for the EC2 instance that will host your Power BI gateway. Name the role pbi-gateway-role (or a name of your choice). The role must use EC2 as the trusted entity and include the following inline policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SageMakerAccess",
            "Effect": "Allow",
            "Action": [
                "datazone:GetConnection",
                "datazone:ListConnections",
                "datazone:GetDomain",
                "datazone:GetProject"
            ],
            "Resource": "*"
        },
        {
            "Sid": "STSForDriver",
            "Effect": "Allow",
            "Action": [
                "sts:GetCallerIdentity"
            ],
            "Resource": "*"
        }
    ]
}

Attach this role to your Power BI Gateway EC2 instance. For detailed steps on creating and attaching an IAM role to an EC2 instance, refer to IAM roles for Amazon EC2 in the Amazon EC2 User Guide.

Add the Power BI Gateway IAM role as a member of SageMaker Unified Studio project

The gateway IAM role needs project-level access to query data through Athena.

  1. Add the IAM pbi-gateway-role role as a domain IAM user: see Managing users in the Amazon SageMaker Unified Studio Admin Guide. Choose Current account (or Associated account if your gateway is deployed in a different account).

The following screenshot, from the Amazon SageMaker page of the AWS Management Console, shows the list of users of your Amazon SageMaker Unified Studio domain, including the IAM gateway role.

SageMaker Unified Studio domain users list including the Power BI gateway IAM role

Figure 12: List of users of your SageMaker Unified Studio domain including the IAM gateway role

Add the IAM pbi-gateway-role role as a project member: see Add project members in the Amazon SageMaker Unified Studio User Guide.

The following screenshot shows the Amazon SageMaker Unified Studio project user management page listing the project members.

SageMaker Unified Studio project members list including the Power BI gateway IAM role

Figure 13: Members of a SageMaker Unified Studio project including the IAM gateway role

Configure the data source on Power BI Gateway

How you configure the data source depends on the method you used in Power BI Desktop.

Method 1 (DSN-based)

Configure a System DSN on the gateway EC2 instance following the same ODBC DSN steps described in Method 1. When configuring, make sure that:

  • You use the System DSN tab (not User DSN) because the gateway runs as a Windows service under a separate account.
  • The authentication type is set to SageMakerIam regardless of what you used on Desktop.
  • The DSN name matches exactly the one configured on Power BI Desktop (for example, pbi-idcdomain)

Method 2 (DSN-less)

No configuration is needed on the gateway machine itself. You configure the data source directly in Power BI Service.

Configure the data source and view your report on Power BI Service

To view your report, complete the following steps:

  1. Open the workspace where you saved your report.
  2. Search the Semantic Model which has the same name as your report (for example, generation-idcdomain) and choose the More options icon (three dots).
  3. Choose Settings.
  4. Expand Gateway and Cloud Connection.
  5. Choose View Datasources (play icon) on your gateway.
  6. Choose Manually add to gateway.
  7. Add a connection name (for example, pbi-idcdomain).

The next step depends on the method that you chose:

Method 1 (DSN-based)

  1. Add the DSN (for example, pbi-idcdomain) that matches exactly the one configured on Power BI Desktop.

Method 2 (DSN-less)

  1. In the Connection string field, enter the connection string that matches exactly the one used in Power BI Desktop.

Next, continue with the configuration:

  1. Select Anonymous as Authentication Method.
  2. Choose Create.
  3. Expand again Gateway and Cloud Connection.
  4. For Maps to, choose the connection that you created (for example, pbi-idcdomain).
  5. Choose Apply.
  6. Return to the workspace where you saved your report.
  7. On the Content section, choose your report (for example, generation-idcdomain).

The following screenshot shows a Power BI report on Power BI Service.

Published Power BI report rendering on Power BI Service

Figure 14: Power BI report on Power BI Service

You can now see your report online with the data from your Amazon SageMaker Unified Studio project.

Clean up

To avoid additional charges after testing, delete the Amazon SageMaker Unified Studio domain and EC2 instances. Refer to Delete domains and Terminate Instances for instructions.

Conclusion

In this post, you connected Microsoft Power BI to Amazon SageMaker Unified Studio using an IDC-based domain with both DSN-based and DSN-less methods. This provides a direct connection, with no third-party licensing, that maintains data governance. In Part 2, we cover IAM-based domains.

You can automate many steps of this process. For information about automating DSN creation on the Power BI Gateway or Service, refer to How ENGIE automates the deployment of Amazon Athena data sources on Microsoft Power BI. If you don’t want users adding the gateway IAM role directly, you can create a custom blueprint as a self-service tool for gateway role addition. The blueprint uses a ProjectMembership resource with a configurable parameter that project owners can activate at project creation, automatically adding the gateway role as a project contributor.

For additional best practices, refer to the Using Microsoft Power BI with the AWS Cloud Whitepaper. To learn more, visit Amazon SageMaker Unified Studio and Amazon Athena.


About the authors

Ramesh Singh

Ramesh Singh

Ramesh is a Senior Product Manager Technical (External Services) at AWS in Seattle, Washington, currently with the Amazon SageMaker team. He is passionate about building high-performance ML/AI and analytics products that help enterprise customers achieve their critical goals.

Armando Segnini

Armando Segnini

Armando is a Senior Analytics Specialist Solutions Architect at AWS, partnering with enterprise customers to architect scalable data, analytics, and AI platforms. He helps organizations turn complex data challenges into business value through expertise in streaming, BI integration, and generative AI. Outside of work, Armando enjoys traveling with his family, exploring new cultures, photography, and functional fitness competitions.

Gaurav Sharma

Gaurav is a Specialist Solutions Architect (Analytics) at AWS, supporting US public sector customers on their cloud journey. Outside of work, Gaurav enjoys spending time with his family and reading books.

Krishna Atluru

Krishna Atluru

Krishna is an Enterprise Support Lead TAM at AWS. He provides customers with in-depth guidance on improving security posture and operational excellence for their workloads, helping them build secure, resilient, and cost-effective solutions. His areas of expertise include building serverless architectures, and data and analytics solutions. Outside of work, Krishna enjoys cooking, swimming, and traveling.

Saushthav Saxena

Saushthav Saxena

Saushthav is a Software Development Engineer at AWS on the Amazon Athena team, where he has spent the past few years working on distributed systems and data analytics at scale. Based in the San Francisco Bay Area, his background spans full-stack development, high performance computing, and large-scale infrastructure. Outside of work, he enjoys reading sci-fi novels, swimming, and traveling with family and friends.

Connect Amazon SageMaker Unified Studio to Microsoft Power BI – Part 2: IAM-based domains

Post Syndicated from Ramesh H Singh original https://aws.amazon.com/blogs/big-data/connect-amazon-sagemaker-unified-studio-to-microsoft-power-bi-part-2-iam-based-domains/

In Part 1 of this series, we connected Microsoft Power BI to Amazon SageMaker Unified Studio using an IAM Identity Center (IDC)-based domain. The Amazon Athena ODBC driver (version 2.2.0 and later) supports Amazon SageMaker Unified Studio authentication natively, removing the third-party ODBC-JDBC bridge previously required. We walked through both the DSN-based connection and the DSN-less connection, from Power BI Desktop through the on-premises data gateway to Power BI Service, where report viewers access published dashboards.

In this post, you create the same direct connection using an AWS Identity and Access Management (IAM)-based domain. The walkthrough covers the same two connection methods. The differences are the Amazon SageMaker Unified Studio console navigation paths, the configuration values, and an additional administrator setup that provides AWS credentials through AWS IAM Identity Center. This is Part 2 of a two-part series. For a detailed comparison of the two connection methods, see Part 1.

Solution overview

The architecture is the same as the previous post (see the architecture diagram and walkthrough scenario in Part 1). Power BI Desktop connects to Amazon Athena through the ODBC driver and the Amazon SageMaker Unified Studio project governs all data access. At the same time, the on-premises data gateway on an Amazon Elastic Compute Cloud (Amazon EC2) instance bridges the connection to Power BI Service so report viewers can access published dashboards.

The difference is in authentication: An IAM-based domain uses SageMakerIam authentication for both connection methods. The driver retrieves credentials from the AWS default credential provider chain. For this walkthrough, AWS IAM Identity Center provides those credentials through a custom permission set. Power BI Desktop can run on-premises or on an EC2 instance in the AWS Cloud. The gateway EC2 instance authenticates using its attached IAM role.

Prerequisites

Complete the prerequisites from Part 1. Additionally, you need:

  • AWS Command Line Interface (AWS CLI) – The latest version of the AWS CLI installed on your Windows machine. In this post series, the ODBC driver uses the AWS IAM Identity Center profile configured through the CLI for authentication.
  • Amazon SageMaker Unified Studio – An Amazon SageMaker Unified Studio IAM-based domain with AWS IAM Identity Center single sign-on (SSO) enabled.

The following screenshot shows the Amazon SageMaker Unified Studio (IAM-based domain) project Query Editor interface. It runs a preview query on the EIA-860 generators dataset.

SageMaker Unified Studio Query Editor previewing the EIA-860 generators dataset in an IAM-based domain

Figure 1: SageMaker Unified Studio (IAM-based domain) project with the EIA-860 generators dataset available in the data catalog

Administrator setup

This section configures AWS IAM Identity Center to provide credentials for the SageMakerIam authentication mode. It applies to Method 1 (IAM-based domain) and Method 2 (both domain types). If your machine already has AWS credentials available through another method in the default credential provider chain, you can skip this section and proceed directly to the method of your choice. For the full list of credential sources, refer to Credential providers in the AWS SDKs and Tools Reference Guide.

Create a permission set in IAM Identity Center

Create a custom permission set named SageMakerDataAnalyst in IAM Identity Center with the following inline policy. For detailed steps, see Create a permission set in the AWS IAM Identity Center User Guide.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SageMakerAccess",
            "Effect": "Allow",
            "Action": [
                "datazone:GetConnection",
                "datazone:ListConnections",
                "datazone:GetDomain",
                "datazone:GetProject"
            ],
            "Resource": "*"
        },
        {
            "Sid": "STSForDriver",
            "Effect": "Allow",
            "Action": [
                "sts:GetCallerIdentity"
            ],
            "Resource": "*"
        }
    ]
}

The "Resource": "*" is required because these API actions do not support resource-level permissions. For more information, see Actions, resources, and condition keys for Amazon DataZone.

This doesn’t grant broad access to your data. These are read-only metadata actions that allow the ODBC driver to discover connection details and retrieve temporary Athena credentials. The actual data access is governed by Amazon SageMaker Unified Studio project membership: Users can only query data within projects where they have been explicitly added as members. The Amazon SageMaker Unified Studio project IAM role provides Athena and Amazon S3 permissions separately.

Assign users to the permission set

To assign users or groups to the target AWS account, complete the following steps:

  1. In the IAM Identity Center console, choose AWS accounts.
  2. Select the target account where your Amazon SageMaker Unified Studio IAM-based domain is deployed.
  3. Choose Assign users or groups.
  4. Select the SSO users or groups that need access.
  5. Select the SageMakerDataAnalyst permission set.
  6. Choose Submit.

Configure AWS IAM Identity Center profile

To configure the AWS IAM Identity Center profile, run the following command in your terminal on Windows:

aws configure sso

When prompted, enter the following values:

Prompt Value
SSO session name For example, smus
SSO start URL The IDC issuer URL. For example, https://identitycenter.amazonaws.com/ssoins-0example
SSO region The SSO Region. For example, us-east-1
SSO registration scopes sso:account:access

A browser window opens for authentication. After authentication, select your account and the SageMakerDataAnalyst role.

The following screenshots show the consent window and the successful authentication message.

Browser consent prompt requesting access approval during AWS CLI SSO authentication

Figure 2: Browser consent prompt

Browser page confirming successful AWS CLI SSO authentication

Figure 3: Browser authentication successful message

When prompted, enter the following values:

Prompt Value
Default client Region None
CLI default output format None
Profile Name Change value by default

The resulting ~/.aws/config file should look like the following:

[default]
sso_session = smus
sso_account_id = 1234example
sso_role_name = SageMakerDataAnalyst

[sso-session smus]
sso_start_url = https://identitycenter.amazonaws.com/ssoins-0example
sso_region = us-east-1
sso_registration_scopes = sso:account:access

Verify authentication and daily use

To verify that your SSO profile is working correctly, run the following command:

aws sts get-caller-identity

You should receive a response like the following:

{
    "UserId": "AROARHJJNFBQD6EXAMPLE:[email protected]",
    "Account": "111122223333",
    "Arn": "arn:aws:sts::111122223333:assumed-role/AWSReservedSSO_SageMakerDataAnalyst_1234example/[email protected]"
}

For daily use, no passwords or EC2 instance roles are required. When your SSO session expires, run the following command to quickly refresh it:

aws sso login

Add your IAM identity as a member of your Amazon SageMaker Unified Studio project

The IAM identity providing credentials to the ODBC driver needs project-level access to query data through Athena. If you completed the administrator setup, this is the SSO role associated with your permission set (for example, AWSReservedSSO_SageMakerDataAnalyst_1234example). If you’re using another credential source, add the IAM role or user that provides those credentials. For detailed steps, see Managing users for IAM-based domains in the Amazon SageMaker Unified Studio Administrator Guide.

The following screenshot shows the Amazon SageMaker Unified Studio domain management page, which lists the members in a project.

SageMaker Unified Studio project members list

Figure 4: List of members of your SageMaker Unified Studio project

Gather the information to authenticate

To get the parameters that you need to authenticate, complete these steps:

  1. Open your Amazon SageMaker Unified Studio Project.
  2. Open Domain Management.
  3. Choose Users.
  4. Choose View SSO connection.
  5. Copy the end of the Instance ARN, so we can build the Instance URL like https://identitycenter.amazonaws.com/ssoins-0example

The following screenshot shows the Amazon SageMaker Unified Studio domain management page with SSO connection details.

SageMaker Unified Studio domain SSO connection details showing the IAM Identity Center instance ARN

Figure 5: AWS IAM Identity Center information

  1. Choose the user icon and copy the Region as shown in the following screenshot.
SageMaker Unified Studio user menu showing the Region

Figure 6: User icon with the Region information

Method 1: DSN-based connection (Athena Power BI connector)

In this method, you configure an ODBC Data Source Name (DSN) and use the Amazon Athena connector in Power BI. This method uses SageMakerIam authentication mode and supports both DirectQuery and Import mode.

This section covers IAM-based domains. For IDC-based domains, see Part 1.

Gather configuration values to configure your Amazon Athena ODBC DSN

Before configuring the ODBC DSN, gather the following connection values from your Amazon SageMaker Unified Studio project:

  1. Open your Amazon SageMaker Unified Studio Project.
  2. Top right, select the three dots.
  3. Choose Project details.
  4. Select JDBC and ODBC details.
  5. Copy the following values: domain ID, Amazon SageMaker project ID, AWS Region, and Athena workgroup.

The following screenshot shows the Amazon SageMaker Unified Studio project overview page, which provides the project details to copy.

SageMaker Unified Studio project details showing domain ID, project ID, Region, and Athena workgroup

Figure 7: Project details with SageMaker domain ID, SageMaker project ID, Region, and Athena workgroup

Configure the ODBC DSN

Create a System DSN using the Amazon Athena ODBC driver. For the general DSN creation steps, see Configuring a data source name on Windows in the Amazon Athena User Guide. Enter the following values:

Field Value
Data Source Name Name your datasource (for example, pbi-iamdomain)
Region The AWS Region where your Amazon SageMaker domain is provisioned (for example, us-east-1)
Catalog AwsDataCatalog
Database default
Workgroup Your Athena workgroup name (for example, workgroup-abcdefghij-klmexample)

In the Authentication Options, configure the following values:

Field Value
Authentication Type SageMakerIam
SageMaker Domain ID dzd-123456example
SageMaker Project ID abcd12example
SageMaker Region Region of your SageMaker Unified Studio project (for example, us-east-1)

Choose OK, then Test to verify the connection. Choose Allow Access when prompted by the browser.

The following screenshot shows the successful connection test.

ODBC DSN configuration showing a successful connection test with SageMakerIam

Figure 8: Successful connection test in the ODBC DSN configuration with SageMakerIam authentication

Connect Power BI Desktop to your data

With the DSN configured, you can connect Power BI Desktop to your data catalog and load the generators dataset.

  1. Open Microsoft Power BI Desktop.
  2. Open the Get Data menu and select More.
  3. Search for and select Amazon Athena and choose Connect.
  4. For Data Source Name (DSN), enter pbi-iamdomain.
  5. Select DirectQuery.
  6. Choose OK.
  7. Choose Use Data Source Configuration and then Connect.
  8. In the AwsDataCatalog folder, navigate to your database.
  9. Select the core_eia860__scd_generators table.
  10. Choose Load.

The following screenshot shows Power BI Desktop successfully connected to the data catalog.

Power BI Desktop connected to the data catalog with the generators table loaded

Figure 9: Power BI Desktop connected to the data catalog with the generators table loaded using SageMakerIam authentication

Create your dashboard and publish it

You can create a dashboard to visualize U.S. power generation data. To create a visualization, complete the following steps:

  1. In the Visualizations pane, choose the Stacked bar chart.
  2. Assign the Y-Axis: Drag technology_description to the Y-Axis.
  3. Assign the X-Axis (Values): Drag capacity_mw to the X-Axis (automatically summed).
  4. Assign the Legend (Stack): Drag operational_status to the Legend field.
  5. Choose Publish.
  6. Give your report a name (for example, generation-iamdomain) and choose Save.
  7. Sign in and choose a destination workspace.

The following screenshot shows the Power BI dashboard with U.S. power generation data.

Power BI stacked bar chart of U.S. generation capacity by technology and operational status

Figure 10: Power BI dashboard with U.S. power generation data

After you publish, the report structure becomes available on Microsoft Power BI Service.

Method 2: DSN-less connection (Power BI ODBC connector)

In this method, you use the Power BI ODBC connector with a connection string (no DSN required). This method supports Import mode only and SageMakerIam authentication. Because the gateway can’t perform browser authentication and connection strings need to match, both Desktop and gateway must use SageMakerIam.

This section covers IAM-based domains. For IDC-based domains, see Part 1.

Gather configuration values to configure your DSN-less connection

Gather the following connection values from your Amazon SageMaker Unified Studio project:

  1. Open your Amazon SageMaker Unified Studio Project.
  2. Top right, select the three dots.
  3. Choose Project details.
  4. Select JDBC and ODBC details.
  5. Copy the ODBC connection string.

The following screenshot shows the Amazon SageMaker Unified Studio project overview page with the ODBC connection string to copy.

SageMaker Unified Studio project overview showing the ODBC connection string

Figure 11: Project details with ODBC connection string

Connect Power BI Desktop to your data and publish

With the configuration parameters of your project, you can connect Power BI Desktop to your data catalog and load the generators dataset.

  1. Open Power BI Desktop.
  2. Open the Get Data menu and select More.
  3. Search for and select ODBC and choose Connect.
  4. For Data Source Name (DSN), select (None).
  5. Expand Advanced Options.
  6. In the Connection string field, enter your connection string. For example, Driver={Amazon Athena ODBC (x64)};AwsRegion=us-east-1;Catalog=AwsDataCatalog;Schema=default;Workgroup=workgroup-abcdefghij-klmexample;SageMakerDomainId= dzd-123456example;SageMakerProjectId= abcd12example;SageMakerDomainRegion=us-east-1;AuthenticationType=SageMakerIam;
  7. Choose OK.
  8. Choose Default or Custom and then Connect.
  9. In the AwsDataCatalog folder, navigate to your database.
  10. Select the core_eia860__scd_generators table.
  11. Choose Load.

When publishing, name your report generation-iamdomain-dsnless.

Configure the gateway and view your report on Power BI Service

After creating your reports in Power BI Desktop, configure the on-premises data gateway to view your report on Power BI Service.

You can configure the gateway using either a DSN or a DSN-less connection string, matching the method you used in Power BI Desktop.

Create and attach an IAM role to the Power BI Gateway EC2 instance

Create an IAM role for the EC2 instance that will host your Power BI gateway. Name the role pbi-gateway-role (or a name of your choice). The role must use EC2 as the trusted entity and include the following inline policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "SageMakerAccess",
            "Effect": "Allow",
            "Action": [
                "datazone:GetConnection",
                "datazone:ListConnections",
                "datazone:GetDomain",
                "datazone:GetProject"
            ],
            "Resource": "*"
        },
        {
            "Sid": "STSForDriver",
            "Effect": "Allow",
            "Action": [
                "sts:GetCallerIdentity"
            ],
            "Resource": "*"
        }
    ]
}

Attach this role to your Power BI Gateway EC2 instance. For detailed steps on creating and attaching an IAM role to an EC2 instance, refer to IAM roles for Amazon EC2 in the Amazon EC2 User Guide.

Add the Power BI Gateway IAM role as a member of SageMaker Unified Studio project

The gateway IAM role needs project-level access to query data through Athena. The steps to add the role differ depending on your domain type.

IAM-based domain

  1. Open your Amazon SageMaker Unified Studio Project.
  2. Open Domain Management.
  3. Choose your Project Name.
  4. Choose Members.
  5. Choose Add members.
  6. Select the IAM role of your Power BI gateway (for example, pbi-gateway-role).
  7. Choose Add.

The following screenshot shows the Amazon SageMaker Unified Studio project domain management page with options to add members to a project.

SageMaker Unified Studio project members list including the Power BI gateway IAM role

Figure 12: List of members of a SageMaker Unified Studio project with the IAM gateway role

Configure the data source on Power BI Gateway

How you configure the data source depends on the method you used in Power BI Desktop.

Method 1 (DSN-based)

Configure a System DSN on the gateway EC2 instance following the same ODBC DSN steps described in Method 1. When configuring, make sure that:

  • You use the System DSN tab (not User DSN) because the gateway runs as a Windows service under a separate account.
  • The authentication type is set to SageMakerIam.
  • The DSN name matches exactly the one configured on Power BI Desktop (for example, pbi-iamdomain).

Method 2 (DSN-less)

No configuration is needed on the gateway machine itself. You configure the data source directly in Power BI Service.

Configure the data source and view your report on Power BI Service

To view your report, complete the following steps:

  1. Open the workspace where you saved your report.
  2. Search the Semantic Model which has the same name as your report (for example, generation-iamdomain) and choose the More options icon (three dots).
  3. Choose Settings.
  4. Expand Gateway and Cloud Connection.
  5. Choose View Datasources (play icon) on your gateway.
  6. Choose Manually add to gateway.
  7. Add a connection name (for example, pbi-iamdomain).

The next step depends on the method that you chose:

Method 1 (DSN-based)

  1. Add the DSN (for example, pbi-iamdomain) that matches exactly the one configured on Power BI Desktop.

Method 2 (DSN-less)

  1. In the Connection string field, enter the connection string that matches exactly the one used in Power BI Desktop.

Next, continue with the configuration:

  1. Select Anonymous as Authentication Method.
  2. Choose Create.
  3. Expand again Gateway and Cloud Connection.
  4. For Maps to, choose the connection that you created (for example, pbi-iamdomain).
  5. Choose Apply.
  6. Return to the workspace where you saved your report.
  7. On the Content section, choose your report (for example, generation-iamdomain).

The following screenshot shows a report on Power BI Service.

Published Power BI report rendering on Power BI Service

Figure 13: Power BI report on Power BI Service

You can now see your report online with the data from your Amazon SageMaker Unified Studio project.

Clean up

To avoid additional charges after testing, delete the Amazon SageMaker Unified Studio domain and EC2 instances. Refer to Delete domains and Terminate Instances for instructions.

Conclusion

In this two-part series, you connected Power BI to Amazon SageMaker Unified Studio through Amazon Athena. Part 1 covered IDC-based domains. This post covered IAM-based domains using SageMakerIam authentication. This provides a direct connection path, with no third-party licensing, while maintaining data governance and security.

You can automate many steps of this process. For information about automating DSN creation on the Power BI Gateway or Service, refer to How ENGIE automates the deployment of Amazon Athena data sources on Microsoft Power BI. If you don’t want users adding the gateway IAM role directly, you can create a custom blueprint as a self-service tool for gateway role addition. The blueprint uses a ProjectMembership resource with a configurable parameter that project owners can activate at project creation, automatically adding the gateway role as a project contributor.

For additional best practices, refer to the Using Microsoft Power BI with the AWS Cloud Whitepaper. To learn more, visit Amazon SageMaker Unified Studio and Amazon Athena.


About the authors

Ramesh H Singh

Ramesh H Singh

Ramesh is a Senior Product Manager Technical at AWS in Seattle, focused on Amazon SageMaker. He’s passionate about building analytics and AI products that help enterprise customers unlock real value from their data. Away from work, he spends his time hiking with family and exploring spirituality. Connect with him on LinkedIn.

Armando Segnini

Armando Segnini

Armando is a Senior Analytics Specialist Solutions Architect at AWS, partnering with enterprise customers to architect scalable data, analytics, and AI platforms. He helps organizations turn complex data challenges into business value through expertise in streaming, BI integration, and generative AI. Outside of work, Armando enjoys traveling with his family, exploring new cultures, photography, and functional fitness competitions.

Gaurav Sharma

Gaurav is a Specialist Solutions Architect (Analytics) at AWS, supporting US public sector customers on their cloud journey. Outside of work, Gaurav enjoys spending time with his family and reading books.

Krishna Atluru

Krishna Atluru

Krishna is an Enterprise Support Lead TAM at AWS. He provides customers with in-depth guidance on improving security posture and operational excellence for their workloads, helping them build secure, resilient, and cost-effective solutions. His areas of expertise include building serverless architectures, and data and analytics solutions. Outside of work, Krishna enjoys cooking, swimming, and traveling.

Saushthav Saxena

Saushthav Saxena

Saushthav is a Software Development Engineer at AWS on the Amazon Athena team, where he has spent the past few years working on distributed systems and data analytics at scale. Based in the San Francisco Bay Area, his background spans full-stack development, high-performance computing, and large-scale infrastructure. Outside of work, he enjoys reading sci-fi novels, swimming, and traveling with family and friends.

Customize Amazon API Gateway destinations for execution logs

Post Syndicated from Giedrius Praspaliauskas original https://aws.amazon.com/blogs/compute/customize-amazon-api-gateway-destinations-for-execution-logs/

Amazon API Gateway execution logs help you trace request processing step by step through your REST API stages. They capture authorization results, integration latency, mapping template output, and error details that are otherwise invisible at the API surface. When a production request fails in a way the access log cannot explain, the execution log is usually where you find the explanation.

Until now, execution logs had two constraints. Every log event was truncated at 1 KB, so a request carrying a moderately sized JSON body would exceed that limit and the remainder was dropped. Logs could only go to the auto-managed log group that API Gateway creates for you (API-Gateway-Execution-Logs_{rest-api-id}/{stage_name}).

With Amazon CloudWatch Logs delivery for REST API execution logs, you can now route execution logs to Amazon CloudWatch Logs, Amazon Simple Storage Service (Amazon S3), or Amazon Data Firehose. Log events can be up to 1 MB per entry, and you benefit from vended logs pricing.

In this post, you learn how CloudWatch Logs delivery works with API Gateway execution logs, how to configure it, and what patterns work best for common observability scenarios.

Understanding API Gateway execution logs

API Gateway produces two categories of logs: access logs and execution logs. Access logs record a summary line per request, similar to an HTTP server access log. You configure the format and destination yourself.

Execution logs are different. They capture the internal processing of each request as it moves through the API Gateway pipeline: authorizer evaluation, request validation, integration dispatch, response mapping, and error handling. These logs exist so you can answer questions such as “why did my authorizer reject this token?” or “what did the mapping template produce before it reached my backend integration?”

API Gateway manages execution log creation automatically. When you set loggingLevel to INFO or ERROR in your stage’s method settings, the service writes execution log events to a CloudWatch Logs log group it manages on your behalf. You do not choose the log group name or configure retention directly on it.

The auto-managed model works for many customers but may create friction for teams with specific observability requirements. Compliance frameworks that require logs in S3 with a particular prefix structure need an extra subscription filter and delivery mechanism. Sending execution logs into a security information and event management (SIEM) tool through a Firehose stream requires a forwarding layer.

Configurable log delivery with CloudWatch Logs

CloudWatch Logs delivery separates log routing from log content. Two concepts control the behavior:

DeliverySource is scoped to your API Gateway stage ARN. It defines where logs go. You create a delivery source, then attach one or more delivery destinations (CloudWatch Logs log group, S3 bucket, or Firehose stream).

MethodSettings controls what gets logged. The loggingLevel setting (INFO, ERROR, or OFF) and dataTraceEnabled flag still determine which log events API Gateway produces. These settings work the same way regardless of whether you use the auto-managed log group or CloudWatch Logs delivery.

When you create a delivery using the CloudWatch Logs APIs, CloudWatch Logs activates your log delivery on your API Gateway stage. When you delete the delivery, CloudWatch Logs disables it accordingly. You do not need to flip any flags on the API Gateway side, and the execution logs automatically resume flowing to the auto-managed log group.

Your existing method settings keep their meaning. The loggingLevel and dataTraceEnabled values continue to control log content. If loggingLevel is already INFO or ERROR, creating a delivery redirects those logs to your chosen destination with no further configuration.

The following diagram shows how the pieces fit together.

Diagram showing one API Gateway stage delivery source fanning out to CloudWatch Logs, Amazon S3, and Firehose destinations.

Figure 1 — A single delivery source scoped to an API Gateway stage feeds one or more deliveries, each of which writes to a delivery destination backed by CloudWatch Logs, Amazon S3, or Amazon Data Firehose

The following table summarizes what changes when log delivery is active.

Aspect Standard execution logging Log delivery
Destination Auto-managed CloudWatch Logs log group CloudWatch Logs, Amazon S3, or Firehose
Multi-destination No Yes
Pricing Standard CloudWatch Logs ingestion Vended logs pricing
Log event size Truncated at 1 KB Up to 1 MB
Setup Set loggingLevel in MethodSettings Create delivery through CloudWatch Logs APIs
Teardown Set loggingLevel to OFF Delete delivery

What stays the same

Only execution log routing changes. Access logs continue to flow through accessLogSettings to whatever log group you configure, and unrelated stage features such as AWS X-Ray tracing, detailed CloudWatch metrics, throttling, and caching behave exactly as they did before.

Configuration and integration options

Before you create a delivery, confirm the following requirements:

  • The API Gateway REST API is deployed to a stage.
  • loggingLevel is set to INFO or ERROR in MethodSettings.
  • The account-level CloudWatch Logs IAM role is configured. For setup steps, see Set up CloudWatch logging for REST APIs in API Gateway.
  • For cross-account delivery, the destination has an appropriate resource policy attached through PutDeliveryDestinationPolicy.

Sending logs to a custom CloudWatch Logs log group

The most common starting point is redirecting execution logs to a log group you own. You get direct control over retention policies, metric filters, and subscription filters. The following steps use the AWS Command Line Interface (AWS CLI) with the fictitious REST API ID abc123, stage prod, Region us-east-1, and account 111122223333.

  1. Create a delivery source referencing your stage ARN. The log type for REST API execution logs is EXECUTION_LOGS:
    aws logs put-delivery-source \
        --name my-apigw-execution-logs \
        --resource-arn arn:aws:apigateway:us-east-1:111122223333:/restapis/abc123/stages/prod \
        --log-type EXECUTION_LOGS

  2. Create a delivery destination pointing to your custom (existing) log group, then create the delivery that connects them:
    aws logs put-delivery-destination \
        --name my-execution-log-destination \
        --delivery-destination-configuration \
            destinationResourceArn=arn:aws:logs:us-east-1:111122223333:log-group:/my-api/execution-logs

    aws logs create-delivery \
        --delivery-source-name my-apigw-execution-logs \
        --delivery-destination-arn arn:aws:logs:us-east-1:111122223333:delivery-destination:my-execution-log-destination

  3. Verify that the delivery is active by listing deliveries for the source:
    aws logs describe-deliveries

The response includes the delivery ID, source, and destination ARN after delivery is established. Execution logs flow to /my-api/execution-logs instead of the auto-managed group.

Note: Log delivery adds structured fields (resource_arn, event_timestamp, api_id, stage, resource_path, http_method, and payload) to each event, so a new delivery emits more than your previous logs. To keep the traditional execution log format with nothing extra, set output format and record fields while creating delivery destination and creating delivery:

aws logs put-delivery-destination \
    --output-format "plain" ...

aws logs create-delivery \
    --record-fields "payload" \
    --field-delimiter "" ...

Routing logs to Amazon S3

S3 works well for long-term retention at lower cost, or for feeding logs into analytics tools such as Amazon Athena. The bucket must be in the same region as your API. Create a delivery destination pointing to your bucket:

aws logs put-delivery-destination \
    --name s3-archive-destination \
    --delivery-destination-configuration \
        destinationResourceArn=arn:aws:s3:::amzn-s3-demo-apigw-logs

Then create a delivery using the same source name. CloudWatch Logs delivers the events to your bucket, where you can query them with Athena or catalog them with AWS Glue.

Streaming to Amazon Data Firehose

For real-time analytics pipelines or third-party SIEM integration, Firehose delivery sends execution log events directly to your stream. The setup is identical: create a delivery destination with your Firehose stream ARN, then create a delivery. With direct Firehose delivery, you no longer need to maintain CloudWatch Logs subscription filters and AWS Lambda forwarders to route execution logs to external analytics systems.

Multi-destination delivery and per-destination shaping

A single delivery source supports multiple destinations. You can route the same execution logs to CloudWatch Logs for real-time alerting, S3 for long-term compliance retention, and Firehose for your SIEM, all from one stage. Create additional deliveries using the same delivery source with different destination ARNs.

Each destination receives identical log events. To shape what reaches each destination, apply a CloudWatch Logs subscription filter on the CloudWatch Logs destination. For example, you can forward only ERROR-level events to a Lambda function that pushes alerts to a SIEM, while the same delivery source writes the full event stream to S3 for compliance.

Management console experience

You can also add a log delivery destination in the management console after you enable logging for the stage.

API Gateway console showing the option to add a log delivery destination after logging is enabled for the stage.

You can specify multiple destinations, both in the current or in a different account:

API Gateway console showing multiple delivery destinations configured, including cross-account options.

Keeping existing monitoring intact

If you have dashboards or alarms on the auto-managed log group, use that same log group as one of your delivery destinations. Your existing monitoring keeps working, and you gain the ability to send logs to additional destinations such as S3 or Firehose in parallel.

Best practices

Update dashboards and alarms before enabling log delivery. When you activate log delivery, the auto-managed log group stops receiving logs. Any CloudWatch alarms, dashboards, or Contributor Insights rules pointing to API-Gateway-Execution-Logs_{rest-api-id}/{stage_name} stop working. Migrate these references to your new log group before creating the delivery.

Keep loggingLevel at INFO or ERROR. Log delivery controls routing, not content. If loggingLevel is OFF, no execution log events are produced regardless of whether a delivery exists. Verify your method settings before troubleshooting missing logs.

Treat the 1 MB log event capacity as a security decision, not only a debugging convenience. With dataTraceEnabled set to true, execution logs include complete request and response payloads up to 1 MB. Those payloads might contain personally identifiable information (PII) or other sensitive data. Confirm your log destinations have appropriate access controls, encryption, and retention policies. Mask or filter sensitive fields in mapping templates upstream of logging and enable data tracing selectively per method or only in non-production stages.

Start with a single destination, then expand. Validate that your log group or bucket receives events correctly before adding Firehose or additional destinations.

Log delivery is best-effort. In rare cases, some log events might not be delivered. For audit-critical workloads, build retention and reconciliation that account for occasional missing events rather than treating execution logs as the system of record.

Cleaning up

To avoid ongoing charges from the resources you created while following this post, delete the delivery and then remove the destinations and any example S3 bucket or Data Firehose delivery stream you no longer need. Deleting the delivery returns the stage to standard auto-managed logging.

aws logs delete-delivery --id <delivery-id>

When the delivery is deleted, CloudWatch Logs disables log delivery on the API Gateway stage automatically. The delivery source and delivery destination remain as independent objects. Delete them with delete-delivery-source and delete-delivery-destination if you do not plan to reuse them.

Conclusion

CloudWatch Logs delivery for API Gateway REST API execution logs helps address the 1 KB event truncation and single managed destination constraints. You can now route full execution logs to CloudWatch Logs, Amazon S3, or Amazon Data Firehose, use multiple destinations from a single stage, and pay vended logs pricing.

The feature works alongside existing method settings. No changes to your current logging configuration are required beyond creating the delivery itself.

To get started, refer to Route execution logs with Amazon CloudWatch Logs delivery in the API Gateway documentation. For more about CloudWatch Logs delivery configuration, see Enable logging from AWS services. For pricing details, review the Amazon CloudWatch pricing page. Try it on a test stage and share your experience in the comments.

Bring your own client certificate for backend mTLS in Amazon API Gateway

Post Syndicated from Biswanath Mukherjee original https://aws.amazon.com/blogs/compute/bring-your-own-client-certificate-for-backend-mtls-in-amazon-api-gateway/

Enterprises that use Amazon API Gateway in front of internal or partner backends often want to bring their own client certificate for backend mutual TLS (mTLS) authentication. During mTLS, the backend presents its own server certificate and also requests the caller to present a client certificate to validate it against a trusted certificate authority (CA). Until now, you could use only an API Gateway-generated, self-signed SSL certificate for the outbound connection, because there was no CA behind it for the backend to trust. Backends that enforce a specific corporate or partner CA reject that self-signed certificate, and the mutual TLS handshake fails. Bringing your own CA-signed certificate is necessary for scenarios such as migrating APIs off legacy gateways or meeting your internal PKI mandates that require certificates from an approved CA.

With API Gateway, you can now bring your own client certificate for backend mutual TLS (mTLS) authentication. You can either use a third-party certificate or a certificate issued by AWS Private Certificate Authority. If you’re using a third-party certificate, you must import the certificate in AWS Certificate Manager (ACM). Then you configure the ACM certificate ARN in your REST API stage. API Gateway presents that certificate during the backend mTLS handshake.

Solution overview

In this post, you build a REST API with an outbound mTLS connection using this newly launched API Gateway feature. This solution demonstrates an outbound mTLS connection between Amazon API Gateway and a backend application running on Amazon Elastic Container Service (Amazon ECS).

The following diagram shows the solution architecture.

Architecture diagram showing API Gateway presenting an ACM client certificate to a Network Load Balancer that forwards traffic to an NGINX sidecar and validator app on Amazon ECS Fargate, with certificates issued by AWS Private CA through ACM

The solution uses:

  1. AWS Private Certificate Authority with a root-subordinate CA hierarchy to issue both the client and server certificates through AWS Certificate Manager (ACM).

  2. Amazon API Gateway REST API stage configured with the ACM client certificate ARN (ClientCertificateId), so that the API Gateway presents the certificate during the outbound TLS handshake.

  3. Amazon ECS on AWS Fargate running an NGINX sidecar that holds the server certificate and validates the incoming client certificate against a CA bundle (root and subordinate chain).

A request goes through the following steps:

  1. Client application invokes the REST API exposed by API Gateway. The API Gateway stage is configured with an ACM client certificate ARN.

  2. API Gateway opens an outbound connection to the Network Load Balancer (NLB) to begin the TLS handshake. The API Gateway presents an ACM client certificate configured at the stage level when the backend requests one.

  3. The NLB listens for the incoming TCP request on port 443 and forwards the call to Amazon ECS Fargate. The NLB acts as a passthrough and does not terminate the TLS connection.

  4. The NGINX sidecar container running on Amazon ECS performs the inbound mTLS handshake:

  • NGINX presents the backend server certificate and verifies the client certificate against a mounted CA bundle (root and subordinate chain).

  • After verification, NGINX forwards the request and parsed certificate details to the validator app container over local HTTP.

  • The validator app re-checks the certificate validity window, matches the common name against an allowlist, and returns a structured JSON response.

Note: The NGINX sidecar is not mandatory for this flow. It demonstrates separation of concerns: NGINX handles the mTLS handshake, and the validator app contains the business logic.

Prerequisites for demo

To follow along, you need the following:

Environment setup

Run the following commands to set up the demo environment:

  1. Create a new folder and clone the GitHub repository:

    git clone https://github.com/aws-samples/sample-api-backend-mtls
    cd sample-api-backend-mtls

  2. Set the environment variables after replacing the placeholders:

    ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
    REGION=<Your AWS Region, for example, us-east-1>
    STACK_NAME=<Your stack name e.g. outbound-mtls-backend>

Build the container images

Run the following commands to create container images of NGINX sidecar container and the validator app containers:

  1. Create two Amazon Elastic Container Registry (Amazon ECR) repositories, one for NGINX and another for validator app containers respectively:

    NGINX_REPO_URI=$(aws ecr create-repository \
      --repository-name $STACK_NAME-nginx-sidecar \
      --image-tag-mutability IMMUTABLE \
      --image-scanning-configuration scanOnPush=true \
      --region "$REGION" \
      --query "repository.repositoryUri" --output text)
    
    VALIDATOR_REPO_URI=$(aws ecr create-repository \
      --repository-name $STACK_NAME-validator-app \
      --image-tag-mutability IMMUTABLE \
      --image-scanning-configuration scanOnPush=true \
      --region "$REGION" \
      --query "repository.repositoryUri" --output text)
    
    aws ecr get-login-password --region "$REGION" \
      | docker login --username AWS --password-stdin "${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com"

  2. Build and push the NGINX and validator app containers:

    docker build --platform linux/amd64 -t $STACK_NAME-nginx-sidecar nginx/
    docker tag $STACK_NAME-nginx-sidecar:latest "${NGINX_REPO_URI}:latest"
    docker push "${NGINX_REPO_URI}:latest"
    docker build --platform linux/amd64 -t $STACK_NAME-validator-app validator_app/
    docker tag $STACK_NAME-validator-app:latest "${VALIDATOR_REPO_URI}:latest"
    docker push "${VALIDATOR_REPO_URI}:latest"

Deploy and test the solution

You first deploy the stack without the client certificate configured in the API Gateway and perform negative testing. The mTLS handshake will fail because of a missing client certificate in the request. Then you update the stack to configure client certificate in API Gateway stage and retest mTLS.

  1. Run the following command to build and deploy the overall stack without client certificate configured at API Gateway stage:

    sam build
    sam deploy \
      --stack-name $STACK_NAME \
      --resolve-s3 \
      --capabilities CAPABILITY_IAM \
      --region "$REGION" \
      --parameter-overrides \
      NginxRepositoryUri="$NGINX_REPO_URI" \
      ValidatorRepositoryUri="$VALIDATOR_REPO_URI" \
      EnableOutboundMtls=false

  2. Wait for the task to reach RUNNING and pass its target group health check:

    EcsClusterName=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region "$REGION" \
      --query "Stacks[0].Outputs[?OutputKey=='EcsClusterName'].OutputValue" \
      --output text)
    
    TargetGroupArn=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region "$REGION" \
      --query "Stacks[0].Outputs[?OutputKey=='TargetGroupArn'].OutputValue" \
      --output text)
    
    aws ecs list-tasks --cluster "$EcsClusterName" --region "$REGION"
    
    aws elbv2 describe-target-health \
      --target-group-arn "$TargetGroupArn" --region "$REGION"

  3. Capture the front API invoke URL from the stack outputs:

    FRONT_API_URL=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region "$REGION" \
      --query "Stacks[0].Outputs[?OutputKey=='FrontApiUrl'].OutputValue" \
      --output text)
    
    NLB_DNS_NAME=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region "$REGION" \
      --query "Stacks[0].Outputs[?OutputKey=='NlbDnsName'].OutputValue" \
      --output text)
    
    FRONT_CLIENT_CERT_ARN=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region "$REGION" \
      --query "Stacks[0].Outputs[?OutputKey=='FrontClientCertArn'].OutputValue" \
      --output text)
    
    FRONT_API_ID=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region "$REGION" \
      --query "Stacks[0].Outputs[?OutputKey=='FrontApiId'].OutputValue" \
      --output text)

  4. Wait a minute or two after the stack finishes, then invoke the front API:

    curl -v "$FRONT_API_URL"

    The following is the NGINX configuration for mTLS:

    ...
    server {
        listen 443 ssl;
        # Server identity (issued by the private CA).
        ssl_certificate /etc/nginx/certs/server.crt;
        ssl_certificate_key /etc/nginx/certs/server.key;
        # Inbound mutual TLS: require and validate the client certificate
        # against the CA bundle (root + subordinate CA chain).
        ssl_client_certificate /etc/nginx/certs/ca_bundle.pem;
        ssl_verify_client on;
        ssl_verify_depth 2;
        ssl_protocols TLSv1.2;
    ...}

    The curl command returns HTTP/2 400, with a response body containing 400 No required SSL certificate was sent. Because the API Gateway is not presenting a client certificate on the outbound handshake, the NGINX sidecar container in Amazon ECS rejects the mTLS connection. The following screenshot shows the response:

    Terminal response showing an HTTP/2 400 error with the message No required SSL certificate was sent
  5. Now redeploy the solution with outbound mTLS enabled:

    sam deploy \
      --stack-name $STACK_NAME \
      --resolve-s3 \
      --capabilities CAPABILITY_IAM \
      --region "$REGION" \
      --parameter-overrides \
      NginxRepositoryUri="$NGINX_REPO_URI" \
      ValidatorRepositoryUri="$VALIDATOR_REPO_URI" \
      EnableOutboundMtls=true

  6. Wait a minute or two after the stack finishes, then invoke the front API again:

    curl -v "$FRONT_API_URL"

    Because the client certificate is now presented during the mTLS handshake, the handshake completes successfully, as shown in the following response snippet:

    Terminal response showing a successful mTLS handshake and an HTTP 200 response from the backend

Automatic certificate renewal

When a certificate changes in ACM, API Gateway detects the update and propagates the new certificate automatically. You do not redeploy the stage, and the API experiences no downtime during rotation. Certificate propagation is eventually consistent. During an update, the backend might briefly receive either the old or the new certificate. ACM also emits certificate expiration notifications through Amazon EventBridge, which you can use to set alarms before a certificate expires.

Clean up

If you followed along only for demonstration purposes, to avoid incurring future charges, run the following commands to delete the resources created in this demo:

  1. Clean up the S3 buckets:

    NLB_LOGS_BUCKET=$(aws cloudformation describe-stacks \
      --stack-name $STACK_NAME --region $REGION \
      --query "Stacks[0].Outputs[?OutputKey=='NlbAccessLogsBucketName'].OutputValue" \
      --output text)
    
    aws s3api list-object-versions --bucket "$NLB_LOGS_BUCKET" \
      --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}' \
      --output json | \
      jq -c '.Objects[]? // empty' | \
      while read -r obj; do
        aws s3api delete-object --bucket "$NLB_LOGS_BUCKET" \
          --key "$(echo "$obj" | jq -r .Key)" \
          --version-id "$(echo "$obj" | jq -r .VersionId)" \
          --region $REGION
      done
    
    aws s3api list-object-versions --bucket "$NLB_LOGS_BUCKET" \
      --query '{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}' \
      --output json | \
      jq -c '.Objects[]? // empty' | \
      while read -r obj; do
        aws s3api delete-object --bucket "$NLB_LOGS_BUCKET" \
          --key "$(echo "$obj" | jq -r .Key)" \
          --version-id "$(echo "$obj" | jq -r .VersionId)" \
          --region $REGION
      done

  2. Delete the stack:

    sam delete --stack-name $STACK_NAME --region $REGION --no-prompts

  3. Delete the ECR repository:

    aws ecr delete-repository --repository-name $STACK_NAME-nginx-sidecar --force --region $REGION
    aws ecr delete-repository --repository-name $STACK_NAME-validator-app --force --region $REGION

Conclusion

In this post, you configured a REST API with an outbound mTLS connection using Amazon API Gateway and an ECS Fargate backend. With this new feature launch in API Gateway, you can now bring your own client certificate for outbound mTLS handshake for your REST APIs. You can now meet your internal PKI mandates to authenticate backends that pin a specific certificate issuer.

To get started, import a certificate from your own PKI into ACM and configure your API Gateway REST API stage for outbound mTLS authentication. For more information, see Present client certificates to backend services with mutual TLS in API Gateway. If you have feedback about this post, leave it in the comments section. For technical questions, you can start a thread on AWS re:Post.

Further reading