3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[AWS] 踏み台サーバ不要!ローカルPCから、Private Subnet上のEC2インスタンスにssh/scpする

Last updated at Posted at 2020-09-09

はじめに

この記事では「[AWS] Private Subnet内のEC2インスタンスにローカルPCのターミナルからSession Managerでアクセスする」の内容を改善し、もっと簡単に環境を構築できる手順を解説します。

そして、何がセキュアかというと、Private Subnetに配置したEC2インスタンスに直接接続できる、という点に注目したいと思います。そう、踏み台サーバが不要なのです。

事前準備

まず、最初にクライアント環境に以下のツールが必要ですので、必要に応じてご用意ください。

また、AWSのクレデンシャル情報も事前に設定してくおいてください。
環境変数

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY

もし必要であれば、

AWS_DEFAULT_REGION

も設定してください。
もしくは、

$ aws configure

で設定しても、どちらでも構いません。

キーペアの作成

クライアントからssh/scpでアクセスするためのキーペアを作成します。
この手順は、既にキーペアが存在し、ローカル環境に.pemファイルをダウンロード済みの場合はスキップして構いません。

$ aws ec2 create-key-pair --key-name [キーペア名] --query 'KeyMaterial' --output text > [ローカル保存する.pemファイル名]

$ aws ec2 create-key-pair --key-name ssm-key --query 'KeyMaterial' --output text > ssm-key.pem

作成したファイルは、アクセス権を変更しておいてください。

$ chmode 400 ssm-key.pem

CloudFormationの実行

テンプレート

構成概説

以下テンプレートは、以下の環境を構築します。

  • VPC
  • Subnet(Private Subnetとして作成)
  • ルートテーブル(EndPointにのみルーティング)
  • セキュリティグループ(443のみ許可)
  • エンドポイント(ssm、ssmmessages、ec2messages、s3)
  • EC2インスタンス
  • EBS
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create Session Manager Environment

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - ProjectPrefix
      - Label:
          default: "Network Configuration"
        Parameters:
          - VPCCIDR
          - PrivateSubnetCIDR
      - Label:
          default: "EC2"
        Parameters:
          - AmiID
          - InstanceType
          - KeyPair

    ParameterLabels: 
      VPCCIDR:
        default: "VPC CIDR"
      PrivateSubnetCIDR: 
        default: "PrivateSubnet CIDR"

Parameters:
  ProjectPrefix:
    Type: String
    Default: "SSM-ACCESS-ENV"

  VPCCIDR:
    Type: String
    Default: "10.1.0.0/16"

  PrivateSubnetCIDR:
    Type: String
    Default: "10.1.100.0/24"

  AmiID:
    Type: String
    Default: "ami-0053d11f74e9e7f52"

  InstanceType:
    Type: String
    Default: "t3.xlarge"

  KeyPair:
    Type: String
    Default: "ssm-key"

Resources:
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub "${ProjectPrefix}-vpc"

  PrivateSubnet:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: !Sub "${AWS::Region}a"
      CidrBlock: !Ref PrivateSubnetCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${ProjectPrefix}-private-subnet"

  PrivateRouteTable:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${ProjectPrefix}-private-route"

  PrivateSubnetRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable

  PrivateSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription : "Private SecurityGroup"
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${ProjectPrefix}-security-group"
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: "443"
          ToPort: "443"
          CidrIp: !Ref VPCCIDR
      SecurityGroupEgress:
        - CidrIp: "127.0.0.1/32"
          IpProtocol: "-1"

  EndPointSSM:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref PrivateSecurityGroup
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssm"
      SubnetIds:
        - !Ref PrivateSubnet
      VpcEndpointType: "Interface"
      VpcId: !Ref VPC

  EndPointSSMMessages:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref PrivateSecurityGroup
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ssmmessages"
      SubnetIds:
        - !Ref PrivateSubnet
      VpcEndpointType: "Interface"
      VpcId: !Ref VPC

  EndPointEC2Messages:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref PrivateSecurityGroup
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ec2messages"
      SubnetIds:
        - !Ref PrivateSubnet
      VpcEndpointType: "Interface"
      VpcId: !Ref VPC

  EndPointS3:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      RouteTableIds:
        - !Ref PrivateRouteTable
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcEndpointType: "Gateway"
      VpcId: !Ref VPC

  EC2IAMRole:
    Type: "AWS::IAM::Role"
    Properties:
      RoleName: !Sub "${ProjectPrefix}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"

  EC2InstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Path: "/"
      Roles:
        - Ref: EC2IAMRole
      InstanceProfileName: !Sub "${ProjectPrefix}-PROFILE"

  EC2SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription : "EC2 SecurityGroup"
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${ProjectPrefix}-ec2-security-group"
      SecurityGroupIngress:
        - IpProtocol: "tcp"
          FromPort: "22"
          ToPort: "22"
          CidrIp: "0.0.0.0/0"

  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      SubnetId: !Ref PrivateSubnet
      ImageId: !Ref AmiID
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 100
            VolumeType: gp2
      EbsOptimized: true
      SourceDestCheck: true
      KeyName: !Ref KeyPair
      Tags:
        - Key: Name
          Value: !Sub "${ProjectPrefix}-ec2"

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${ProjectPrefix}-vpc"

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub "${ProjectPrefix}-vpc-cidr"

  PrivateSubnet:
    Value: !Ref PrivateSubnet
    Export:
      Name: !Sub "${ProjectPrefix}-private-subnet"

  PrivateRouteTable:
    Value: !Ref PrivateRouteTable
    Export:
      Name: !Sub "${ProjectPrefix}-private-route"

  PrivateSecurityGroup:
    Value: !Ref PrivateSecurityGroup
    Export:
      Name: !Sub "${ProjectPrefix}-private-securitygroup"

  EndPointSSM:
    Value: !Ref EndPointSSM
    Export:
      Name: !Sub "${ProjectPrefix}-endpoint-ssm"

  EndPointSSMMessages:
    Value: !Ref EndPointSSMMessages
    Export:
      Name: !Sub "${ProjectPrefix}-endpoint-ssmmessages"

  EndPointEC2Messages:
    Value: !Ref EndPointEC2Messages
    Export:
      Name: !Sub "${ProjectPrefix}-endpoint-ec2messages"

  EndPointS3:
    Value: !Ref EndPointS3
    Export:
      Name: !Sub "${ProjectPrefix}-endpoint-s3"

  EC2SecurityGroup:
    Value: !Ref EC2SecurityGroup
    Export:
      Name: !Sub "${ProjectPrefix}-ec2-security-group"

  EC2Instance:
    Value: !Ref EC2Instance
    Export:
      Name: !Sub "${ProjectPrefix}-ec2"

