Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws-ec2: requireImdsv2 forces EC2 replacement #32886

Open
1 task
dil-mocsy opened this issue Jan 13, 2025 · 5 comments
Open
1 task

aws-ec2: requireImdsv2 forces EC2 replacement #32886

dil-mocsy opened this issue Jan 13, 2025 · 5 comments
Labels
@aws-cdk/aws-ec2 Related to Amazon Elastic Compute Cloud bug This issue is a bug. effort/small Small work item – less than a day of effort needs-cfn This issue is waiting on changes to CloudFormation before it can be addressed. p2

Comments

@dil-mocsy
Copy link

dil-mocsy commented Jan 13, 2025

Describe the bug

When creating a new EC2 instance the IMDSv2 setting is handled in a surprising way.

new ec2.Instance(this, myInstanceId, {
      requireImdsv2: true,
      ...
      }

Creates a launch template at the background if one didn't exists yet.

This is problematic as attaching a launch template to an existing instance results in a replace.
Replacing an EC2 instance is a destructive operation:

  • any data/config stored on there is lost
  • the server is taken offline
    • availability disruption
    • blackout/outage scenarios come to mind

[packages/aws-cdk-lib/aws-ec2/lib/instance.ts#672

```](https://github.com/aws/aws-cdk/blob/f0e2f2a0aeeb9538bac101b523decb25d96cfc8a/packages/aws-cdk-lib/aws-ec2/lib/instance.ts#L672) 
points to [InstanceRequireImdsv2Aspect](https://github.com/aws/aws-cdk/blob/f0e2f2a0aeeb9538bac101b523decb25d96cfc8a/packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts#L71).

Which identifies the scenario in the bug, but I think it got it backwards #83:
```typescript
    if (node.instance.launchTemplate !== undefined) {
      this.warn(node, 'Cannot toggle IMDSv1 because this Instance is associated with an existing Launch Template.');
      return;
    }

Then proceeds to add a new launchTemplate if there wasn't one, therefore replacing the instance #100:

    node.instance.launchTemplate = {
      launchTemplateName: launchTemplate.launchTemplateName,
      version: launchTemplate.getAtt('LatestVersionNumber').toString(),
    };

Regression Issue

  • Select this option if this issue appears to be a regression.

Last Known Working CDK Version

No response

Expected Behavior

EC2 is not replaced by changing a simple bool from false to true.

This setting can be toggled on AWS Console without EC2 replace, it is a CDK bug.

Current Behavior

EC2 is replaced when IMDSv2 is set to required from CDK because it forces a new launchTemplate to be associated.

Reproduction Steps

Deploy a new instance from cdk without:

new ec2.Instance(this, myInstanceId, {
      ...
      }

Then add requireImdsv2: true,

new ec2.Instance(this, myInstanceId, {
      requireImdsv2: true,
      ...
      }

Either:

  • Observe the template.json changes.
  • Use cdk diff, it will show the new launchTemplate and the need to replace.
  • Turn on termination protection and try cdk deploy

All of these will show this.

Possible Solution

No response

Additional Information/Context

No response

CDK CLI Version

2.174.1 (build f353fc7)

Framework Version

No response

Node.js Version

20

OS

macos

Language

TypeScript

Language Version

No response

Other information

It's not OS or nodejs version related.

@dil-mocsy dil-mocsy added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Jan 13, 2025
@github-actions github-actions bot added the @aws-cdk/aws-ec2 Related to Amazon Elastic Compute Cloud label Jan 13, 2025
@dil-mocsy
Copy link
Author

This is my corporate github account.
For my personal/free time account see https://github.com/mocsy

@ashishdhingra ashishdhingra self-assigned this Jan 13, 2025
@ashishdhingra ashishdhingra added p2 investigating This issue is being investigated and/or work is in progress to resolve the issue. and removed needs-triage This issue or PR still needs to be triaged. labels Jan 13, 2025
@ashishdhingra
Copy link
Contributor

Per AWS::EC2::LaunchTemplate MetadataOptions, change to HttpTokens caused update that requires No interruption, which means AWS CloudFormation updates the resource without disrupting operation of that resource and without changing the resource's physical ID..

Initially when below CDK code is synthesized and deployed:

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

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

    const vpc = ec2.Vpc.fromLookup(this, 'MyDefaultVpc', { isDefault: true });

    new ec2.Instance(this, 'MyEC2Instance', {
      vpc,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
      machineImage: ec2.MachineImage.latestAmazonLinux2023(),
      disableApiTermination: true
    });
  }
}

it used the below CFN template:

{
 "Resources": {
  "MyEC2InstanceInstanceSecurityGroup06C6622F": {
   "Type": "AWS::EC2::SecurityGroup",
   "Properties": {
    "GroupDescription": "CdktestStackNew/MyEC2Instance/InstanceSecurityGroup",
    "SecurityGroupEgress": [
     {
      "CidrIp": "0.0.0.0/0",
      "Description": "Allow all outbound traffic by default",
      "IpProtocol": "-1"
     }
    ],
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdktestStackNew/MyEC2Instance"
     }
    ],
    "VpcId": "vpc-0bcd157c9276f648b"
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/InstanceSecurityGroup/Resource"
   }
  },
  "MyEC2InstanceInstanceRole1A6C2310": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "ec2.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdktestStackNew/MyEC2Instance"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/InstanceRole/Resource"
   }
  },
  "MyEC2InstanceInstanceProfile9377ECBE": {
   "Type": "AWS::IAM::InstanceProfile",
   "Properties": {
    "Roles": [
     {
      "Ref": "MyEC2InstanceInstanceRole1A6C2310"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/InstanceProfile"
   }
  },
  "MyEC2InstanceB097982C": {
   "Type": "AWS::EC2::Instance",
   "Properties": {
    "AvailabilityZone": "us-east-2a",
    "DisableApiTermination": true,
    "IamInstanceProfile": {
     "Ref": "MyEC2InstanceInstanceProfile9377ECBE"
    },
    "ImageId": {
     "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter"
    },
    "InstanceType": "t2.micro",
    "SecurityGroupIds": [
     {
      "Fn::GetAtt": [
       "MyEC2InstanceInstanceSecurityGroup06C6622F",
       "GroupId"
      ]
     }
    ],
    "SubnetId": "subnet-045c5a5af92ce5bf5",
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdktestStackNew/MyEC2Instance"
     }
    ],
    "UserData": {
     "Fn::Base64": "#!/bin/bash"
    }
   },
   "DependsOn": [
    "MyEC2InstanceInstanceRole1A6C2310"
   ],
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/Resource"
   }
  },
  ...
 },
