[アップデート] AWS CDKでEC2インスタンスに割り当てられたIAMロールにAmazonSSMManagedInstanceCoreを簡単にアタッチできるようになりました

検証用にAWS CDKでEC2インスタンスを構築する時ちょっと楽になりました
2023.03.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

都度IAMロール定義するの面倒だな

こんにちは、のんピ(@non____97)です。

皆さんはAWS CDKで検証用のEC2インスタンスを立てるときにAmazonSSMManagedInstanceCoreがアタッチされたIAMロールを定義するのが面倒だなと思ったことはありますか? 私はあります。

AWS CDK 2.70.0からssmSessionPermissionstrueとすることでEC2インスタンスに割り当てられたIAMロールにAmazonSSMManagedInstanceCoreを簡単にアタッチできるようになりました。

これにより、AWS SSM Default Host Management Configurationが有効にできない環境において、より簡単にEC2インスタンスにSSMセッションマネージャーで接続できるようになります。

AWS SSM Default Host Management Configurationの詳細は以下記事をご覧ください。

検証用のEC2インスタンスをサクッと立てたい時に非常に役立ちそうです。

実際に試してみたので紹介します。

やってみた

ssmSessionPermissions を指定しない場合

比較用にssmSessionPermissionsを指定しない場合を確認します。

以下のようなEC2インスタンスを1台Public Subnetにデプロイするスタックを定義します。

./lib/ec2-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

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

    // VPC
    const vpc = new cdk.aws_ec2.Vpc(this, "Vpc", {
      ipAddresses: cdk.aws_ec2.IpAddresses.cidr("10.1.1.0/24"),
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 0,
      maxAzs: 1,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: cdk.aws_ec2.SubnetType.PUBLIC,
          cidrMask: 27,
          mapPublicIpOnLaunch: true,
        },
      ],
    });

    // EC2 Instance
    new cdk.aws_ec2.Instance(this, "Ec2 Instance", {
      machineImage: cdk.aws_ec2.MachineImage.fromSsmParameter(
        "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
      ),
      instanceType: new cdk.aws_ec2.InstanceType("t3.micro"),
      vpc,
      vpcSubnets: vpc.selectSubnets({
        subnetGroupName: "Public",
      }),
      propagateTagsToVolumeOnCreation: true,
      // ssmSessionPermissions: true,
    });
  }
}

cdk deployする前にcdk diffをしてどんなリソースが作成されるのか確認します。

