【AWS CDK】Session Manager 経由で EC2 インスタンスの Windows Server に RDP 接続してみた

【AWS CDK】Session Manager 経由で EC2 インスタンスの Windows Server に RDP 接続してみた

はじめに

テントの中から失礼します。製造ビジネステクノロジー部の「てんとタカハシ」です!

Session Manager を使用することで、パブリック IP を持たないプライベートサブネットに配置した EC2 インスタンスの Windows Server に対して RDP 接続が可能です。同様のことは EC2 Instance Connect Endpoint(EIC Endpoint)を使用した場合でも実現できます。

https://dev.classmethod.jp/articles/ec2-instance-connect-endpoint-windows-server-rdp-with-cdk/

それぞれの違いをざっくり説明すると以下の通りで、用途に合わせた使い分けができると良いでしょう。

  • Session Manager
    • NAT Gateway もしくは VPC Endpoint(最低でも3種類)が必要 = 料金が発生する
    • Multi-AZ 構成が可能
    • CloudWatch Logs や S3 へのログ出力が可能
  • EIC Endpoint
    • 無料で使用可能
    • VPC 内に1つしか作成できず、特定のサブネット、AZ に紐づくため、単一障害点になる
    • ログ出力は不可

それぞれの違いについては、以下の記事で詳しく解説されていますので、併せてご参照ください。

https://dev.classmethod.jp/articles/compare-eic-endpoint-and-session-manager/

本記事では、Session Manager を経由して EC2 インスタンスの Windows Server に RDP で接続する構成を AWS CDK で実装します。

開発環境

開発環境は下記の通りです。

% sw_vers
ProductName:		macOS
ProductVersion:		14.6.1
BuildVersion:		23G93

% aws --version
aws-cli/2.26.2 Python/3.13.2 Darwin/23.6.0 exe/x86_64

% cdk --version
2.177.0 (build b396961)

実装のポイント

ソースコード全体をお見せする前に、実装のポイントをピックアップして解説いたします。

AmazonSSMManagedInstanceCore をアタッチ

Session Manager を通じて EC2 インスタンスを操作するには、AmazonSSMManagedInstanceCore ポリシーをアタッチした IAM ロールが必要です。

const instanceRole = new iam.Role(this, 'EC2InstanceRole', {
  roleName: `${projectName}-instance-role`,
  assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
  managedPolicies: [
    iam.ManagedPolicy.fromAwsManagedPolicyName(
      'AmazonSSMManagedInstanceCore'
    ),
  ],
});

VPC エンドポイントの作成

※ NAT Gateway を使用しない場合に必要なリソースです

Session Manager を通じてプライベートサブネットに配置した EC2 インスタンスへ接続するには、以下3種類の VPC エンドポイントが必要です。

  • com.amazonaws.ap-northeast-1.ssm
  • com.amazonaws.ap-northeast-1.ssmmessages
  • com.amazonaws.ap-northeast-1.ec2messages
const subnets = vpc.selectSubnets({
  subnetGroupName: 'private',
});
vpc.addInterfaceEndpoint('SSMEndpoint', {
  subnets,
  service: ec2.InterfaceVpcEndpointAwsService.SSM,
});
vpc.addInterfaceEndpoint('SSMMessagesEndpoint', {
  subnets,
  service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
});
vpc.addInterfaceEndpoint('EC2MessagesEndpoint', {
  subnets,
  service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
});

Systems Manager エージェント

Windows Server の AMI には、デフォルトで Systems Manager エージェント(SSM エージェント)がインストールされているため、追加の実装は不要です。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ssm-agent-windows.html

AWS が提供する Windows Server 用の Amazon Machine Images (AMIs) には、デフォルトで AWS Systems Manager エージェント (SSM Agent) がプリインストールされています。

ソースコード

VPC

NAT Gateway を使用する場合は、以下の記事に記載している VPC の実装と同じです。

https://dev.classmethod.jp/articles/running-windows-container-on-fargate-with-cdk/#vpc

NAT Gateway を使用しない場合は、以下の通りです。

vpc.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Construct } from 'constructs';

type VpcStackProps = cdk.StackProps & {
  projectName: string;
};

export class VpcStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: VpcStackProps) {
    super(scope, id, props);

    const { projectName } = props;

    const cidr = '10.100.0.0/16';
    const cidrMask = 24;
    const maxAzs = 2;
    const natGateways = 0;

    // VPC
    const vpc = new ec2.Vpc(this, 'Vpc', {
      vpcName: `${projectName}-vpc`,
      ipAddresses: ec2.IpAddresses.cidr(cidr),
      subnetConfiguration: [
        {
          cidrMask,
          name: 'public',
          subnetType: ec2.SubnetType.PUBLIC,
          mapPublicIpOnLaunch: false,
        },
        {
          cidrMask,
          name: 'private',
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ],
      maxAzs,
      natGateways,
    });

    const subnets = vpc.selectSubnets({
      subnetGroupName: 'private',
    });
    vpc.addInterfaceEndpoint('SSMEndpoint', {
      subnets,
      service: ec2.InterfaceVpcEndpointAwsService.SSM,
    });
    vpc.addInterfaceEndpoint('SSMMessagesEndpoint', {
      subnets,
      service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
    });
    vpc.addInterfaceEndpoint('EC2MessagesEndpoint', {
      subnets,
      service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
    });
  }
}

