AWS Systems Managerセッションマネージャーの「ポートフォワーディング」をHTTPプロキシ経由で利用する (Windows編)
みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。
今回は AWS Systems Manager (SSM) の「セッションマネージャー」機能について実験してみました。
同じような内容で「Linux編」と「Windows編」を執筆しています。
こちらのブログエントリは「Windows編」です。
Linux編は下記リンク先を参照してください。
Linux編とWindows編で内容が重複している箇所が多々ありますが、ご了承ください。
はじめに
セッションマネージャーには「プライベートサブネットのEC2に直接接続できる」「IAMでアクセス制御を行うためパスワードや秘密鍵の管理が不要」などのメリットがありますが、「マネジメントコンソールからWebブラウザだけでEC2へシェルログインして操作できる」という隠れた(?)利点もあります。
特に、インターネットへのアクセスがHTTPプロキシ経由となっている企業・学校などに所属されている方は、助かっている方も多いのではないでしょうか。
ところが、セッションマネージャーの便利な機能「ポートフォワーディング」を利用しようとすると、一転して話が変わります。
ポートフォワーディング機能はマネジメントコンソールから利用することができず、AWS CLIを使う必要があるためです。
しかし、AWS CLIも内部ではAWSのWeb APIを呼び出すことで処理を実行しており、プロキシ経由でのHTTP(S)の通信が行えればAWS CLIを利用できるはずです。
そこで、実際にプロキシ経由で利用できるか、試してみました。
SSMセッションマネージャーの「ポートフォワーディング」について
機能や手順については、こちらのブログが分かり易いかと思います。
SSMセッションマネージャーの「ポートフォワーディング」を利用する際、クライアントから接続先EC2インスタンスへの通信経路はこのようになります。
クライアントからSSMまでHTTPSで通信できればよい訳ですから、今回はHTTPプロキシ経由でこのように接続してみようと思います。
検証環境
「さあ、試してみよう」と思ったところ、困ったことに気付きました。
弊社ではHTTPプロキシではなく「SOCKSプロキシ」を利用しており、そもそも、現在は絶賛「テレワーク中」であるため、HTTPプロキシを使って実際に試すことができないのです。
そこで、AWS上に疑似的に「HTTPプロキシを設置しているオンプレミス環境」を構築して、試すことにしました。
図の左側が、SSMセッションマネージャーで接続する対象となるAWS環境です。
図の右側のように、別のAWSアカウントで疑似オンプレミス環境を構築します。
(別のAWSアカウントを用意するのが難しければ、同一AWSアカウントの別のVPCでも構いません)
パブリックサブネットにEC2インスタンスを配置してHTTPプロキシをインストールします。
プライベートサブネットには検証用クライアントとなるEC2インスタンスを配置し、プロキシを参照する設定を行います。
ポイントとしては、パブリックサブネットにNATゲートウェイ/NATインスタンスを設置しないことです。
そうすることで、HTTPプロキシ経由でなければインターネットへアクセスできないオンプレミス環境を再現することができます。
なお、検証用クライアントへのログインのために踏み台サーバー (Bastion) を利用します。
(ここもSSMセッションマネージャーを使うという方法もありますが、あちこちでSSMによる接続が登場すると分かり辛くなると思いますので、今回は踏み台サーバーを採用しました)
検証環境を構築する
AWS環境のプロビジョニング
接続対象のAWS環境、疑似オンプレミス環境を構築するCloudFormationテンプレートを用意しました。
それぞれ、環境を作成したいAWSアカウント・リージョンで実行してください。
(事前にキーペアを準備しておいてください。また、CloudFormationスタック作成時に自分のIPアドレスを聞かれますので、入力してください)
接続対象AWS環境作成CloudFormationテンプレート (クリックすると展開します)
--- AWSTemplateFormatVersion: "2010-09-09" Description: "SSM test environment (Windows)" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "General Information" Parameters: - SystemName - Label: default: "Network Configuration" Parameters: - CidrBlockVPC - CidrBlockSubnetPublic - CidrBlockSubnetPrivate - Label: default: "EC2 Instance Configuration" Parameters: - EC2ImageID - EC2InstanceType - EC2KeyName - EC2VolumeType - EC2VolumeSize Parameters: SystemName: Type: String Default: ssmtestwin CidrBlockVPC: Type: String Default: 192.168.0.0/16 CidrBlockSubnetPublic: Type: String Default: 192.168.0.0/24 CidrBlockSubnetPrivate: Type: String Default: 192.168.128.0/24 EC2ImageID: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base EC2InstanceType: Type: String Default: t3.micro EC2KeyName: Type: AWS::EC2::KeyPair::KeyName EC2VolumeType: Type: String Default: gp2 EC2VolumeSize: Type: String Default: 30 Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref CidrBlockVPC EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: Name Value: !Sub "${SystemName}-vpc" - Key: System Value: !Ref SystemName InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub "${SystemName}-igw" - Key: System Value: !Ref SystemName VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC SubnetPublic: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref AWS::Region CidrBlock: !Ref CidrBlockSubnetPublic MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${SystemName}-public-subnet" - Key: System Value: !Ref SystemName SubnetPrivate: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref AWS::Region CidrBlock: !Ref CidrBlockSubnetPrivate Tags: - Key: Name Value: !Sub "${SystemName}-private-subnet" - Key: System Value: !Ref SystemName EIPNatGateway: DependsOn: - VPCGatewayAttachment Type: AWS::EC2::EIP Properties: Domain: vpc NatGateway: DependsOn: - EIPNatGateway - SubnetPublic Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt EIPNatGateway.AllocationId SubnetId: !Ref SubnetPublic Tags: - Key: Name Value: !Sub "${SystemName}-natgateway" - Key: System Value: !Ref SystemName RouteTablePublic: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${SystemName}-public-rtb" - Key: System Value: !Ref SystemName RouteTablePrivate: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${SystemName}-private-rtb" - Key: System Value: !Ref SystemName RouteIGW: DependsOn: - VPCGatewayAttachment Type: AWS::EC2::Route Properties: RouteTableId: !Ref RouteTablePublic DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway RouteNatGateway: DependsOn: - VPCGatewayAttachment - NatGateway Type: AWS::EC2::Route Properties: RouteTableId: !Ref RouteTablePrivate DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway RouteTableAssociationPublic: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref SubnetPublic RouteTableId: !Ref RouteTablePublic RouteTableAssociationPrivate: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref SubnetPrivate RouteTableId: !Ref RouteTablePrivate SecurityGroupServer: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${SystemName}-server-sg" GroupDescription: "Security group for server" VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${SystemName}-server-sg" - Key: System Value: !Ref SystemName IAMRoleServer: Type: AWS::IAM::Role Properties: RoleName: !Sub "${SystemName}-server-role" AssumeRolePolicyDocument: | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore Path: / IAMInstanceProfileServer: Type: AWS::IAM::InstanceProfile Properties: InstanceProfileName: !Sub "${SystemName}-server-role" Roles: - !Ref IAMRoleServer Path: / EC2InstanceServer: Type: AWS::EC2::Instance Properties: ImageId: !Ref EC2ImageID InstanceType: !Ref EC2InstanceType KeyName: !Ref EC2KeyName BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: !Ref EC2VolumeType VolumeSize: !Ref EC2VolumeSize NetworkInterfaces: - DeviceIndex: 0 SubnetId: !Ref SubnetPrivate GroupSet: - !Ref SecurityGroupServer IamInstanceProfile: !Ref IAMInstanceProfileServer Tags: - Key: Name Value: !Sub "${SystemName}-server" - Key: System Value: !Ref SystemName Outputs: VPC: Value: !Ref VPC Export: Name: !Sub "${AWS::StackName}::VPC" SubnetPublic: Value: !Ref SubnetPublic Export: Name: !Sub "${AWS::StackName}::SubnetPublic" SubnetPrivate: Value: !Ref SubnetPrivate Export: Name: !Sub "${AWS::StackName}::SubnetPrivate" SecurityGroupServer: Value: !Ref SecurityGroupServer Export: Name: !Sub "${AWS::StackName}::SecurityGroupServer" IAMRoleServer: Value: !Ref IAMRoleServer Export: Name: !Sub "${AWS::StackName}::IAMRoleServer" IAMInstanceProfileServer: Value: !Ref IAMInstanceProfileServer Export: Name: !Sub "${AWS::StackName}::IAMInstanceProfileServer" EC2InstanceServer: Value: !Ref EC2InstanceServer Export: Name: !Sub "${AWS::StackName}::EC2InstanceServer"
疑似オンプレミス環境作成CloudFormationテンプレート (クリックすると展開します)
--- AWSTemplateFormatVersion: "2010-09-09" Description: "SSM test environment (Windows) (virtual 'on-premises' environment)" Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "General Information" Parameters: - SystemName - Label: default: "Network Configuration" Parameters: - CidrBlockVPC - CidrBlockSubnetPublic - CidrBlockSubnetPrivate - MyIP - Label: default: "EC2 Instance Configuration (Common)" Parameters: - EC2KeyName - Label: default: "EC2 Instance Configuration (Proxy)" Parameters: - EC2ProxyImageID - EC2ProxyInstanceType - EC2ProxyVolumeType - EC2ProxyVolumeSize - Label: default: "EC2 Instance Configuration (Bastion)" Parameters: - EC2BastionImageID - EC2BastionInstanceType - EC2BastionVolumeType - EC2BastionVolumeSize - Label: default: "EC2 Instance Configuration (Client)" Parameters: - EC2ClientImageID - EC2ClientInstanceType - EC2ClientVolumeType - EC2ClientVolumeSize Parameters: SystemName: Type: String Default: onpremiseswin CidrBlockVPC: Type: String Default: 10.0.0.0/16 CidrBlockSubnetPublic: Type: String Default: 10.0.0.0/24 CidrBlockSubnetPrivate: Type: String Default: 10.0.128.0/24 MyIP: Type: String EC2KeyName: Type: AWS::EC2::KeyPair::KeyName EC2ProxyImageID: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 EC2ProxyInstanceType: Type: String Default: t3.micro EC2ProxyVolumeType: Type: String Default: gp2 EC2ProxyVolumeSize: Type: String Default: 8 EC2BastionImageID: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base EC2BastionInstanceType: Type: String Default: t3.micro EC2BastionVolumeType: Type: String Default: gp2 EC2BastionVolumeSize: Type: String Default: 30 EC2ClientImageID: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base EC2ClientInstanceType: Type: String Default: t3.micro EC2ClientVolumeType: Type: String Default: gp2 EC2ClientVolumeSize: Type: String Default: 30 Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref CidrBlockVPC EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: Name Value: !Sub "${SystemName}-vpc" - Key: System Value: !Ref SystemName InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub "${SystemName}-igw" - Key: System Value: !Ref SystemName VPCGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC SubnetPublic: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref AWS::Region CidrBlock: !Ref CidrBlockSubnetPublic MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${SystemName}-public-subnet" - Key: System Value: !Ref SystemName SubnetPrivate: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select - 0 - Fn::GetAZs: !Ref AWS::Region CidrBlock: !Ref CidrBlockSubnetPrivate Tags: - Key: Name Value: !Sub "${SystemName}-private-subnet" - Key: System Value: !Ref SystemName RouteTablePublic: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${SystemName}-public-rtb" - Key: System Value: !Ref SystemName RouteTablePrivate: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${SystemName}-private-rtb" - Key: System Value: !Ref SystemName RouteIGW: DependsOn: - VPCGatewayAttachment Type: AWS::EC2::Route Properties: RouteTableId: !Ref RouteTablePublic DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway RouteTableAssociationPublic: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref SubnetPublic RouteTableId: !Ref RouteTablePublic RouteTableAssociationPrivate: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref SubnetPrivate RouteTableId: !Ref RouteTablePrivate SecurityGroupProxy: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${SystemName}-proxy-sg" GroupDescription: "Security group for Proxy" VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref MyIP Description: "SSH access from MyIP" - IpProtocol: tcp FromPort: 8080 ToPort: 8080 CidrIp: !Ref CidrBlockVPC Description: "Access from this VPC to Proxy" Tags: - Key: Name Value: !Sub "${SystemName}-proxy-sg" - Key: System Value: !Ref SystemName SecurityGroupBastion: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${SystemName}-bastion-sg" GroupDescription: "Security group for Bastion" VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 3389 ToPort: 3389 CidrIp: !Ref MyIP Description: "RDP access from MyIP" Tags: - Key: Name Value: !Sub "${SystemName}-bastion-sg" - Key: System Value: !Ref SystemName SecurityGroupClient: Type: AWS::EC2::SecurityGroup Properties: GroupName: !Sub "${SystemName}-client-sg" GroupDescription: "Security group for Client" VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 3389 ToPort: 3389 SourceSecurityGroupId: !Ref SecurityGroupBastion Description: "RDP access from Bastion" Tags: - Key: Name Value: !Sub "${SystemName}-client-sg" - Key: System Value: !Ref SystemName EC2InstanceProxy: Type: AWS::EC2::Instance Properties: ImageId: !Ref EC2ProxyImageID InstanceType: !Ref EC2ProxyInstanceType KeyName: !Ref EC2KeyName SourceDestCheck: false BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeType: !Ref EC2ProxyVolumeType VolumeSize: !Ref EC2ProxyVolumeSize NetworkInterfaces: - DeviceIndex: 0 SubnetId: !Ref SubnetPublic GroupSet: - !Ref SecurityGroupProxy Tags: - Key: Name Value: !Sub "${SystemName}-proxy" - Key: System Value: !Ref SystemName EC2InstanceBastion: Type: AWS::EC2::Instance Properties: ImageId: !Ref EC2BastionImageID InstanceType: !Ref EC2BastionInstanceType KeyName: !Ref EC2KeyName BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: !Ref EC2BastionVolumeType VolumeSize: !Ref EC2BastionVolumeSize NetworkInterfaces: - DeviceIndex: 0 SubnetId: !Ref SubnetPublic GroupSet: - !Ref SecurityGroupBastion Tags: - Key: Name Value: !Sub "${SystemName}-bastion" - Key: System Value: !Ref SystemName EC2InstanceClient: Type: AWS::EC2::Instance Properties: ImageId: !Ref EC2ClientImageID InstanceType: !Ref EC2ClientInstanceType KeyName: !Ref EC2KeyName BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: !Ref EC2ClientVolumeType VolumeSize: !Ref EC2ClientVolumeSize NetworkInterfaces: - DeviceIndex: 0 SubnetId: !Ref SubnetPrivate GroupSet: - !Ref SecurityGroupClient Tags: - Key: Name Value: !Sub "${SystemName}-client" - Key: System Value: !Ref SystemName Outputs: VPC: Value: !Ref VPC Export: Name: !Sub "${AWS::StackName}::VPC" SubnetPublic: Value: !Ref SubnetPublic Export: Name: !Sub "${AWS::StackName}::SubnetPublic" SubnetPrivate: Value: !Ref SubnetPrivate Export: Name: !Sub "${AWS::StackName}::SubnetPrivate" SecurityGroupProxy: Value: !Ref SecurityGroupProxy Export: Name: !Sub "${AWS::StackName}::SecurityGroupProxy" SecurityGroupBastion: Value: !Ref SecurityGroupBastion Export: Name: !Sub "${AWS::StackName}::SecurityGroupBastion" SecurityGroupClient: Value: !Ref SecurityGroupClient Export: Name: !Sub "${AWS::StackName}::SecurityGroupClient" EC2InstanceProxy: Value: !Ref EC2InstanceProxy Export: Name: !Sub "${AWS::StackName}::EC2InstanceProxy" EC2InstanceBastion: Value: !Ref EC2InstanceBastion Export: Name: !Sub "${AWS::StackName}::EC2InstanceBastion" EC2InstanceClient: Value: !Ref EC2InstanceClient Export: Name: !Sub "${AWS::StackName}::EC2InstanceClient"
SSMセッションマネージャーで接続できることの確認
AWS環境を作成しましたら、接続対象EC2インスタンスへSSMセッションマネージャーで接続できることを確認しましょう。
対象EC2インスタンスを右クリックして「接続」を選択し、「セッションマネージャー」を選択して「接続」をクリックします。
接続できることを確認できましたら、次へ進みましょう。
HTTPプロキシを構築する
今度は疑似オンプレミス環境の方に移って、まずはHTTPプロキシを構築します。
HTTPプロキシのソフトウェアとしてSquidを使用します。
EC2インスタンス「proxy」へSSHで接続します:
$ ssh -i ~/.ssh/<pemfile> ec2-user@<proxy_public_ip>
yumを使ってSquidをインストールします:
$ sudo yum install -y squid
Squidの設定ファイルを編集します:
$ sudo vi /etc/squid/squid.conf
プロキシの待ち受けポートを「8080」に変更します:
: # Squid normally listens to port 3128 http_port 3128 → 8080 へ変更する :
設定ファイルを保存して、Squidを起動します:
$ sudo systemctl enable squid.service $ sudo systemctl start squid.service
Squidが正常に起動すれば準備はOKです。
クライアントでプロキシ参照の設定を行う
EC2インスタンス「client」へリモートデスクトップ接続で接続します。
踏み台サーバー「bastion」へリモートデスクトップ接続でいったん接続し、「bastion」上で更にリモートデスクトップ接続を起動して「client」へ接続します。
HTTPプロキシ参照の設定を行います。
設定方法は、AWSドキュメントの「AWS CLI」の下記ページの記述に従います。
HTTP プロキシを使用する - AWS Command Line Interface
コマンドプロンプトを起動して、以下のコマンドを実行します。
C:\> setx HTTP_PROXY http://<proxy_private_ip>:8080 C:\> setx HTTPS_PROXY http://<proxy_private_ip>:8080
なお、今回は疑似的なクライアントとしてEC2インスタンスを利用していますので、以下のコマンドも実行する必要があります。
(これは、EC2インスタンスメタデータへアクセスする際にプロキシを使用しないようにする設定です。物理のクライアントPCを利用している際は設定不要です)
C:\> setx NO_PROXY 169.254.169.254
プロキシ参照の設定を行いましたので、実際にプロキシを使ってインターネットへアクセスできることを確認しましょう。
AWSが提供しているアクセス元IPアドレス確認サイトcheckip.amazonaws.com
を利用します。
PowerShellコンソールを起動して、以下のコマンドレットを実行します。
PS C:\> Invoke-WebRequest -Uri https://checkip.amazonaws.com/
ただし、PowerShellのバージョンが7.0未満の場合は、環境変数HTTP_PROXY
、HTTPS_PROXY
の設定を残念ながら参照してくれないようです。
その場合は、以下のようにプロキシサーバーのアドレスを直接指定して実行します。
PS C:\> Invoke-WebRequest -Uri https://checkip.amazonaws.com/ -Proxy http://<proxy_private_ip>:8080
実行結果は以下のようになりました。
StatusCode : 200 StatusDescription : OK Content : {49, 56, 46, 50...} RawContent : HTTP/1.1 200 OK Connection: keep-alive Content-Length: 15 Date: Wed, 24 Jun 2020 11:30:20 GMT Server: lighttpd/1.4.53 XX.XX.XX.XX Headers : {[Connection, keep-alive], [Content-Length, 15], [Date, Wed, 24 Jun 2020 11:30:20 GMT], [Server, lighttpd/1.4.53]} RawContentLength : 15
ここまで上手くいっていれば、アクセス元のIPアドレスが表示されるはずです。
表示されているIPアドレスが「EC2インスタンス『proxy』のパブリックIPアドレス」であることを確認してください。
SSMセッションマネージャーを利用するためのIAMユーザーの作成
冒頭でチラッと書きましたように、SSMセッションマネージャーはIAMユーザーによるアクセス制御が可能です。
クライアントでSSMセッションマネージャーを使うためのIAMユーザーを作成しましょう。
まず、アクセス権限を定義するIAMポリシーを作成します。
マネジメントコンソールで「IAM」から「ポリシー」を選択して、「ポリシーの作成」をクリックします。
アクセス権限を編集する画面になりますので、「JSON」タブを選択します。
テキストボックスに以下の内容を貼り付けて、「ポリシーの確認」をクリックします。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:StartSession" ], "Resource": [ "arn:aws:ec2:*:*:instance/*", "arn:aws:ssm:*:*:document/AWS-StartPortForwardingSession" ] }, { "Effect": "Allow", "Action": [ "ssm:TerminateSession" ], "Resource": [ "arn:aws:ssm:*:*:session/${aws:username}-*" ] } ] }
ポリシーの名前を入力して、「ポリシーの作成」をクリックします。
(名前は任意ですが、ここではssm-session-manager-policy
としました)
これでIAMポリシーが作成されました。
続けて、IAMユーザーを作成します。
マネジメントコンソールで「IAM」から「ユーザー」を選択して、「ユーザーを追加」をクリックします。
ユーザーの名前を入力します。
(名前は任意ですが、ここではremote-access-user
としました)
アクセスの種類のうち「プログラムによるアクセス」にのみチェックを入れます。
「次のステップ: アクセス権限」をクリックします。
「既存のポリシーを直接アタッチ」を選択します。
ポリシーの一覧から、さきほど作成したIAMポリシーを探して、ポリシー名の左側のチェックボックスにチェックを入れます。
(検索窓にポリシー名を入力すると探すのが楽です)
「次のステップ: タグ」をクリックします。
次の画面「タグの追加 (オプション)」は何も入力せずスキップします。
確認画面になりますので、内容を確認して「ユーザーの作成」をクリックします。
AWS CLIを使ってSSMセッションマネージャーを使うために必要となる「アクセスキーID」「シークレットアクセスキー」が表示されます。
この画面を閉じてしまうと「シークレットアクセスキー」は二度と確認することができなくなりますので、「.csvのダウンロード」をクリックして保存しましょう。
(保存したcsvファイルやメモしたシークレットアクセスキーは、安全な場所に保管してください)
これでIAMユーザーも作成されました。
次の手順へ進みます。
クライアントでAWS CLIのインストール・設定を行う
以下のページを参照して、AWS CLIをインストールします。
Windows への AWS CLI バージョン 2 のインストール - AWS Command Line Interface
インストーラーを下記リンク先からダウンロードします。
https://awscli.amazonaws.com/AWSCLIV2.msi
ダウンロードしたインストーラー (MSIファイル) をダブルクリックして実行します。
設定は全てデフォルト値で構いません。
インストールが終わりましたら、コマンドが実行できることを確認しましょう。
C:\> aws --version aws-cli/2.0.25 Python/3.7.7 Windows/10 botocore/2.0.0dev29
AWS CLIをインストールしましたら、AWS CLIを実行するユーザーの認証情報設定を行います。
設定ファイルと認証情報ファイルの設定 - AWS Command Line Interface
aws configure
コマンドを実行して、さきほどIAMユーザーを作成した際に取得した「アクセスキーID」「シークレットアクセスキー」を入力します。
C:\> aws configure AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Default region name [None]: ap-northeast-1 Default output format [None]:
これで認証情報が保存されました。
指定したIAMユーザーによってAWS CLIのコマンドが実行できることを確認します。
C:\> aws sts get-caller-identity { "UserId": "XXXXXXXXXXXXXXXXXXXXX", "Account": "123456789012", "Arn": "arn:aws:iam::123456789012:user/remote-access-user" }
このようにArn
欄にIAMユーザー名remote-access-user
が表示されていればOKです。
(エラーとなった場合は、ここまでの手順を確認してください)
※ 補足
「EC2インスタンスに対してIAMによるアクセス権限を与える場合は『IAMロール』と『インスタンスプロファイル』を使うのがベストプラクティスじゃないの?」と思われる方もいらっしゃると思います。
その通りなのですが、今回はあくまで「オンプレミス環境のクライアント」を疑似的に再現しているため、アクセスキーIDとシークレットアクセスキーによる認証情報の設定手順を採用しています。
Session Manager Pluginのインストール
これでAWS CLIを実行することができるようになりましたが、AWS CLIでSSMセッションマネージャーを使うためには「Session Manager Plugin」を追加でインストールする必要があります。
(オプション) AWS CLI 用の Session Manager Plugin をインストールする - AWS Systems Manager
インストーラーを下記リンク先からダウンロードします。
https://s3.amazonaws.com/session-manager-downloads/plugin/latest/windows/SessionManagerPluginSetup.exe
ダウンロードしたインストーラー (EXEファイル) をダブルクリックして実行します。
設定は全てデフォルト値で構いません。
インストールが終わりましたら、以下のコマンドを実行してインストール結果を確認しましょう。
C:\> session-manager-plugin The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
接続を試してみる
SSMセッションマネージャーを使ってEC2インスタンスへ接続してみる
さて、全ての準備が整いましたので、いよいよセッションマネージャーを使ってEC2インスタンスへ接続してみたいと思います。
セッションを開始する - AWS Systems Manager
以下のようにコマンドを実行します。
C:\> aws ssm start-session --target <instance-id>
実際に接続してみると、以下のようになりました。
セッションIDを見るとIAMユーザーremote-access-user
でセッションが開始されたことが分かります。
セッションが開始されると、マネジメントコンソールでSSMセッションマネージャーを利用する時と同様にプロンプトPS C:\Windows\system32>
が表示されます。
C:\> aws ssm start-session --target i-0c8ab910de5d74cd8 Starting session with SessionId: remote-access-user-090615001a0b9ce6c Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved. PS C:\Windows\system32>
接続先でコマンドを実行してみましょう。
PS C:\Windows\system32> Get-ComputerInfo WindowsBuildLabEx : 17763.1.amd64fre.rs5_release.180914-1434 WindowsCurrentVersion : 6.3 WindowsEditionId : ServerDatacenter WindowsInstallationType : Server WindowsInstallDateFromRegistry : 2020/06/23 6:55:14 WindowsProductId : XXXXX-XXXXX-XXXXX-XXXXX WindowsProductName : Windows Server 2019 Datacenter WindowsRegisteredOrganization : Amazon.com WindowsRegisteredOwner : EC2 WindowsSystemRoot : C:\Windows WindowsVersion : 1809 (以下省略) PS C:\Windows\system32>
ちゃんと対象のEC2インスタンスへ接続できていることが確認できました。
確認が終わりましたら、exit
コマンドで接続を終了してください。
SSMセッションマネージャーの「ポートフォワーディング」を使ってみる
最後に、今回の最終目標である「HTTPプロキシ経由でSSMセッションマネージャーの『ポートフォワーディング』を利用する」を試してみたいと思います。
今回は、ポートフォワーディングを使って接続する対象として「リモートデスクトップ接続 (RDP)」を使いたいと思います。
以下のようにコマンドを実行します。
C:\> aws ssm start-session ^ --target <instance-id> ^ --document-name AWS-StartPortForwardingSession ^ --parameters portNumber="3389",localPortNumber="13389"
実際に接続してみると、以下のようになります。
IAMユーザーremote-access-user
でセッションが開始された後、セッション内でポート13389
がオープンされたことが分かります。
C:\> aws ssm start-session ^ More? --target i-0c8ab910de5d74cd8 ^ More? --document-name AWS-StartPortForwardingSession ^ More? --parameters portNumber="3389",localPortNumber="13389" Starting session with SessionId: remote-access-user-0e20f92a19c70c08d Port 13389 opened for sessionId remote-access-user-0e20f92a19c70c08d.
ポートフォワーディングによる接続が確立しましたので、リモートデスクトップ接続で対象EC2インスタンスへ接続したいと思います。
「リモートデスクトップ接続」を起動して、接続先アドレスにlocalhost:13389
を指定して接続します。
資格情報はAdministrator
およびパスワードを入力してください。
(Administratorのパスワードは、マネジメントコンソールから「Windowsパスワードの取得」で確認してください)
対象のEC2インスタンスへリモートデスクトップ接続できれば成功です!
いかがでしょうか?
各サーバーを行ったり来たりしながら設定を行うので分かり辛いところがあるかもしれませんが、ここまでの手順を1ステップずつ実施すれば、無事に接続できたのではないかと思います。
おわりに
HTTPプロキシ経由でしかインターネットへアクセスできない環境で、SSMセッションマネージャーの「ポートフォワーディング」機能が利用できることが確認できました。
今回は疑似的にオンプレミス環境をAWS上に構築して検証を行いましたが、実際のオンプレミス環境の場合でも今回の手順に沿って実施して頂ければ (一部読み替えが必要ですが) 利用できるのではないかと思います。
なお、今回はHTTPプロキシとしてSquidを利用しましたが、Squidの設定によっては、あるいは他のプロキシソフトウェアを利用している場合は、今回のように上手く接続できるとは限らない可能性があります。
その場合はご容赦ください。