diff --git a/inspector-sfn-ecr-quarantine-cdk/README.md b/inspector-sfn-ecr-quarantine-cdk/README.md new file mode 100644 index 0000000000..d25475ac51 --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/README.md @@ -0,0 +1,109 @@ +# Auto-quarantine vulnerable Amazon ECR images with Amazon Inspector, Amazon EventBridge, and AWS Step Functions + +This pattern automatically quarantines a vulnerable container image the moment Amazon Inspector raises a HIGH or CRITICAL finding on it. An Amazon Inspector finding on an Amazon ECR container image is routed by Amazon EventBridge to an AWS Step Functions workflow that re-tags the offending image with a `quarantine` tag and sends an Amazon SNS notification. It is the first Amazon Inspector serverless pattern that **acts** on a finding rather than only notifying. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/inspector-sfn-ecr-quarantine-cdk + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Architecture + +``` +Amazon ECR image (vulnerable) + │ enhanced scanning + ▼ +Amazon Inspector ──finding──▶ Amazon EventBridge rule + (source: aws.inspector2, + detail-type: "Inspector2 Finding", + severity: HIGH | CRITICAL, + resource type: AWS_ECR_CONTAINER_IMAGE) + │ + ▼ + AWS Step Functions (STANDARD) + 1. Choice: severity HIGH/CRITICAL? + 2. ecr:BatchGetImage (read manifest by digest) + 3. ecr:PutImage (re-tag same manifest as "quarantine") + 4. Amazon SNS publish (quarantined / failed) + Retry + Catch → failure notification +``` + +The workflow reads the finding fields directly from the Amazon EventBridge event: +`$.detail.severity`, `$.detail.resources[0].details.awsEcrContainerImage.repositoryName`, and `$.detail.resources[0].details.awsEcrContainerImage.imageHash`. + +## Design decisions + +- **How the image is quarantined (no AWS Lambda function required).** A container image in Amazon ECR is content-addressed by its digest; a tag is a named pointer to a manifest. To mark an existing image without rebuilding or copying layers, the workflow calls `ecr:BatchGetImage` to read the manifest by digest, then `ecr:PutImage` to write the **same** manifest bytes back under a new `quarantine` tag. Both calls are available as AWS Step Functions optimized AWS SDK service integrations, so the entire remediation runs from the state machine with no AWS Lambda function. +- **Tag mutability.** The demo Amazon ECR repository is created with MUTABLE tags so the `ecr:PutImage` re-tag can succeed. `ecr:PutImageTagMutability` is a repository-level setting (not per-image), so flipping the repository to IMMUTABLE mid-workflow would block the re-tag itself. The `quarantine` tag is therefore the durable per-image marker that downstream Amazon ECR lifecycle policies or deployment admission controllers can key off. +- **Rebuild/patch branch.** A documented extension point (`RebuildBranchHint`) is included where a real deployment would kick off a patched-image rebuild (for example, an AWS CodeBuild project). It is intentionally a no-op so the pattern stays focused on the quarantine headline. + +## Requirements + +- [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +- [Git installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [Node.js 18+](https://nodejs.org/en/download/) installed +- [AWS Cloud Development Kit (AWS CDK) v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed +- **Amazon Inspector enhanced scanning for Amazon ECR must be enabled at the account level.** Enable it in the [Amazon Inspector console](https://console.aws.amazon.com/inspector/v2/home) (Account management → Activate → enable ECR scanning), or with `aws inspector2 enable --resource-types ECR`. Without this, no findings are generated and the workflow is never triggered. + +## Deployment Instructions + +1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository: + ``` + git clone https://github.com/aws-samples/serverless-patterns + ``` +2. Change directory to the pattern directory: + ``` + cd inspector-sfn-ecr-quarantine-cdk/cdk + ``` +3. Install dependencies: + ``` + npm install + ``` +4. Deploy the stack to your default AWS account and Region. The output of this command should give you the Amazon ECR repository name, the AWS Step Functions state machine ARN, and the Amazon SNS topic ARN: + ``` + npx cdk deploy + ``` + +## Testing + +The headline of this pattern is that a HIGH/CRITICAL Amazon Inspector finding on an Amazon ECR image automatically results in that image being tagged `quarantine`. + +1. Subscribe your email to the Amazon SNS topic from the stack output so you receive notifications: + ``` + aws sns subscribe --topic-arn --protocol email --notification-endpoint you@example.com + ``` + Confirm the subscription from the email you receive. + +2. Authenticate Docker to Amazon ECR and push a known-vulnerable image to the demo repository (using the `RepositoryUri` output). A deliberately old base image reliably produces HIGH/CRITICAL findings: + ``` + aws ecr get-login-password --region | docker login --username AWS --password-stdin + docker pull public.ecr.aws/docker/library/ubuntu:20.04 + docker tag public.ecr.aws/docker/library/ubuntu:20.04 :vulnerable + docker push :vulnerable + ``` + +3. Amazon Inspector scans the image on push. When it reports a HIGH or CRITICAL finding (this can take a few minutes), Amazon EventBridge triggers the AWS Step Functions workflow. Watch the execution in the [AWS Step Functions console](https://console.aws.amazon.com/states/home) — you should see the `BatchGetImage` → `PutImage` → `NotifyQuarantined` path succeed. + +4. Verify the image now carries the `quarantine` tag, alongside its original tag, pointing at the same digest: + ``` + aws ecr describe-images --repository-name \ + --query 'imageDetails[].imageTags' + ``` + You should see `quarantine` in the tag list, and you will receive an Amazon SNS email confirming the quarantine with the repository, digest, and severity. + +## Cleanup + +1. Delete the stack. The demo Amazon ECR repository is created with `RemovalPolicy.DESTROY` and `emptyOnDelete`, so the repository **and any images you pushed into it (including the vulnerable test image) will be permanently deleted**: + ``` + cd inspector-sfn-ecr-quarantine-cdk/cdk + npx cdk destroy + ``` +2. Optionally, disable Amazon Inspector ECR enhanced scanning if you enabled it only for this test: + ``` + aws inspector2 disable --resource-types ECR + ``` + +---- +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/inspector-sfn-ecr-quarantine-cdk/cdk/.gitignore b/inspector-sfn-ecr-quarantine-cdk/cdk/.gitignore new file mode 100644 index 0000000000..3a671bb3bd --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/cdk/.gitignore @@ -0,0 +1,9 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out +cdk.context.json diff --git a/inspector-sfn-ecr-quarantine-cdk/cdk/bin/app.ts b/inspector-sfn-ecr-quarantine-cdk/cdk/bin/app.ts new file mode 100644 index 0000000000..b2c545146a --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/cdk/bin/app.ts @@ -0,0 +1,17 @@ +#!/usr/bin/env node +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { InspectorSfnEcrQuarantineStack } from '../lib/inspector-sfn-ecr-quarantine-stack'; + +const app = new cdk.App(); + +new InspectorSfnEcrQuarantineStack(app, 'InspectorSfnEcrQuarantineStack', { + // The stack is environment-agnostic. It reads no hardcoded account/region; + // resource ARNs are built from Aws.ACCOUNT_ID / Aws.REGION at synth time. + description: + 'Auto-quarantine vulnerable Amazon ECR images: Amazon Inspector finding -> Amazon EventBridge -> AWS Step Functions -> Amazon ECR re-tag + Amazon SNS notify', +}); + +app.synth(); diff --git a/inspector-sfn-ecr-quarantine-cdk/cdk/cdk.json b/inspector-sfn-ecr-quarantine-cdk/cdk/cdk.json new file mode 100644 index 0000000000..a2491fd5a4 --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/cdk/cdk.json @@ -0,0 +1,22 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeVersionProps": true, + "@aws-cdk/core:stackRelativeExports": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:target-partitions": ["aws", "aws-cn"] + } +} diff --git a/inspector-sfn-ecr-quarantine-cdk/cdk/lib/inspector-sfn-ecr-quarantine-stack.ts b/inspector-sfn-ecr-quarantine-cdk/cdk/lib/inspector-sfn-ecr-quarantine-stack.ts new file mode 100644 index 0000000000..c583273863 --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/cdk/lib/inspector-sfn-ecr-quarantine-stack.ts @@ -0,0 +1,293 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: MIT-0 +import { + Stack, + StackProps, + RemovalPolicy, + Duration, + CfnOutput, +} from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ecr from 'aws-cdk-lib/aws-ecr'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as kms from 'aws-cdk-lib/aws-kms'; + +/** + * Inspector -> EventBridge -> Step Functions -> ECR quarantine + SNS. + * + * When Amazon Inspector raises a HIGH or CRITICAL finding on an Amazon ECR + * container image, Amazon EventBridge routes the finding to an AWS Step + * Functions STANDARD workflow that QUARANTINES the offending image by applying + * a `quarantine` tag to it, then sends an Amazon SNS notification. + * + * How the ECR "quarantine tag" is implemented (verified against the ECR API): + * An OCI/Docker image in Amazon ECR is identified by its content-addressable + * digest (imageHash, e.g. sha256:...). A "tag" is just a named pointer to a + * manifest. To add a tag to an EXISTING image WITHOUT rebuilding or copying + * image data you: + * 1. ecr:BatchGetImage - read the image manifest by its digest. + * 2. ecr:PutImage - write the SAME manifest bytes back under a new + * imageTag ("quarantine"). This is a pure + * control-plane call; no layers are moved. + * Both calls are available as Step Functions optimized AWS SDK service + * integrations (arn:aws:states:::aws-sdk:ecr:batchGetImage / :putImage), so + * the whole quarantine action runs from the state machine with no AWS + * Lambda function. + * + * Note on tag mutability: the demo repository is created with MUTABLE tags so + * the PutImage re-tag can succeed. PutImageTagMutability is a REPOSITORY-level + * setting (not per-image), so flipping the repo to IMMUTABLE mid-workflow + * would block the very re-tag we depend on. The `quarantine` tag is therefore + * the durable per-image marker; downstream lifecycle policies or admission + * controllers can key off it. See README "Design decisions". + */ +export class InspectorSfnEcrQuarantineStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + // --------------------------------------------------------------------- + // Demo target: an Amazon ECR repository. + // Tags are MUTABLE so the quarantine re-tag (PutImage) can succeed. + // scanOnPush requests a scan on push; enhanced scanning by Amazon + // Inspector must additionally be ENABLED AT THE ACCOUNT LEVEL (see README). + // --------------------------------------------------------------------- + const repository = new ecr.Repository(this, 'DemoRepository', { + // A static, lowercase, valid ECR repository name. Aws.STACK_NAME cannot be + // used here: it is an unresolved CloudFormation token at synth time and + // fails ECR's synth-time repositoryName validation. + repositoryName: 'inspector-quarantine-demo', + imageTagMutability: ecr.TagMutability.MUTABLE, + imageScanOnPush: true, + emptyOnDelete: true, // allow the repo (and its images) to be removed on cleanup + removalPolicy: RemovalPolicy.DESTROY, // DEMO ONLY - deletes the repo + images on `cdk destroy` + }); + + // --------------------------------------------------------------------- + // Amazon SNS topic for quarantine notifications. + // Encrypted at rest with the AWS-managed KMS key for Amazon SNS + // (alias/aws/sns) - no extra cost and no customer key to manage. + // --------------------------------------------------------------------- + const topic = new sns.Topic(this, 'QuarantineTopic', { + displayName: 'ECR image quarantine notifications', + masterKey: kms.Alias.fromAliasName(this, 'SnsManagedKey', 'alias/aws/sns'), + }); + + // --------------------------------------------------------------------- + // Step Functions tasks. + // + // The EventBridge event delivers the finding under $.detail. The fields we + // rely on (verified against the Inspector2 Finding event schema): + // $.detail.severity + // $.detail.resources[0].details.awsEcrContainerImage.repositoryName + // $.detail.resources[0].details.awsEcrContainerImage.imageHash (sha256:...) + // --------------------------------------------------------------------- + + // 1) Read the image manifest by digest (BatchGetImage). + const getManifest = new tasks.CallAwsService(this, 'GetImageManifest', { + service: 'ecr', + action: 'batchGetImage', + // ACCEPTED media types cover both Docker v2 and OCI manifests. + parameters: { + RepositoryName: sfn.JsonPath.stringAt( + '$.detail.resources[0].details.awsEcrContainerImage.repositoryName', + ), + ImageIds: [ + { + ImageDigest: sfn.JsonPath.stringAt( + '$.detail.resources[0].details.awsEcrContainerImage.imageHash', + ), + }, + ], + AcceptedMediaTypes: [ + 'application/vnd.docker.distribution.manifest.v2+json', + 'application/vnd.oci.image.manifest.v1+json', + 'application/vnd.oci.image.index.v1+json', + 'application/vnd.docker.distribution.manifest.list.v2+json', + ], + }, + iamResources: [repository.repositoryArn], + resultPath: '$.manifest', + }); + + // 2) Re-tag the SAME manifest under the "quarantine" tag (PutImage). + // ImageManifest is the manifest string returned by BatchGetImage above. + const applyQuarantineTag = new tasks.CallAwsService( + this, + 'ApplyQuarantineTag', + { + service: 'ecr', + action: 'putImage', + parameters: { + RepositoryName: sfn.JsonPath.stringAt( + '$.detail.resources[0].details.awsEcrContainerImage.repositoryName', + ), + ImageManifest: sfn.JsonPath.stringAt( + '$.manifest.Images[0].ImageManifest', + ), + ImageTag: 'quarantine', + }, + iamResources: [repository.repositoryArn], + resultPath: '$.putImage', + }, + ); + + // Retry transient ECR/throttling errors on the two ECR calls. + const retryProps: sfn.RetryProps = { + errors: ['States.ALL'], + interval: Duration.seconds(2), + maxAttempts: 3, + backoffRate: 2, + }; + getManifest.addRetry(retryProps); + applyQuarantineTag.addRetry(retryProps); + + // 3) Success notification via Amazon SNS. + const notifyQuarantined = new tasks.SnsPublish(this, 'NotifyQuarantined', { + topic, + subject: 'ECR image quarantined', + message: sfn.TaskInput.fromObject({ + status: 'QUARANTINED', + repository: sfn.JsonPath.stringAt( + '$.detail.resources[0].details.awsEcrContainerImage.repositoryName', + ), + imageDigest: sfn.JsonPath.stringAt( + '$.detail.resources[0].details.awsEcrContainerImage.imageHash', + ), + severity: sfn.JsonPath.stringAt('$.detail.severity'), + quarantineTag: 'quarantine', + findingTitle: sfn.JsonPath.stringAt('$.detail.title'), + }), + }); + + // 4) Failure notification path (Catch target for the quarantine steps). + const notifyFailure = new tasks.SnsPublish(this, 'NotifyFailure', { + topic, + subject: 'ECR image quarantine FAILED', + message: sfn.TaskInput.fromObject({ + status: 'QUARANTINE_FAILED', + severity: sfn.JsonPath.stringAt('$.detail.severity'), + error: sfn.JsonPath.stringAt('$.error'), + detail: sfn.JsonPath.stringAt('$.detail'), + }), + }); + const fail = new sfn.Fail(this, 'QuarantineFailed', { + cause: 'ECR image quarantine failed. See the Amazon SNS failure notification.', + error: 'QuarantineError', + }); + notifyFailure.next(fail); + + // 5) Optional rebuild/patch branch placeholder. This pattern quarantines; + // a real deployment would trigger a CI rebuild here (e.g. start a + // CodeBuild project or notify a pipeline). Kept as a documented no-op + // so the composition stays focused on the remediation headline. + const rebuildHint = new sfn.Pass(this, 'RebuildBranchHint', { + comment: + 'Extension point: kick off a patched-image rebuild (CodeBuild / pipeline) here.', + result: sfn.Result.fromObject({ + note: 'Rebuild/patch branch is an intentional extension point. See README.', + }), + resultPath: '$.rebuild', + }); + + // Wire the quarantine happy path, attaching a Catch to the failure path. + const quarantineFlow = getManifest + .next(applyQuarantineTag) + .next(rebuildHint) + .next(notifyQuarantined); + + getManifest.addCatch(notifyFailure, { resultPath: '$.error' }); + applyQuarantineTag.addCatch(notifyFailure, { resultPath: '$.error' }); + + // Severity Choice: act only on HIGH/CRITICAL; otherwise skip. + const skip = new sfn.Pass(this, 'SkipLowSeverity', { + comment: 'Severity below HIGH - no quarantine action taken.', + }); + + const definition = new sfn.Choice(this, 'IsHighOrCritical') + .when( + sfn.Condition.or( + sfn.Condition.stringEquals('$.detail.severity', 'HIGH'), + sfn.Condition.stringEquals('$.detail.severity', 'CRITICAL'), + ), + quarantineFlow, + ) + .otherwise(skip); + + // --------------------------------------------------------------------- + // STANDARD state machine with full execution logging. + // --------------------------------------------------------------------- + const logGroup = new logs.LogGroup(this, 'StateMachineLogs', { + retention: logs.RetentionDays.ONE_WEEK, + removalPolicy: RemovalPolicy.DESTROY, // DEMO ONLY + }); + + const stateMachine = new sfn.StateMachine(this, 'QuarantineStateMachine', { + definitionBody: sfn.DefinitionBody.fromChainable(definition), + stateMachineType: sfn.StateMachineType.STANDARD, + timeout: Duration.minutes(5), + logs: { + destination: logGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + tracingEnabled: true, + }); + + // Least-privilege: the CallAwsService tasks already added ECR actions + // scoped to the repository ARN via `iamResources`. SnsPublish grants + // publish scoped to the topic automatically. No wildcards are used. + + // --------------------------------------------------------------------- + // Amazon EventBridge rule: Inspector2 HIGH/CRITICAL findings on ECR images. + // --------------------------------------------------------------------- + const rule = new events.Rule(this, 'InspectorEcrFindingRule', { + description: + 'Route HIGH/CRITICAL Amazon Inspector findings on Amazon ECR container images to the quarantine workflow', + eventPattern: { + source: ['aws.inspector2'], + detailType: ['Inspector2 Finding'], + detail: { + severity: ['HIGH', 'CRITICAL'], + resources: { + type: ['AWS_ECR_CONTAINER_IMAGE'], + }, + }, + }, + }); + + // EventBridge -> Step Functions. The construct creates a scoped role that + // may only StartExecution on THIS state machine. A dead-letter queue + // captures any event that Amazon EventBridge cannot deliver to the + // workflow (for example, a transient StartExecution throttle) so no + // finding is silently lost. + const dlq = new sqs.Queue(this, 'InspectorRuleDlq', { + retentionPeriod: Duration.days(14), + enforceSSL: true, + }); + rule.addTarget( + new targets.SfnStateMachine(stateMachine, { + deadLetterQueue: dlq, + }), + ); + + // --------------------------------------------------------------------- + // Outputs. + // --------------------------------------------------------------------- + new CfnOutput(this, 'RepositoryName', { + value: repository.repositoryName, + description: 'Push a vulnerable image here to trigger the workflow', + }); + new CfnOutput(this, 'RepositoryUri', { value: repository.repositoryUri }); + new CfnOutput(this, 'StateMachineArn', { value: stateMachine.stateMachineArn }); + new CfnOutput(this, 'SnsTopicArn', { + value: topic.topicArn, + description: 'Subscribe to this topic to receive quarantine notifications', + }); + } +} diff --git a/inspector-sfn-ecr-quarantine-cdk/cdk/package.json b/inspector-sfn-ecr-quarantine-cdk/cdk/package.json new file mode 100644 index 0000000000..1fc6902b3f --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/cdk/package.json @@ -0,0 +1,23 @@ +{ + "name": "inspector-sfn-ecr-quarantine-cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/app.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk" + }, + "devDependencies": { + "@types/node": "20.14.9", + "aws-cdk": "2.170.0", + "ts-node": "^10.9.2", + "typescript": "~5.5.3" + }, + "dependencies": { + "aws-cdk-lib": "2.170.0", + "constructs": "^10.3.0", + "source-map-support": "^0.5.21" + } +} diff --git a/inspector-sfn-ecr-quarantine-cdk/cdk/tsconfig.json b/inspector-sfn-ecr-quarantine-cdk/cdk/tsconfig.json new file mode 100644 index 0000000000..11e87c6d92 --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/cdk/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": ["./node_modules/@types"] + }, + "exclude": ["node_modules", "cdk.out"] +} diff --git a/inspector-sfn-ecr-quarantine-cdk/example-pattern.json b/inspector-sfn-ecr-quarantine-cdk/example-pattern.json new file mode 100644 index 0000000000..9514530be7 --- /dev/null +++ b/inspector-sfn-ecr-quarantine-cdk/example-pattern.json @@ -0,0 +1,116 @@ +{ + "title": "Auto-quarantine vulnerable Amazon ECR images", + "description": "An Amazon Inspector finding on a vulnerable Amazon ECR image triggers an AWS Step Functions workflow via Amazon EventBridge that quarantines the image and sends Amazon SNS.", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "patternType": "Standalone", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern automatically quarantines a vulnerable container image the moment Amazon Inspector raises a HIGH or CRITICAL finding on it.", + "An Amazon Inspector finding on an Amazon ECR container image is routed by an Amazon EventBridge rule (source aws.inspector2, detail-type Inspector2 Finding, filtered to HIGH/CRITICAL severity and the AWS_ECR_CONTAINER_IMAGE resource type) to an AWS Step Functions STANDARD workflow.", + "The workflow reads the image manifest by digest with ecr:BatchGetImage and writes the same manifest back under a quarantine tag with ecr:PutImage - a pure control-plane re-tag that needs no AWS Lambda function - then publishes an Amazon SNS notification. Retry and Catch drive a failure-notification path.", + "It is the first Amazon Inspector serverless pattern that acts on a finding rather than only notifying." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/inspector-sfn-ecr-quarantine-cdk", + "templateURL": "serverless-patterns/inspector-sfn-ecr-quarantine-cdk", + "projectFolder": "inspector-sfn-ecr-quarantine-cdk", + "templateFile": "cdk/lib/inspector-sfn-ecr-quarantine-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon EventBridge event schema for Amazon Inspector events", + "link": "https://docs.aws.amazon.com/inspector/latest/user/eventbridge-integration.html" + }, + { + "text": "Call AWS SDK services from AWS Step Functions", + "link": "https://docs.aws.amazon.com/step-functions/latest/dg/supported-services-awssdk.html" + }, + { + "text": "Amazon ECR PutImage API reference", + "link": "https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_PutImage.html" + } + ] + }, + "deploy": { + "text": [ + "cd inspector-sfn-ecr-quarantine-cdk/cdk", + "npm install", + "npx cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "Delete the stack: cd inspector-sfn-ecr-quarantine-cdk/cdk", + "npx cdk destroy." + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "image": "https://avatars.githubusercontent.com/u/NithinChandranR-AWS", + "bio": "Solutions Architect at AWS.", + "linkedin": "", + "twitter": "" + } + ], + "patternArch": { + "icon1": { + "x": 15, + "y": 50, + "service": "inspector", + "label": "Amazon Inspector" + }, + "icon2": { + "x": 40, + "y": 50, + "service": "eventbridge", + "label": "Amazon EventBridge" + }, + "icon3": { + "x": 65, + "y": 50, + "service": "stepfunctions", + "label": "AWS Step Functions" + }, + "icon4": { + "x": 90, + "y": 30, + "service": "ecr", + "label": "Amazon ECR" + }, + "icon5": { + "x": 90, + "y": 70, + "service": "sns", + "label": "Amazon SNS" + }, + "line1": { + "from": "icon1", + "to": "icon2" + }, + "line2": { + "from": "icon2", + "to": "icon3" + }, + "line3": { + "from": "icon3", + "to": "icon4" + }, + "line4": { + "from": "icon3", + "to": "icon5" + } + } +}