Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
175 changes: 175 additions & 0 deletions content/blog/cve-ami-refresh-lifecycle-with-pulumi-and-neo/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
---
title: "Close the CVE-to-AMI Loop with Pulumi and Neo"
date: 2026-05-26
draft: false
meta_desc: "Design a CVE-driven AMI refresh lifecycle with Pulumi, Neo-assisted changes, policy gates, previews, deployments, and audit-ready evidence."
meta_image: meta.png
feature_image: feature.png
authors:
- pablo-seibelt
tags:
- security
- neo
- aws
social:
twitter: |
A CVE alert should not become a ticket that waits for context.

Build an AMI refresh loop with Pulumi, Neo-assisted changes, previews, and policy gates.
linkedin: |
AMI refreshes are quick to start and hard to close.

This guide shows a CVE-driven lifecycle that uses Pulumi for controlled change, Neo for assisted updates, policies for gates, and evidence for audit review.
bluesky: |
Turn CVE alerts into a closed AMI refresh loop: Pulumi change control, Neo-assisted updates, previews, policy gates, evidence.

See how to scan, patch, preview, and gate AMI refreshes.
---

Managing Amazon Machine Images (AMIs) across a large cloud estate is a constant race against security advisories. When a new Common Vulnerabilities and Exposures (CVE) report drops, the clock starts ticking. You need to identify every running instance using the vulnerable image, build a patched version, and roll it out without breaking production.

Traditional approaches involve manual spreadsheets or disconnected scanning tools that lack the context of your infrastructure code. Pulumi changes this by integrating security directly into the deployment lifecycle. By combining [Pulumi Insights](/docs/insights/), [Neo](/product/neo/), and [Pulumi Policies](/docs/insights/policy/), you can create a closed-loop system for AMI refreshes.

This post demonstrates how to build a CVE and AMI refresh loop that uses policy context and operator review to ensure your infrastructure stays patched and secure. By the end, you will have a repeatable lifecycle for scanning, proposing, and gating AMI updates.

<!--more-->

## The AMI refresh lifecycle

A defensible security posture requires more than a one-time fix. It needs a repeatable lifecycle that handles everything from discovery to verification.

### 1. Scan for vulnerable AMIs