EC2

ec2.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';

type Ec2StackProps = cdk.StackProps & {
  projectName: string;
};

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

    const { projectName } = props;

    // VPC
    const vpcName = `${projectName}-vpc`;
    const vpc = ec2.Vpc.fromLookup(this, 'Vpc', {
      vpcName,
    });

    // https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/optimize-costs-microsoft-workloads/right-size-selection.html#right-size-selection-next-steps
    const instanceType = ec2.InstanceType.of(
      ec2.InstanceClass.T3,
      ec2.InstanceSize.MEDIUM
    );

    // https://zenn.dev/hiren/scraps/4f768929fcfe25
    const machineImage = new ec2.LookupMachineImage({
      name: `${ec2.WindowsVersion.WINDOWS_SERVER_2019_JAPANESE_FULL_BASE}-*`,
      filters: {
        'image-type': ['machine'],
        state: ['available'],
      },
      owners: ['amazon'],
      windows: true,
    });
    // こちらだと AMI のバージョンが更新されるたびに EC2 インスタンスが再作成されてしまう
    // const machineImage = new ec2.WindowsImage(
    //   ec2.WindowsVersion.WINDOWS_SERVER_2019_JAPANESE_FULL_BASE
    // );

    const instanceSecurityGroup = new ec2.SecurityGroup(
      this,
      'Ec2InstanceSecurityGroup',
      {
        vpc: vpc,
        securityGroupName: `${projectName}-instance-security-group`,
      }
    );

    const instanceRole = new iam.Role(this, 'EC2InstanceRole', {
      roleName: `${projectName}-instance-role`,
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName(
          'AmazonSSMManagedInstanceCore'
        ),
      ],
    });

    const keyPairName = `${projectName}-ec2-key`;
    const keyPair = new ec2.KeyPair(this, 'EC2KeyPair', {
      keyPairName,
    });

    const volumeSize = 100;
    const vpcSubnets = vpc.selectSubnets({ subnetGroupName: 'private' });
    const blockDevices = [
      {
        // ルートボリューム上書き
        deviceName: '/dev/sda1',
        volume: ec2.BlockDeviceVolume.ebs(volumeSize, {
          volumeType: ec2.EbsDeviceVolumeType.GP3,
        }),
      },
    ];

    new ec2.Instance(this, 'EC2Instance', {
      vpc,
      vpcSubnets,
      instanceName: `${projectName}-instance`,
      instanceType,
      machineImage,
      securityGroup: instanceSecurityGroup,
      role: instanceRole,
      keyPair,
      blockDevices,
      ebsOptimized: true,
      requireImdsv2: true,
    });
  }
}

SSH で接続する手順

上記のリソースをデプロイした時点で、マネジメントコンソールから SSH で接続可能です。

作成した EC2 インスタンスの詳細画面で「接続」に進みます。

スクリーンショット 2025-04-16 21.59.22

「接続」します。

スクリーンショット 2025-04-16 21.59.28

SSH で接続できました!

スクリーンショット 2025-04-16 21.59.49

RDP で接続する手順

Session Manager プラグインをインストール

私の環境は MacBook Pro M2 なので、以下のドキュメントを参考にしました。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/install-plugin-macos-overview.html

% curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac_arm64/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
% unzip sessionmanager-bundle.zip
% sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

ポートフォワーディングを実行

以下のドキュメントを参考にしました。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-sessions-start.html#sessions-start-port-forwarding

% aws ssm start-session \
--target <EC2 インスタンス ID> \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["3389"], "localPortNumber":["13389"]}'

Starting session with SessionId: xxxx
Port 13389 opened for sessionId xxxx
Waiting for connections...

続きの手順

ここから先は EIC Endpoint で RDP 接続する際の手順と重複します。

  • RDP 接続用のパスワードを取得
  • Windows App をインストール
  • Windows App から RDP 接続

これらの手順は以下の記事に記載した内容をご参照ください。

https://dev.classmethod.jp/articles/ec2-instance-connect-endpoint-windows-server-rdp-with-cdk/#rdp-%25E3%2581%25A7%25E6%258E%25A5%25E7%25B6%259A%25E3%2581%2599%25E3%2582%258B%25E6%2589%258B%25E9%25A0%2586

Windows App から RDP 接続すると、以下のように Windows Server のデスクトップが表示されます。

スクリーンショット 2025-04-16 22.06.52

おわりに

本記事では、Session Manager を経由して EC2 インスタンスの Windows Server に RDP で接続する構成を AWS CDK で実装してみました。EIC Endpoint と同様に Session Manager でも、踏み台サーバーを用意せずにセキュアかつシンプルな構成を比較的簡単に構築できるのは非常に便利です。

Session Manager は、Multi-AZ 構成が可能であったり、CloudWatch Logs や S3 にログを出力できたりと、運用・監査面で優れた選択肢になります。一方で、NAT Gateway や VPC エンドポイントのコストが発生するため、コスト重視の場合は EIC Endpoint を選択すると良いでしょう。

最後までお読みいただきありがとうございました!この記事が少しでも参考になれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.