...
}

It doesn't uses launch template.

When property requireImdsv2: true is added, it synthesizes to below CFN template:

{
 "Resources": {
  "MyEC2InstanceInstanceSecurityGroup06C6622F": {
   "Type": "AWS::EC2::SecurityGroup",
   "Properties": {
    "GroupDescription": "CdktestStackNew/MyEC2Instance/InstanceSecurityGroup",
    "SecurityGroupEgress": [
     {
      "CidrIp": "0.0.0.0/0",
      "Description": "Allow all outbound traffic by default",
      "IpProtocol": "-1"
     }
    ],
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdktestStackNew/MyEC2Instance"
     }
    ],
    "VpcId": "vpc-0bcd157c9276f648b"
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/InstanceSecurityGroup/Resource"
   }
  },
  "MyEC2InstanceInstanceRole1A6C2310": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "ec2.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdktestStackNew/MyEC2Instance"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/InstanceRole/Resource"
   }
  },
  "MyEC2InstanceInstanceProfile9377ECBE": {
   "Type": "AWS::IAM::InstanceProfile",
   "Properties": {
    "Roles": [
     {
      "Ref": "MyEC2InstanceInstanceRole1A6C2310"
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/InstanceProfile"
   }
  },
  "MyEC2InstanceB097982C": {
   "Type": "AWS::EC2::Instance",
   "Properties": {
    "AvailabilityZone": "us-east-2a",
    "DisableApiTermination": true,
    "IamInstanceProfile": {
     "Ref": "MyEC2InstanceInstanceProfile9377ECBE"
    },
    "ImageId": {
     "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter"
    },
    "InstanceType": "t2.micro",
    "LaunchTemplate": {
     "LaunchTemplateName": "CdktestStackNewMyEC2InstanceLaunchTemplate88E948F0",
     "Version": {
      "Fn::GetAtt": [
       "MyEC2InstanceLaunchTemplate7D3AFA7C",
       "LatestVersionNumber"
      ]
     }
    },
    "SecurityGroupIds": [
     {
      "Fn::GetAtt": [
       "MyEC2InstanceInstanceSecurityGroup06C6622F",
       "GroupId"
      ]
     }
    ],
    "SubnetId": "subnet-045c5a5af92ce5bf5",
    "Tags": [
     {
      "Key": "Name",
      "Value": "CdktestStackNew/MyEC2Instance"
     }
    ],
    "UserData": {
     "Fn::Base64": "#!/bin/bash"
    }
   },
   "DependsOn": [
    "MyEC2InstanceInstanceRole1A6C2310"
   ],
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/Resource"
   }
  },
  "MyEC2InstanceLaunchTemplate7D3AFA7C": {
   "Type": "AWS::EC2::LaunchTemplate",
   "Properties": {
    "LaunchTemplateData": {
     "MetadataOptions": {
      "HttpTokens": "required"
     }
    },
    "LaunchTemplateName": "CdktestStackNewMyEC2InstanceLaunchTemplate88E948F0"
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/MyEC2Instance/LaunchTemplate"
   }
  },
  "CDKMetadata": {
   "Type": "AWS::CDK::Metadata",
   "Properties": {
    "Analytics": "v2:deflate64:H4sIAAAAAAAA/2WOuwrCQBREvyX95mqCon0KESxCYi/XzQ1u3EfYh0GW/XcxGhurgTNnYEoodlsoMpxczrt7LsUVYuuR31lDzgTLieHkLpF4CfGonUfNibXEgxX+ebAmjKzq9R/4qVWvTxg0v51JjRI9JSZQQWyMnMsll0FtTS8kpfSGNVpU5MnO5vdQYtp0BINbPYo9lGvYZIMTIrdBe6EImk++ALTh8tLbAAAA"
   },
   "Metadata": {
    "aws:cdk:path": "CdktestStackNew/CDKMetadata/Default"
   }
  }
 },
 ...
 },
 ...
}