npx cdk diff
Stack Ec2Stack
IAM Statement Changes
┌───┬──────────────────────────────────┬────────┬────────────────┬───────────────────────────┬───────────┐
│   │ Resource                         │ Effect │ Action         │ Principal                 │ Condition │
├───┼──────────────────────────────────┼────────┼────────────────┼───────────────────────────┼───────────┤
│ + │ ${Ec2 Instance/InstanceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:ec2.amazonaws.com │           │
└───┴──────────────────────────────────┴────────┴────────────────┴───────────────────────────┴───────────┘
Security Group Changes
┌───┬───────────────────────────────────────────────┬─────┬────────────┬─────────────────┐
│   │ Group                                         │ Dir │ Protocol   │ Peer            │
├───┼───────────────────────────────────────────────┼─────┼────────────┼─────────────────┤
│ + │ ${Ec2 Instance/InstanceSecurityGroup.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴───────────────────────────────────────────────┴─────┴────────────┴─────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter SsmParameterValue:--aws--service--ami-amazon-linux-latest--al2023-ami-kernel-6.1-x86_64:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter SsmParameterValueawsserviceamiamazonlinuxlatestal2023amikernel61x8664C96584B6F00A464EAD1953AFF4B05118Parameter: {"Type":"AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>","Default":"/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"}
[+] Parameter BootstrapVersion BootstrapVersion: {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/cdk-bootstrap/hnb659fds/version","Description":"Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"}

Conditions
[+] Condition CDKMetadata/Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"af-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::EC2::VPC Vpc Vpc8378EB38
[+] AWS::EC2::Subnet Vpc/PublicSubnet1/Subnet VpcPublicSubnet1Subnet5C2D37C4
[+] AWS::EC2::RouteTable Vpc/PublicSubnet1/RouteTable VpcPublicSubnet1RouteTable6C95E38E
[+] AWS::EC2::SubnetRouteTableAssociation Vpc/PublicSubnet1/RouteTableAssociation VpcPublicSubnet1RouteTableAssociation97140677
[+] AWS::EC2::Route Vpc/PublicSubnet1/DefaultRoute VpcPublicSubnet1DefaultRoute3DA9E72A
[+] AWS::EC2::InternetGateway Vpc/IGW VpcIGWD7BA715C
[+] AWS::EC2::VPCGatewayAttachment Vpc/VPCGW VpcVPCGWBF912B6E
[+] AWS::EC2::SecurityGroup Ec2 Instance/InstanceSecurityGroup Ec2InstanceInstanceSecurityGroupDFF556AA
[+] AWS::IAM::Role Ec2 Instance/InstanceRole Ec2InstanceInstanceRole8E810D82
[+] AWS::IAM::InstanceProfile Ec2 Instance/InstanceProfile Ec2InstanceInstanceProfileA536E794
[+] AWS::EC2::Instance Ec2 Instance Ec2Instance1F7BF384

Other Changes
[+] Unknown Rules: {"CheckBootstrapVersion":{"Assertions":[{"Assert":{"Fn::Not":[{"Fn::Contains":[["1","2","3","4","5"],{"Ref":"BootstrapVersion"}]}]},"AssertDescription":"CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."}]}}

どうやらIAMロールとインスタンスプロファイルは指定しなくても作成されるようですね。

cdk deploy後作成されたEC2インスタンスを確認します。

EC2インスタンスにアタッチされているIAMロールの確認

インスタンスプロファイルがアタッチされていますね。

それでは、このインスタンスプロファイルのIAMロールにアタッチされているIAMポリシーを確認します。

IAMロールの許可ポリシーの確認

はい、何もアタッチされていないようです。

AWS CLIからも確認しましょう

# IAMロールの確認
$ aws iam get-role --role-name Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU
{
    "Role": {
        "Path": "/",
        "RoleName": "Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU",
        "RoleId": "AROA6KUFAVPUUYMVBFMEL",
        "Arn": "arn:aws:iam::<AWSアカウントID>:role/Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU",
        "CreateDate": "2023-03-25T02:03:35+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ec2.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "Description": "",
        "MaxSessionDuration": 3600,
        "Tags": [
            {
                "Key": "Name",
                "Value": "Ec2Stack/Ec2 Instance"
            }
        ],
        "RoleLastUsed": {}
    }
}

# IAMロールにアタッチされているIAMポリシーの確認
$ aws iam list-attached-role-policies --role-name Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU
{
    "AttachedPolicies": []
}

# IAMロールのインスタンスプロファイルの確認
$ aws iam get-instance-profile --instance-profile-name Ec2Stack-Ec2InstanceInstanceProfileA536E794-if1RdbarivZR
{
    "InstanceProfile": {
        "Path": "/",
        "InstanceProfileName": "Ec2Stack-Ec2InstanceInstanceProfileA536E794-if1RdbarivZR",
        "InstanceProfileId": "AIPA6KUFAVPU4PEYXCPDC",
        "Arn": "arn:aws:iam::<AWSアカウントID>:instance-profile/Ec2Stack-Ec2InstanceInstanceProfileA536E794-if1RdbarivZR",
        "CreateDate": "2023-03-25T02:03:52+00:00",
        "Roles": [
            {
                "Path": "/",
                "RoleName": "Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU",
                "RoleId": "AROA6KUFAVPUUYMVBFMEL",
                "Arn": "arn:aws:iam::<AWSアカウントID>:role/Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU",
                "CreateDate": "2023-03-25T02:03:35+00:00",
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "ec2.amazonaws.com"
                            },
                            "Action": "sts:AssumeRole"
                        }
                    ]
                }
            }
        ],
        "Tags": []
    }
}

SSMセッションマネージャーでこちらのEC2インスタンスに接続できるかも確認します。

$ aws ssm start-session \
    --target i-0b963c41f2fc17f60

