DEV Community

Cover image for Why My CDK Deploys Started Failing After Org Added Strict SCP Rules
Jayesh Shinde
Jayesh Shinde

Posted on

Why My CDK Deploys Started Failing After Org Added Strict SCP Rules

Recently, I ran into a head‑scratcher while deploying a CDK stack. Everything used to work fine, but once my organization introduced strict SCP rules based on tags, my cdk deploy started failing with:

AccessDenied: action cloudformation:CreateChangeSet is not authorized
Enter fullscreen mode Exit fullscreen mode

At first glance, it didn’t make sense. I was already tagging everything in my CDK code. I even had a loop that pulled tags from props.Tags and attached them with cdk.Tags.of(this).add(...). These tags used to flow down nicely to all resources — including the CloudFormation stack itself.

So why did it suddenly stop working? 🤔


What Changed? SCPs and Request‑Time Enforcement

The key is where SCP rules get evaluated.

An SCP can restrict not only what resources exist, but also what API calls are allowed. In my case, the org had a policy like this:(dummy example)

{
  "Effect": "Deny",
  "Action": "cloudformation:CreateChangeSet",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestTag/Org": "ABC"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This means: if the CreateChangeSet request doesn’t already include the required tag, the call is blocked right away.


CDK Tagging: Two Different Worlds

This is where CDK behavior matters:

  1. StackProps.tags
    When you pass tags into the super(scope, id, props) constructor, CDK includes those tags in the CreateChangeSet API call. These show up as RequestTags. That’s what SCPs check.

  2. cdk.Tags.of(resource).add(...)
    This method attaches tags to resources inside the CloudFormation template. They are applied after the stack is already created.

So my old approach of looping through props.Tags and calling cdk.Tags.of(this).add(...) worked fine in the past, but now fails because the SCP never lets the stack get created in the first place. The required tags simply aren’t present yet at request time.


Fix: Pass Tags via StackProps

The solution was simple once I understood the difference. Previously my props had Tags property which I replaced with tags which props:cdk.StackProps expects and uses to initialize CDK’s in-memory construct tree (the Stack object inside your app).
When you run cdk deploy, the CLI uses the CloudFormation SDK to call:

CreateChangeSet (or UpdateChangeSet) → this is the first API call to AWS.

This is where stack-level tags (props.tags) are injected into the request payload

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

      for (const [k, v] of Object.entries(props.tags)) {
        cdk.Tags.of(this).add(k, v);
      }

  }
}

new MyStack(app, "MyTaggedStack", {
  tags: {
    Org: "ABC",
    Owner: "TeamA"
  }
});
Enter fullscreen mode Exit fullscreen mode

Now:

  • StackProps.tags → go straight into the CreateChangeSet request (SCP passes ✅).
  • cdk.Tags.of(this).add(...) → still ensures all resources get the same tags after creation.

Takeaway

If your organization enforces strict SCP rules on cloudformation:CreateChangeSet, you can’t rely on cdk.Tags.of(...) alone. Those tags arrive too late. You need to use StackProps.tags so the tags are present in the request itself.

It’s a subtle but important difference — and once I understood it, the “AccessDenied” error finally made sense.

Top comments (0)