Blog

AWS CloudFormation Security: Best Practices

A practical guide to CloudFormation security: secrets handling, IAM least privilege, insecure defaults, drift, stack policies, and scanning in CI.

Bruno Baldo·Sep 21, 2026·Updated Sep 14, 2026·8 min read·Reviewed by Rainforest Technologies

AWS CloudFormation turns your infrastructure into declarative templates, which is exactly why CloudFormation security deserves the same rigor you apply to application code. A template is a blueprint for real resources — IAM roles, S3 buckets, security groups, databases — and any weakness baked into that blueprint is faithfully reproduced every time the stack deploys, across every account and region you roll it out to. The good news is that the same "as code" property that lets a misconfiguration spread also lets you review, test, and gate it automatically. This article is a practical, CloudFormation-specific deep dive into the AWS CloudFormation security best practices that keep your templates from becoming a repeatable source of risk. It sits inside our broader Infrastructure as Code security pillar, so if you want the strategic picture across tools, start there and come back for the CloudFormation specifics.

A quick framing before the specifics. CloudFormation is not secure by default in the sense people often assume: it will happily create a wide-open resource if that is what your template describes. Its job is to make your desired state real, not to judge whether that state is safe. Security therefore lives in two places — in the templates you author, and in the pipeline that turns those templates into deployed stacks. Get both right and you get secure CloudFormation templates that stay secure as they evolve.

Keep secrets out of your templates

The most common and most damaging CloudFormation mistake is putting secrets directly in a template. A database password, an API key, or a token written as a literal string is now committed to version control, copied into every stack event, and visible to anyone with read access to the template or the stack. Rotating it later means editing and redeploying, and the old value lingers in git history forever.

The fix is to reference secrets rather than embed them. CloudFormation supports dynamic references that resolve values at deploy time from AWS Secrets Manager or SSM Parameter Store, so the secret never appears in the template itself:

Resources:

  Database:

    Type: AWS::RDS::DBInstance

    Properties:

      Engine: postgres

      MasterUsername: admin

      MasterUserPassword: '{{resolve:secretsmanager:prod/db:SecretString:password}}'

For values passed in at deploy time, declare the parameter with NoEcho: true so it is masked in the console, the CLI, and the API responses:

Parameters:

  DbPassword:

    Type: String

    NoEcho: true

    Description: Injected at deploy time; never stored in the template.

NoEcho is a genuine improvement, but treat it as one layer, not a guarantee. Secrets can still leak through several side channels: a NoEcho parameter surfaces in plaintext if you reference it in an Outputs section, values can appear in stack events and change sets, and anything you print in a custom resource's Lambda can land in CloudWatch logs. So the rule is broader than "use NoEcho": never place a secret in an output, be careful what custom resources log, and prefer resolving secrets at runtime from a managed store over passing them through the stack at all.

Enforce IAM least privilege

CloudFormation and IAM intersect in two ways, and both matter. First, the IAM roles, policies, and users your template creates should follow least privilege — grant only the actions and resources a workload genuinely needs, scope resource ARNs instead of using "Resource": "*", and resist the convenience of Action: "*" or broad service-level wildcards like s3:*. A role your stack provisions with admin-equivalent permissions is a standing risk for the life of the stack.

Second, there is the identity CloudFormation itself uses. By default the service acts with your caller permissions, but you can and generally should attach a dedicated service role to a stack so its actions are scoped and auditable independently of whoever runs the deploy. When a template creates or modifies IAM resources, CloudFormation requires you to explicitly acknowledge that with CAPABILITY_IAM, or CAPABILITY_NAMED_IAM when those resources have custom names. That acknowledgment exists precisely because IAM changes are sensitive — treat granting it as a deliberate review checkpoint, not a checkbox you tick to make an error go away. A pull request that suddenly needs CAPABILITY_NAMED_IAM is a signal to look closely at what permissions are being created.

Catch insecure resource defaults

Most real CloudFormation risk is not exotic — it is ordinary resources deployed with permissive or unencrypted settings. A few recurring offenders:

  • Public S3 buckets and bucket policies — buckets left readable to the world, or policies with a Principal of "*", are a perennial source of data exposure. Set PublicAccessBlockConfiguration and keep bucket policies scoped.
  • Open security groups — an ingress rule allowing 0.0.0.0/0 to port 22 or 3389 exposes SSH or RDP to the entire internet.
  • Unencrypted storage — S3 buckets, EBS volumes, and RDS instances without encryption at rest, or resources that skip encryption in transit.
  • Publicly accessible databases — an RDS instance with PubliclyAccessible: true sitting in a public subnet.

Here is what an over-exposed security group looks like in a template — exactly the kind of thing static analysis should flag before it ever deploys:

  SshFromAnywhere:

    Type: AWS::EC2::SecurityGroup

    Properties:

      GroupDescription: Bad idea

      SecurityGroupIngress:

        - IpProtocol: tcp

          FromPort: 22

          ToPort: 22

          CidrIp: 0.0.0.0/0 # exposes SSH to the whole internet

These are declarative properties, which means a static analysis tool can read the template and tell you the resource is misconfigured before a single API call is made. That is the single highest-leverage habit in CloudFormation security: scan the template, not just the running account.

Protect deployed stacks: policies, termination protection, and drift

Security does not end at deploy. Once a stack is live, three CloudFormation features help keep it trustworthy.