AWS::EC2::Instance resource now uses LaunchTemplate property. Per AWS::EC2::Instance, setting LaunchTemplate causes Replacement.

As of now, enabling IMDSv2 requires a Launch template. There is an open issue aws-cloudformation/cloudformation-coverage-roadmap#655 in CloudFormation coverage repository which is a feature request to have IMDSv2 option at EC2 instance level itself. Until that is fixed, there might not be a way around.

Storage options for your Amazon EC2 instances lists some of the storage options for EC2 instances. To summarise:

  • The EC2 instance store volume is erased when the instance is stopped or terminated.
  • In contrast, EBS (Elastic Block Storage) can be mounted to a running EC2 instance (c.f. a physical hard drive). EBS volumes can be detached from one instance and attached to another instance. Moreover, you can create a snapshot of an EBS volume that is stored in S3 and you can recreate EBS volumes from that snapshot that can be attached to other EC2 instances.

@aws-cdk/aws-ec2:uniqueImdsv2TemplateName (enabled by default), mentioned at CDK Feature Flags might not help (also refer #20851 (comment)).

@dil-mocsy The behavior appears to be expected based on CloudFormation implementation where enabling IMDSv2 is done via Launch Template and later when Launch Template is associated with EC2 instance, it causes replacement per CloudFormation docs. So this feature gap needs to be supported in CloudFormation first per aws-cloudformation/cloudformation-coverage-roadmap#655.

@ashishdhingra ashishdhingra added the effort/small Small work item – less than a day of effort label Jan 13, 2025
@ashishdhingra ashishdhingra removed their assignment Jan 13, 2025
@ashishdhingra ashishdhingra added needs-cfn This issue is waiting on changes to CloudFormation before it can be addressed. and removed investigating This issue is being investigated and/or work is in progress to resolve the issue. labels Jan 13, 2025
@dil-mocsy
Copy link
Author

dil-mocsy commented Jan 14, 2025

@ashishdhingra If I read that properly, you basically confirmed what I wrote.

I came to the same conclusion that this is a Cloudformation limitation for now.

Found the same link #655
That's open for more than 4 years now.
Will it ever get momentum?

Launch template mandatory

Today's implementation makes a launchTemplate mandatory.

If cdk users accept that reality, then the existing implementation is strage:

    if (node.instance.launchTemplate !== undefined) {
      this.warn(node, 'Cannot toggle IMDSv1 because this Instance is associated with an existing Launch Template.');
      return;
    }

If you already have a launch template then InstanceRequireImdsv2Aspect doesn't work.

Shouldn't InstanceRequireImdsv2Aspect packages/aws-cdk-lib/aws-ec2/lib/aspects/require-imdsv2-aspect.ts

  • Try to mutate the existing launch template instead of bailing out?
  • Warn when it needs to create a new launch template implicitly, in a surprising way, hidden from view (might be better to remove that code, but that would be a breaking change -> skip)
  • The docs of requireImdsv2 right here should warn the user that it creates a new launch template and destroys the existing instance resulting in potential data loss
  • Other docs to update?

@dil-mocsy
Copy link
Author

dil-mocsy commented Jan 14, 2025

Termination protection

Termination protection should be on.
disableApiTermination = true does that.
In which case Cloudformation will try to destroy the original EC2 but fails, the stack remains in a failed state.
This makes the pipelines clogged.

@ashishdhingra
Copy link
Contributor

@dil-mocsy Unfortunately we cannot provide ETA on when aws-cloudformation/cloudformation-coverage-roadmap#655 would be addressed by CloudFormation team. Please try to add your comment on that issue and may be add 👍 to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-ec2 Related to Amazon Elastic Compute Cloud bug This issue is a bug. effort/small Small work item – less than a day of effort needs-cfn This issue is waiting on changes to CloudFormation before it can be addressed. p2
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants