AWS Systems Managerセッションマネージャーの「ポートフォワーディング」をHTTPプロキシ経由で利用する (Linux編)
みなさん、こんにちは!
AWS事業本部の青柳@福岡オフィスです。
今回は AWS Systems Manager (SSM) の「セッションマネージャー」機能について実験してみました。
同じような内容で「Linux編」と「Windows編」を執筆しています。
こちらのブログエントリは「Linux編」です。
Windows編は下記リンク先を参照してください。
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" 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: ssmtest 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-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 EC2InstanceType: Type: String Default: t3.micro EC2KeyName: Type: AWS::EC2::KeyPair::KeyName EC2VolumeType: Type: String Default: gp2 EC2VolumeSize: Type: String Default: 8 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/xvda 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 (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: onpremises 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-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 EC2BastionInstanceType: Type: String Default: t3.micro EC2BastionVolumeType: Type: String Default: gp2 EC2BastionVolumeSize: Type: String Default: 8 EC2ClientImageID: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 EC2ClientInstanceType: Type: String Default: t3.micro EC2ClientVolumeType: Type: String Default: gp2 EC2ClientVolumeSize: Type: String Default: 8 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: 22 ToPort: 22 CidrIp: !Ref MyIP Description: "SSH 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: 22 ToPort: 22 SourceSecurityGroupId: !Ref SecurityGroupBastion Description: "SSH 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/xvda 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/xvda 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」へSSHで接続します。
踏み台サーバー「bastion」を経由して接続するには、以下のようにします。
$ ssh -i ~/.ssh/<pemfile> -o ProxyCommand='ssh -i ~/.ssh/<pemfile> -W %h:%p ec2-user@<bastion_public_ip>' ec2-user@<client_public_ip>
HTTPプロキシ参照の設定を行います。
設定方法は、AWSドキュメントの「AWS CLI」の下記ページの記述に従います。
HTTP プロキシを使用する - AWS Command Line Interface
~/.bashrc
ファイルに以下の行を追記します。
export HTTP_PROXY=http://<proxy_private_ip>:8080 export HTTPS_PROXY=http://<proxy_private_ip>:8080
なお、今回は疑似的なクライアントとしてEC2インスタンスを利用していますので、以下の行も記述する必要があります。
(これは、EC2インスタンスメタデータへアクセスする際にプロキシを使用しないようにする設定です。物理のクライアントPCを利用している際は設定不要です)
export NO_PROXY=169.254.169.254
~/.bashrc
ファイルを編集しましたら、一度ログアウトして再度ログインするか、以下のコマンドを実行して設定を反映させます。
$ source ~/.bashrc
プロキシ参照の設定を行いましたので、実際にプロキシを使ってインターネットへアクセスできることを確認しましょう。
AWSが提供しているアクセス元IPアドレス確認サイトcheckip.amazonaws.com
を利用します。
$ curl https://checkip.amazonaws.com/ XX.XX.XX.XX
ここまで上手くいっていれば、アクセス元のIPアドレスが表示されるはずです。
表示されているIPアドレスが「EC2インスタンス『proxy』のパブリックIPアドレス」であることを確認してください。
※ 補足
上記のように~/.bashrc
ファイルへ記述することで、AWS CLIおよび他のLinuxコマンドがHTTPプロキシを利用するようになるはずです。
しかし、場合によっては設定が反映されない場合もあるようですので、その際は各Linuxコマンドの設定ファイルにプロキシ参照の設定を行ってください。
今回の手順ではcurl
コマンドやyum
コマンドを利用していますので、~/.curl
ファイルや/etc/yum.conf
ファイルに以下の行を記述するとプロキシを利用するようになると思います。
proxy=http://<proxy_private_ip>:8080
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のインストール・設定を行う
今回はAmazon Linux 2を利用していますので、最初からAWS CLIがインストールされています。
ただし、バージョンが古いため、バージョンアップするか削除後に新規にインストールした方がよいでしょう。
AWS CLI のインストール - AWS Command Line Interface
最初からインストールされているAWS CLIのアンインストール:
$ sudo yum erase -y awscli
AWS CLIインストーラーのダウンロードとインストール:
$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" $ unzip awscliv2.zip $ sudo ./aws/install
インストールされたことの確認:
$ aws --version aws-cli/2.0.25 Python/3.7.3 Linux/4.14.177-139.254.amzn2.x86_64 botocore/2.0.0dev29
AWS CLIをインストールしましたら、AWS CLIを実行するユーザーの認証情報設定を行います。
設定ファイルと認証情報ファイルの設定 - AWS Command Line Interface
aws configure
コマンドを実行して、さきほどIAMユーザーを作成した際に取得した「アクセスキーID」「シークレットアクセスキー」を入力します。
$ 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のコマンドが実行できることを確認します。
$ 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
Linuxの場合は、以下の手順でインストールできます。
$ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm" -o "session-manager-plugin.rpm" $ sudo yum install -y session-manager-plugin.rpm
インストールが終わりましたら、以下のコマンドを実行してインストール結果を確認しましょう。
$ session-manager-plugin The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
接続を試してみる
SSMセッションマネージャーを使ってEC2インスタンスへ接続してみる
さて、全ての準備が整いましたので、いよいよセッションマネージャーを使ってEC2インスタンスへ接続してみたいと思います。
セッションを開始する - AWS Systems Manager
以下のようにコマンドを実行します。
$ aws ssm start-session --target <instance-id>
実際に接続してみると、以下のようになりました。
セッションIDを見るとIAMユーザーremote-access-user
でセッションが開始されたことが分かります。
セッションが開始されると、マネジメントコンソールでSSMセッションマネージャーを利用する時と同様にプロンプトsh-4.2$
が表示されます。
$ aws ssm start-session --target i-05003b21db12d64e1 Starting session with SessionId: remote-access-user-0b1738dc9c8e4510b sh-4.2$
接続先でコマンドを実行してみましょう。
sh-4.2$ uname -a Linux ip-192-168-128-199.ap-northeast-1.compute.internal 4.14.177-139.254.amzn2.x86_64 #1 SMP Thu May 7 18:48:23 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux sh-4.2$
ちゃんと対象のEC2インスタンスへ接続できていることが確認できました。
確認が終わりましたら、exit
コマンドで接続を終了してください。
SSMセッションマネージャーの「ポートフォワーディング」を使ってみる
最後に、今回の最終目標である「HTTPプロキシ経由でSSMセッションマネージャーの『ポートフォワーディング』を利用する」を試してみたいと思います。
今回は、ポートフォワーディングを使って接続する対象として「postgresql」を使いたいと思います。
対象EC2インスタンスへのpostgresqlのインストールと設定
対象EC2インスタンスへログインします。
(マネジメントコンソールからSSMセッションマネージャーで接続しても、さきほど設定したばかりのAWS CLIからのSSMセッションマネージャー接続でも、どちらでも構いません)
yumを使ってpostgresql (サーバーの方) をインストールします:
$ sudo yum install -y postgresql-server
デフォルトユーザーpostgres
のパスワードを設定します:
$ sudo passwd postgres
ユーザーpostgres
の権限で、データベースを初期化します:
$ su - postgres Password: $ initdb $ exit
postgresqlを起動します:
$ sudo systemctl enable postgresql.service $ sudo systemctl start postgresql.service
postgresqlサーバーへ接続できることを確認します:
$ psql -l -U postgres List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -----------+----------+----------+-------------+-------------+----------------------- postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres (3 rows)
クライアントへのpostgresqlクライアントツールのインストール
クライアントからpostgresqlサーバーへ接続するために、postgresqlクライアントツールをインストールします。
$ sudo yum install -y postgresql
「ポートフォワーディング」でpostgresqlのポートへ接続する
お待たせしました!!
それでは、セッションマネージャーの「ポートフォワーディング」機能を使ってEC2インスタンスへ接続してみたいと思います。
以下のようにコマンドを実行します。
$ aws ssm start-session \ --target <instance-id> \ --document-name AWS-StartPortForwardingSession \ --parameters '{"portNumber":["5432"], "localPortNumber":["15432"]}'
実際に接続してみると、以下のようになります。
IAMユーザーremote-access-user
でセッションが開始された後、セッション内でポート15432
がオープンされたことが分かります。
$ aws ssm start-session \ > --target i-05003b21db12d64e1 \ > --document-name AWS-StartPortForwardingSession \ > --parameters '{"portNumber":["5432"], "localPortNumber":["15432"]}' Starting session with SessionId: remote-access-user-0eb75e03d274256e9 Port 15432 opened for sessionId remote-access-user-0eb75e03d274256e9.
ポートフォワーディングによる接続が確立しましたので、postgresqlサーバーへ接続したいと思います。
別のターミナルでクライアントへ接続して、以下のコマンドを実行します。
$ psql -l -U postgres -h localhost -p 15432
以下のように表示されれば成功です!
$ psql -l -U postgres -h localhost -p 15432 List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -----------+----------+----------+-------------+-------------+----------------------- postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres (3 rows)
いかがでしょうか?
各サーバーを行ったり来たりしながら設定を行うので分かり辛いところがあるかもしれませんが、ここまでの手順を1ステップずつ実施すれば、無事に接続できたのではないかと思います。
おわりに
HTTPプロキシ経由でしかインターネットへアクセスできない環境で、SSMセッションマネージャーの「ポートフォワーディング」機能が利用できることが確認できました。
今回は疑似的にオンプレミス環境をAWS上に構築して検証を行いましたが、実際のオンプレミス環境の場合でも今回の手順に沿って実施して頂ければ (一部読み替えが必要ですが) 利用できるのではないかと思います。
なお、今回はHTTPプロキシとしてSquidを利用しましたが、Squidの設定によっては、あるいは他のプロキシソフトウェアを利用している場合は、今回のように上手く接続できるとは限らない可能性があります。
その場合はご容赦ください。