An error occurred (TargetNotConnected) when calling the StartSession operation: i-0b963c41f2fc17f60 is not connected.

接続できないと怒られましたね。

ssmSessionPermissions を true にした場合

それではssmSessionPermissionstrueにしてデプロイしてみます。

./lib/ec2-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

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

    // VPC
    const vpc = new cdk.aws_ec2.Vpc(this, "Vpc", {
      ipAddresses: cdk.aws_ec2.IpAddresses.cidr("10.1.1.0/24"),
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 0,
      maxAzs: 1,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: cdk.aws_ec2.SubnetType.PUBLIC,
          cidrMask: 27,
          mapPublicIpOnLaunch: true,
        },
      ],
    });

    // EC2 Instance
    new cdk.aws_ec2.Instance(this, "Ec2 Instance", {
      machineImage: cdk.aws_ec2.MachineImage.fromSsmParameter(
        "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
      ),
      instanceType: new cdk.aws_ec2.InstanceType("t3.micro"),
      vpc,
      vpcSubnets: vpc.selectSubnets({
        subnetGroupName: "Public",
      }),
      propagateTagsToVolumeOnCreation: true,
      ssmSessionPermissions: true,
    });
  }
}

cdk diffをすると、IAMロールへのAmazonSSMManagedInstanceCoreのアタッチが差分として表示されました。

$ npx cdk diff
Stack Ec2Stack
IAM Policy Changes
┌───┬──────────────────────────────┬────────────────────────────────────────────────────────────────────┐
│   │ Resource                     │ Managed Policy ARN                                                 │
├───┼──────────────────────────────┼────────────────────────────────────────────────────────────────────┤
│ + │ ${Ec2 Instance/InstanceRole} │ arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore │
└───┴──────────────────────────────┴────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[~] AWS::IAM::Role Ec2 Instance/InstanceRole Ec2InstanceInstanceRole8E810D82
 └─ [+] ManagedPolicyArns
     └─ [{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::aws:policy/AmazonSSMManagedInstanceCore"]]}]

cdk deplory後、IAMロールにアタッチされているIAMポリシーを確認します。

$ aws iam list-attached-role-policies --role-name Ec2Stack-Ec2InstanceInstanceRole8E810D82-MG2MR00NHQRU
{
    "AttachedPolicies": [
        {
            "PolicyName": "AmazonSSMManagedInstanceCore",
            "PolicyArn": "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
        }
    ]
}

確かにIAMロールにAmazonSSMManagedInstanceCoreがアタッチされていますね。

SSMセッションマネージャーで接続できるかも確認します。

$ aws ssm start-session \
    --target i-0b963c41f2fc17f60

Starting session with SessionId: botocore-session-1679710533-0f34a9bcfe619372d

$ whoami
ssm-user

$ hostname
ip-10-1-1-14.ec2.internal

$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2023"
ID="amzn"
ID_LIKE="fedora"
VERSION_ID="2023"
PLATFORM_ID="platform:al2023"
PRETTY_NAME="Amazon Linux 2023"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023"
HOME_URL="https://aws.amazon.com/linux/"
BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023"
SUPPORT_END="2028-03-01"

接続できましたね。これは楽です。

AmazonSSMManagedInstanceCoreがアタッチされていないIAMロールの指定 かつ ssmSessionPermissions を true にした場合

「AmazonSSMManagedInstanceCoreがアタッチされていないIAMロールの指定」かつ「ssmSessionPermissionstrue」にした場合、IAMロールにAmazonSSMManagedInstanceCoreがアタッチされるかも確認します。

以下のようにssmSessionPermissionstrueにした状態で、IAMポリシーを何もアタッチしていないIAMロールをEC2インスタンスに割り当てます。

./lib/ec2-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

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

    // IAM Role
    const iamRole = new cdk.aws_iam.Role(this, "Iam Role", {
      assumedBy: new cdk.aws_iam.ServicePrincipal("ec2.amazonaws.com"),
    });

    // VPC
    const vpc = new cdk.aws_ec2.Vpc(this, "Vpc", {
      ipAddresses: cdk.aws_ec2.IpAddresses.cidr("10.1.1.0/24"),
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 0,
      maxAzs: 1,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: cdk.aws_ec2.SubnetType.PUBLIC,
          cidrMask: 27,
          mapPublicIpOnLaunch: true,
        },
      ],
    });

    // EC2 Instance
    new cdk.aws_ec2.Instance(this, "Ec2 Instance", {
      machineImage: cdk.aws_ec2.MachineImage.fromSsmParameter(
        "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
      ),
      instanceType: new cdk.aws_ec2.InstanceType("t3.micro"),
      vpc,
      vpcSubnets: vpc.selectSubnets({
        subnetGroupName: "Public",
      }),
      propagateTagsToVolumeOnCreation: true,
      ssmSessionPermissions: true,
      role: iamRole,
    });
  }
}

cdk diffをすると、どうやらAmazonSSMManagedInstanceCoreをアタッチするような差分が表示されました。

npx cdk diff
Stack Ec2Stack
IAM Statement Changes
┌───┬─────────────────┬────────┬────────────────┬───────────────────────────┬───────────┐
│   │ Resource        │ Effect │ Action         │ Principal                 │ Condition │
├───┼─────────────────┼────────┼────────────────┼───────────────────────────┼───────────┤
│ + │ ${Iam Role.Arn} │ Allow  │ sts:AssumeRole │ Service:ec2.amazonaws.com │           │
└───┴─────────────────┴────────┴────────────────┴───────────────────────────┴───────────┘
IAM Policy Changes
┌───┬─────────────┬────────────────────────────────────────────────────────────────────┐
│   │ Resource    │ Managed Policy ARN                                                 │
├───┼─────────────┼────────────────────────────────────────────────────────────────────┤
│ + │ ${Iam Role} │ arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore │
└───┴─────────────┴────────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::IAM::Role Ec2InstanceInstanceRole8E810D82 destroy
[+] AWS::IAM::Role Iam Role IamRoleB5F0CB96
[~] AWS::IAM::InstanceProfile Ec2 Instance/InstanceProfile Ec2InstanceInstanceProfileA536E794
 └─ [~] Roles
     └─ @@ -1,5 +1,5 @@
        [ ] [
        [ ]   {
        [-]     "Ref": "Ec2InstanceInstanceRole8E810D82"
        [+]     "Ref": "IamRoleB5F0CB96"
        [ ]   }
        [ ] ]
[~] AWS::EC2::Instance Ec2 Instance Ec2Instance1F7BF384
 └─ [~] DependsOn
     └─ @@ -1,3 +1,3 @@
        [ ] [
        [-]   "Ec2InstanceInstanceRole8E810D82"
        [+]   "IamRoleB5F0CB96"

cdk deploy後EC2インスタンスにアタッチされているIAMロールを確認すると、AmazonSSMManagedInstanceCoreがアタッチされていることが分かりました。

自分で定義したIAMロールの許可ポリシーの確認

AWS CDKのEC2インスタンスのConstructのコードを確認するとssmSessionPermissionstrueの場合はIAMロールにアタッチしているIAMポリシーをAmazonSSMManagedInstanceCoreで上書きするのではなく、AmazonSSMManagedInstanceCoreを追加する処理をしていました。

aws-cdk/packages/@aws-cdk/aws-ec2/lib/instance.ts

    this.role = props.role || new iam.Role(this, 'InstanceRole', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
    });
    this.grantPrincipal = this.role;

    if (props.ssmSessionPermissions) {
      this.role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
    }

    const iamProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
      roles: [this.role.roleName],
    });

そのため、AmazonSSMManagedInstanceCoreのアタッチはssmSessionPermissionsによってコントロールするといったこともできますね。

検証用にAWS CDKでEC2インスタンスを構築する時ちょっと楽になりました

AWS CDKでEC2インスタンスに割り当てられたIAMロールにAmazonSSMManagedInstanceCoreを簡単にアタッチできるようになったアップデートを紹介しました。

検証用にAWS CDKでEC2インスタンスを構築する時ちょっと楽になりましたね。AWS SSM Default Host Management Configurationが有効にできない環境で検証する場合は使っていこうと思います。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!