The first step is visibility. [Pulumi Insights](https://www.pulumi.com/docs/insights/discovery/search/) allows you to query resources tracked by Pulumi across your organization. When a CVE is announced for a specific base image, you can use [Pulumi Insights](/docs/insights/) search to find every resource using that AMI ID.

For example, a platform team can start with an Insights Resource Search query that finds EC2 instances and launch templates whose Pulumi state has not changed since a cutoff date. If today is May 13, 2026, a 30-day cutoff would be April 13, 2026:

```text
(type:aws:ec2/launchTemplate:LaunchTemplate OR type:aws:ec2/instance:Instance) modified:<2026-04-13
```

If the CVE maps to a known vulnerable AMI ID, narrow the blast radius with property search:

```text
((type:aws:ec2/instance:Instance .ami:"ami-0123456789abcdef0") OR (type:aws:ec2/launchTemplate:LaunchTemplate .imageId:"ami-0123456789abcdef0"))
```

The first query gives you the aging EC2 surface area. The second query gives you the specific resources still pinned to the vulnerable image, regardless of when the resource was last updated.

### 2. Neo-assisted change proposals

Once you identify the vulnerable resources, the next step is remediation. This is where [Pulumi Neo](/product/neo/), our infrastructure agent, comes in. Instead of manually hunting down the right lines of code across repositories, you can use a Neo-assisted workflow to propose changes.

You can ask Neo to update the AMI IDs across your stacks to an approved patched version supplied by your image-release process. Neo analyzes your existing code, identifies the correct resource definitions, and generates a draft change set with the necessary updates. This keeps humans in the loop for review while automating the tedious work of finding and replacing values.

### 3. Gate with Pulumi Policies

Before those changes reach production, use Pulumi Policies to block deployments that reference an AMI outside your approved list. The important detail is that Pulumi Policies does not need to discover the newest AMI itself. Your AMI build pipeline, EC2 Image Builder workflow, or CVE response automation should publish the approved AMI IDs after the image is patched and scanned. The policy pack then treats that generated list as policy configuration.

Here is a TypeScript policy pack that gates EC2 instances and launch templates against an approved AMI list. If your estate also uses launch configurations or other indirection patterns, add resource validations for those patterns too.

```typescript
import * as aws from "@pulumi/aws";
import { PolicyPack, validateResourceOfType } from "@pulumi/policy";

type AmiPolicyConfig = {
approvedAmis?: string[];
};

new PolicyPack("ami-refresh-guardrails", {
policies: [
{
name: "approved-amis-only",
description: "Allow EC2 resources to use only approved AMI IDs.",
enforcementLevel: "mandatory",
configSchema: {
properties: {
approvedAmis: {
type: "array",
items: { type: "string" },
},
},
required: ["approvedAmis"],
},
validateResource: [
validateResourceOfType(aws.ec2.Instance, (instance, args, reportViolation) => {
const config = args.getConfig<AmiPolicyConfig>();
const approvedAmis = new Set(config.approvedAmis ?? []);

if (!instance.ami || !approvedAmis.has(instance.ami)) {
reportViolation(`EC2 Instance uses unapproved AMI '${instance.ami}'.`);
}
}),
validateResourceOfType(aws.ec2.LaunchTemplate, (template, args, reportViolation) => {
const config = args.getConfig<AmiPolicyConfig>();
const approvedAmis = new Set(config.approvedAmis ?? []);

if (template.imageId && !approvedAmis.has(template.imageId)) {
reportViolation(`EC2 LaunchTemplate uses unapproved AMI '${template.imageId}'.`);
}
}),
],
},
],
});
```

If you want the latest scanned AMI to become the approved AMI automatically, one option is to have your AMI build pipeline tag the image after it passes vulnerability scanning, for example:

```text
PulumiApproved=true
PatchGroup=web
```

Then a CI job can query the newest approved AMI for that patch group and generate the policy config file before running `pulumi preview`:

```bash
#!/usr/bin/env bash
set -euo pipefail

PATCH_GROUP="web"

APPROVED_AMI_ID=$(aws ec2 describe-images \
--owners self \
--filters \
"Name=tag:PulumiApproved,Values=true" \
"Name=tag:PatchGroup,Values=${PATCH_GROUP}" \
--query 'Images[].{ImageId:ImageId,CreationDate:CreationDate}' \
--output json \
| jq -r 'sort_by(.CreationDate) | last | .ImageId')

if [[ -z "${APPROVED_AMI_ID}" || "${APPROVED_AMI_ID}" == "null" ]]; then
echo "No approved AMI found for patch group ${PATCH_GROUP}" >&2
exit 1
fi

jq -n --arg ami "${APPROVED_AMI_ID}" '{
"approved-amis-only": {
"approvedAmis": [$ami]
}
}' > approved-amis.json
```

The generated file becomes the policy input for the deployment. The [`pulumi preview`](/docs/iac/cli/commands/pulumi_preview/) command accepts policy-pack flags for this step:

```bash
pulumi preview --policy-pack ./policy-packs/ami-refresh --policy-pack-config ./approved-amis.json
```

That keeps the source of truth in the image-release process. When a newer AMI is approved, the pipeline updates `approved-amis.json`, and the same policy pack immediately blocks stacks that still reference the vulnerable image.

### 4. Review stacks and canary deployments

With the proposal generated and the policies passing, you can use Pulumi previews to validate the changes in an isolated environment. This allows security and platform teams to see exactly what will happen before the main branch is updated.

For critical services, you can implement a canary strategy with a dedicated canary Auto Scaling Group (ASG) or an [instance refresh](https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-instance-refresh.html) configuration. That gives you a controlled way to monitor for regressions before completing the full refresh.

### 5. Audit and close the loop

The lifecycle isn't complete until you verify the fix. After the deployment, Pulumi Insights provides resource search and change context. You can run a final scan to confirm that no instances are still running the vulnerable AMI.

This change history supports compliance reporting. It helps show that the organization responded to the CVE, followed the approved review process, and remediated the risk across the tracked fleet.

## Conclusion

Security is a continuous process, not a destination. By looping Pulumi, Neo, and Pulumi Policies into your AMI refresh lifecycle, you turn a reactive scramble into a proactive, automated workflow. You get the speed of AI-assisted remediation with the safety of policy-driven guardrails.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading