この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
みなさん、こんにちは!
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テンプレート (クリックすると展開します)
cfn-ssmtest.yaml
---
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テンプレート (クリックすると展開します)
cfn-onpremises.yaml
---
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の設定によっては、あるいは他のプロキシソフトウェアを利用している場合は、今回のように上手く接続できるとは限らない可能性があります。
その場合はご容赦ください。