Cloud9 IDE で AWS CLIを実行する際に注意したいネットワーク的制約

プライベートサブネット上では全て失敗します。 特定サービスのVPCエンドポイントを置くと、そのサービスの APIコールが失敗します。
2020.11.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Cloud9 IDE 検証中に置きた事象とその原因を共有します。 以下まとめです。

  • Cloud9 IDE(となるEC2インスタンス)にはグローバルIPが付与される
  • そのグローバルIP経由でしか AWS CLIは実行できない
  • そのためプライベートサブネット(NATGW経由)からの AWS CLIは失敗する
  • 同様に 特定サービスのVPCエンドポイントを置いていると、特定サービスの AWS CLI が失敗する

なにが起きたか

下図のようなシンプルなネットワーク構成で Cloud9 IDEを建てます。

  • Cloud9 at PublicSubnetdirect access で作成
  • Cloud9 at PrivateSubnetaccess via Systems Manager で作成

img

direct access は SSH経由で直接 EC2インスタンスへアクセスする構成、 access via Systems Manager は SSM セッションマネージャ経由でアクセスする構成です

img

この構成で Cloud9 IDEで AWS APIコールすると、以下のような結果になりました。

VPCエンドポイントが無い状態

パブリックサブネットの Cloud9 IDE は問題無く実行できるが、 プライベートサブネットの Cloud9 IDE で全ての AWS APIコールが失敗する。

なにかVPCエンドポイントを置いたとき

パブリック/プライベートサブネット関係なく、 置いた VPCエンドポイントのサービスの AWS APIコール が失敗する。

なぜ起きたか

Cloud9 IDEで AWS CLIを利用する際には AWS 側で一時クレデンシャル AWS managed temporary credentials(AMTC) が発行され、それを利用しています。

この一時クレデンシャルは利用者の保有する権限と基本的には同等ですが、いくつか制約があります。 その制約で APIコールが失敗していました。

具体的には AMTC ドキュメントの以下部分が原因です。

All supported AWS actions are restricted to the IP address of the environment . This is an AWS security best practice.

How AWS Cloud9 works with IAM | AWS Document

Cloud9 IDE(となるEC2インスタンス)にはアクセス方式に関わらず グローバルIPアドレスが付与されます。 引用の the IP address of the environment はこのグローバルIPアドレスのことです。

AWS APIコールの送信元IPアドレスが このグローバルIPアドレスに制限 されています。

なので、プライベートサブネット上の IDEから APIコールを行った際には、 送信元IPが NATゲートウェイのIP となるので失敗します。

img

同様に 特定サービスの VPCエンドポイントを置いた場合も、 それ経由の APIコールは 送信元IPが IDEのローカルIP となるので失敗します。

img

対処法

AMTCを使わない

ローカル端末で行うように、 ~/.aws/credentials や ~/.aws/config を修正して AWS CLIプロファイルを別のものに設定することでネットワーク的制約は無くなります。

以下記事で実際に別のログインプロファイルを設定しているので参照下さい。

別途IAMユーザーのアクセスキー情報を準備する必要があります。 また 利用者と同等のIAM権限でIDEを利用できる という Cloud9のメリットも当然失われます。

(推奨) Cloud9用の VPCを別途作成する

Cloud9 IDEは他システムが稼働しているVPCに置かない ようにする対処方法です。 Cloud9用のVPCとパブリックサブネットを作って、 そこに IDEを建てます。

▼ サクッと Cloud9用VPC/サブネットを作成する CFnテンプレート

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  VpcCidr:
    Type: String
    Default: 192.168.254.0/24
  SubnetACidr:
    Type: String
    Default: 192.168.254.0/26
  SubnetCCidr:
    Type: String
    Default: 192.168.254.64/26
  SubnetDCidr:
    Type: String
    Default: 192.168.254.128/26
Resources:
  # VPC
  Vpc:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: !Ref VpcCidr
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags: 
        - Key: Name
          Value: cloud9-vpc
  # IGW
  Igw:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: cloud9-igw
  IgwAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties: 
      InternetGatewayId: !Ref Igw
      VpcId: !Ref Vpc
  # Subnets
  SubnetA:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref SubnetACidr
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: cloud9-subnet-a
  SubnetC:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref SubnetCCidr
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: cloud9-subnet-c
  SubnetD:
    Type: AWS::EC2::Subnet
    Properties: 
      VpcId: !Ref Vpc
      AvailabilityZone: ap-northeast-1d
      CidrBlock: !Ref SubnetDCidr
      MapPublicIpOnLaunch: false
      Tags: 
        - Key: Name
          Value: cloud9-subnet-d
  # Routing
  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties: 
      VpcId: !Ref Vpc
      Tags: 
        - Key: Name
          Value: cloud9-public-rtb
  RouteTableAssocA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetA
  RouteTableAssocC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetC
  RouteTableAssocD:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties: 
      RouteTableId: !Ref RouteTable
      SubnetId: !Ref SubnetD
  Route:
    Type: AWS::EC2::Route
    Properties: 
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref Igw

おわりに

以上、Cloud9 IDEで AWS CLIなど実行する際のネットワーク的制約でした。

Cloud9 IDE の接続方法として SSM Session Manager がサポートされていますが、 この意図としては プライベートサブネットでも使えるよ! ではなくて インバウンドを閉じた状態でも使えるよ!(ただしパブリックサブネットに限る) みたいです。

少しでもどなたかのお役に立てば幸いです。

参考