aws-cdk: Tags: should error for duplicate tag keys

Describe the bug

“Please note that Tag keys are case insensitive.” We have developers who discovered the inconsistency around tag key case insensitivity the hard way.

Expected Behavior

Cdk should have error out and refused in cases where tag names are only differentiated by case. Cfn should probably do this, too, but… protecting the user from the stupidity of Cfn is basically why Cdk exists.

Current Behavior

CDK will happily put tags all over the place. Your Cfn run will do different things depending on the resources involved. So you’ll go and remove the duplicate tags, and Cdk will happily build the Cfn and then blammo, you don’t have a tag. Hope you weren’t doing ABAC or anything…

Reproduction Steps

https://github.com/ahammond/repro-tag-collision-cdk

import { App, Stack, StackProps, Tags } from 'aws-cdk-lib';
import { AnyPrincipal, Role } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

import { name } from './helpers';

const app = new App();

class Repro extends Stack {
  constructor(scope: Construct, props?: StackProps) {
    super(scope, name.pascal, props);
    const role = new Role(this, 'Role', { assumedBy: new AnyPrincipal() });

    Tags.of(role).add('Foo', 'bar');
    // 1 - Deploy once with the following `foo` (note lower case) tag commented out.
    // 2 - Deploy 2nd time with the following line uncommented. Should have errored at CDK level.
    // Cfn should have errored (on some resources it gives a "Please note that Tag keys are case insensitive.")
    // INSTEAD what it does is change the `Foo` tag's name to `foo`.
    //Tags.of(role).add('foo', 'bar');
    // 3- Comment out and deploy a 3rd time. Note that your role now doesn't have either the `foo` or the `Foo` tag.
    // Cfn has happily cleaned up your mess and broken you.
    // As a bonus, you have also achieved an inconsistent stack state.
  }
}

new Repro(app);

app.synth();

Possible Solution

Error when name collisions are detected.

Additional Information/Context

No response

CDK CLI Version

2.86.0 (build 1130fab)

Framework Version

same

Node.js Version

v16.20.0

OS

MacOS

Language

Typescript

Language Version

4.9.5

Other information

No response

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 1
  • Comments: 19 (18 by maintainers)

Most upvoted comments

Honestly I don’t think it’s at all realistic to expect us to handle this. That requires somehow tracking and always keeping up to date exactly how each service implements tagging, and somehow applying that knowledge to all CfnResources with tagging.

This should be up to CloudFormation and the implementation of CloudFormation resources to get right if the tagging for a resource differs from what the standard should be

Official docs on tagging states that tagging is case sensitive

If a service is not accepting tags that only vary in casing, then that should be a bug with the service

@evgenyka any kind of warning would really help. Cdk is in a great position to protect customers from an otherwise pretty rough experience.

The behavior by IAM is documented. Not at the top of the page, but somewhere in the middle of the page:

Case sensitivity – Case sensitivity for tag keys differs depending on the type of IAM resource that is tagged. Tag key values for IAM users and roles are not case sensitive, but case is preserved.

(source)

That makes it a CloudFormation issue since the AWS::IAM::Role handler is not honoring its desired-state contract. Created an ticket to CloudFormation, internal reference D88663713

@ahammond it errors out if you deploy both tags at once. But it doesn’t if you add them one at a time

Whaddaya know, my assumption that the service would be correctly documented is wrong:

$ aws --region eu-west-1 iam tag-role --role-name MyRole --tags '{"Key": "Test", "Value": "1"}'
$ aws --region eu-west-1 iam tag-role --role-name MyRole --tags '{"Key": "test", "Value": "1"}'
$ aws --region eu-west-1 iam list-role-tags --role-name MyRole
{
    "Tags": [
        {
            "Key": "test",
            "Value": "1"
        }
    ],
    "IsTruncated": false
}