Stack policies protect critical resources from accidental updates or replacement during a stack update. Attaching a policy that denies updates to, say, a production database prevents a careless template change from replacing it and destroying data.

Termination protection stops a stack from being deleted — either accidentally or by an attacker with stack permissions — until protection is explicitly turned off. Enable it on any stack whose deletion would be disruptive.

Drift detection tells you when the real resources have diverged from what the template declares. Drift usually means someone made a change directly in the console or CLI, bypassing your reviewed pipeline. That is both an operational risk and a security one: a security group opened by hand, or an encryption setting quietly disabled, will not show up in your templates. Run drift detection on a regular cadence and investigate anything that comes back drifted, because your templates are only a source of truth if reality actually matches them.

Trust your template supply chain

Templates rarely live alone. Nested stacks, CloudFormation modules, and shared templates pulled from S3 or a registry all bring in code you did not necessarily write. Each of those is a supply-chain dependency, and the same caution you apply to third-party libraries applies here:

  • Source nested stacks and modules from repositories and buckets you control and trust, not arbitrary public URLs.
  • Pin versions rather than always pulling "latest," so a change upstream cannot silently alter what you deploy.
  • Review shared templates before adopting them, and re-scan them as part of your own pipeline rather than assuming an upstream author already did.

A misconfiguration inherited from a nested stack is just as real as one you wrote yourself, so bring imported templates inside your scanning and review boundary.

Put guardrails in the pipeline

Everything above becomes sustainable only when it is automated. Manual review catches some issues, but the reliable way to enforce secure CloudFormation templates is to make your pipeline do it on every change.

Start with linting and policy-as-code. cfn-lint validates template structure, resource properties, and intrinsic functions, catching whole classes of errors before deploy. AWS CloudFormation Guard lets you express organizational rules as policy — "every S3 bucket must block public access," "no security group may allow 0.0.0.0/0 on port 22" — and evaluate templates against them automatically. Running these in continuous integration means a rule violation shows up as a failed check on the pull request, where it is a quick edit, instead of as an incident weeks later.

Then add security scanning of the templates themselves. Scan every template for misconfigurations — the insecure defaults described above and more — and fail the pipeline on high-severity findings so an open bucket or an over-broad IAM policy cannot merge silently. Gate deployments on these checks the same way you gate on passing tests. The goal is simple: security travels with the change, automatically, so the secure path is also the path of least resistance for your developers.

How Rainforest helps

Doing all of this by hand across many templates, stacks, and accounts does not scale. Rainforest brings CloudFormation security into your normal development workflow with infrastructure-as-code scanning that reads your templates and flags misconfigurations — public storage, open security groups, missing encryption, over-privileged IAM, hardcoded secrets — before they ship. Findings surface directly in pull requests and CI, prioritized by severity, so your team fixes issues at the source rather than discovering them in a live account. Because the same platform also covers your application code and dependencies, you get one consistent view of risk across the whole stack instead of stitching together disconnected tools. And because CloudFormation is just one of several IaC formats teams use, scanning it alongside your other templates keeps your standards uniform no matter how a given piece of infrastructure is defined.

If you would like to see it against your own templates, explore our IaC security tooling, take in the wider application security testing platform, or book a demo.

Frequently asked questions

Is CloudFormation secure by default?

Not in the way people often hope. CloudFormation reliably deploys whatever your template describes, but it does not judge whether that description is safe — if your template defines a public S3 bucket or a security group open to the internet, it will create exactly that. AWS secures the CloudFormation service itself; the security of what you deploy is your responsibility. That is why scanning templates and enforcing guardrails in your pipeline matters so much.

How do I handle secrets in CloudFormation?

Never hardcode them. Use dynamic references ({{resolve:secretsmanager:...}} or {{resolve:ssm-secure:...}}) so values are pulled from AWS Secrets Manager or SSM Parameter Store at deploy time and never stored in the template. Mark any sensitive parameter with NoEcho: true to mask it. Remember the leak paths that NoEcho does not close: keep secrets out of stack outputs, watch what custom-resource Lambdas write to logs, and be aware that values can appear in stack events. Prefer resolving secrets at runtime over passing them through the stack at all.

How do I enforce least privilege in CloudFormation?

Scope the IAM resources your templates create — grant specific actions on specific resource ARNs instead of "*" wildcards — and give CloudFormation a dedicated, tightly scoped service role rather than deploying with broad caller permissions. Treat CAPABILITY_IAM and CAPABILITY_NAMED_IAM as deliberate review checkpoints, since they signal that a change creates or modifies permissions. Reviewing and scanning IAM changes in every pull request keeps privilege creep from accumulating.

How do I scan CloudFormation templates for misconfigurations?

Run static analysis on the templates before they deploy. Use cfn-lint for structural and property validation, CloudFormation Guard for policy-as-code rules, and an IaC security scanner to detect insecure defaults such as public buckets, open security groups, unencrypted storage, and over-broad IAM. Wire these into CI and fail the build on high-severity findings so problems are fixed in the pull request rather than in a live account.

What is drift detection?

Drift detection is a CloudFormation feature that compares the actual configuration of a stack's resources against what the template declares. When they differ — usually because someone changed a resource directly in the console or CLI — the resource is reported as drifted. That matters for security because out-of-band changes, like a security group opened by hand or encryption switched off, bypass your reviewed pipeline and are invisible in your templates. Running drift detection regularly keeps your templates a genuine source of truth.

Bruno Baldo

Written by

Bruno Baldo

CMO

Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Keep reading