以下の内容をファイルに保存してください。以降、テンプレートファイル名を「ssmenv.yml」という前提で記述します。

スタックの作成

$ aws cloudformation create-stack --stack-name SSMEnvStack --template-body file://ssmenv.yml --capabilities CAPABILITY_NAMED_IAM
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:767054379442:stack/DPM-DATA-MIGRATION-ENV-Stack/0e0b65d0-f268-11ea-be88-06e06928d272"
}

もし、KeyPareを、上記で作成したものと違うものを指定する場合は、コマンドの最後に

--parameters ParameterKey=KeyPair,ParameterValue="キーペア名"

のようにしてデフォルトのパラメータ名を上書きしてください。
他に上書き可能なパラメータは、以下の通りです。

パラメータ名 設定値 デフォルト値
ProjectPrefix 各リソースに設定する名称の接頭子 SSM-ACCESS-ENV
VPCCIDR VPCのCIDR 10.1.0.0/16
PrivateSubnetCIDR SubnetのCIDR 10.1.100.0/24
AmiID EC2のAMI ID ami-0053d11f74e9e7f52
InstanceType EC2のインスタンスタイプ t3.xlarge
KeyPair EC2のキーペア名 ssm-key

なお、AMIは、Amazon Linux 2 64bitを前提にしていますが、リージョンによってはIDが異なります。上記は、ap-northeast-1が前提です。

また、上記以外のEBSのボリュームサイズなどは、適宜テンプレートを修正してください。

sshの準備

sshコマンドでSession Manager経由でEC2インスタンスに接続するためには
ホームディレクトリ/.ssh/config
に、以下の記述を行います。

config
host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"

ssh実行

sshでEC2インスタンスに接続するには、以下の構文で行います。

$ ssh -i [pemファイル] ec2-user@[EC2インスタンスのインスタンスID]

です。

$ ssh -i ssm-key.pem ec2-user@i-0af4afe4df0b3900b

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-10-1-100-148 ~]$

EC2インスタンスのインスタンスID

以下のいずれかの方法で取得できます。

  • マネジメントコンソールのEC2のインスタンスの一覧から取得する

  • AWS Cliでインスタンス一覧にフィルターをかけて取得する

    $ aws ec2 describe-instances --filters "Name=tag:Name,Values={ProjectPrefixの値}-ec2" --query "Reservations[].Instances[].InstanceId"
    

    $ aws ec2 describe-instances --filters "Name=tag:Name,Values=SSM-ACCESS-ENV-ec2" --query "Reservations[].Instances[].InstanceId"
    

[
"i-0af4afe4df0b3900b"
]
```

まとめ

CloudFormationで構成しているため、スタックを削除すると、全て綺麗に消えてくれます。
今回はVPCごと作成するようになっていますが、少しテンプレートをいじることで、既存のVPCに接続することももちろんできます。
その場合は、EndPointの接続先になるサブネットなどにご注意ください。

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?