AWS Site-to-Site VPNで使用しているカスタマーゲートウェイをAWS CLIで切り替えてみた

2023.02.17

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

AWS Site-to-Site VPNで使用しているカスタマーゲートウェイを切り替える機会があったのでブログに残します。

どんな時にカスタマーゲートウェイを切り替えるのか

以下のドキュメントの通りカスタマーゲートウェイで使用しているパブリックIPアドレスが変更された場合などに行う作業になります。
AWS VPN 接続のカスタマーゲートウェイのパブリック IP アドレスを変更するにはどうすればよいですか?

検証で作成する環境

今回検証で作成する環境は以下の構成図の通りになります。

自宅にVPNルーターを準備する余裕が無かったので東京リージョンにVPCを2つ作成して、VyOSのAMIでEC2を起動後にVPN接続の設定を行います。
構成図の切り替え元VyOSとVPN接続ができたらカスタマーゲートウェイを切り替えます。
VyOSでのVPN設定は以下のブログを参考にしました。

VPN設定

まずは構成図に記載したAWSリソースを作成していきます。
作成にはCloudFormationを利用しました。
以下のCloudFormationテンプレートで構成図左側にあるVGWが紐づくVPCを作成を作成します。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: EC2 Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for VPC
        Parameters:
          - VPCCIDR
      - Label: 
          default: Parameters for Subnet
        Parameters:
          - PublicSubnet01CIDR
      - Label: 
          default: Parameters for EC2
        Parameters:
          - EC2VolumeSize
          - EC2VolumeIOPS
          - EC2AMI
          - EC2InstanceType

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  VPCCIDR:
    Default: 172.16.0.0/16
    Type: String

  PublicSubnet01CIDR:
    Default: 172.16.0.0/24
    Type: String

  EC2VolumeSize:
    Default: 30
    Type: Number

  EC2VolumeIOPS:
    Default: 3000
    Type: Number

  EC2AMI:
    Default: ami-06ee4e2261a4dc5c3
    Type: AWS::EC2::Image::Id

  EC2InstanceType:
    Default: t3.micro
    Type: String

Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: vpn-test-vpc

# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------# 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: vpn-test-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet01CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: vpn-test-public-subnet-01
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: vpn-test-public-rtb

  PublicRouteTableRoute1:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicRouteTable

  PublicRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01

# ------------------------------------------------------------#
# IAM
# ------------------------------------------------------------# 
  EC2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - Effect: Allow
            Principal: 
              Service: 
                - ec2.amazonaws.com
            Action: 
              - 'sts:AssumeRole'
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      RoleName: iam-role-ec2
      Tags:
        - Key: Name
          Value: iam-role-ec2

  EC2IAMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: iam-instanceprofile-ec2
      Roles: 
        - !Ref EC2IAMRole

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ec2
      GroupName: vpn-test-sg-ec2-ping
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress: 
        - CidrIp: 192.168.0.0/16
          FromPort: -1
          IpProtocol: icmp
          ToPort: -1
      Tags: 
        - Key: Name
          Value: vpn-test-sg-ec2-ping
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# KeyPair
# ------------------------------------------------------------# 
  KeyPair:
    Type: AWS::EC2::KeyPair
    Properties: 
      KeyName: vpn-test-ec2-key
      KeyType: rsa
      Tags: 
        - Key: Name
          Value: vpn-test-ec2-key

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------# 
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: !Ref EC2VolumeIOPS
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp3
      DisableApiTermination: false
      EbsOptimized: true
      IamInstanceProfile: !Ref EC2IAMInstanceProfile
      ImageId: !Ref EC2AMI
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref KeyPair
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref EC2SG
          SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: vpn-test-ec2-ping

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  VPCID:
    Value: !Ref VPC
    Export: 
      Name: VPCID

  PublicRouteTableID:
    Value: !Ref PublicRouteTable
    Export:
      Name: PublicRouteTableID

  EC2IAMInstanceProfileName:
    Value: !Ref EC2IAMInstanceProfile
    Export: 
      Name: iam-instanceprofile-ec2

こちらのCloudFormationテンプレートではVPCと疎通確認用のEC2が作成されます。

以下のコマンドでデプロイします。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM

スタックが作成されたらVyOS側を作成します。
以下のCloudFormationテンプレートで作成します。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: VyOS Stack

Metadata:
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------# 
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label: 
          default: Parameters for VPC
        Parameters:
          - VPCCIDR
      - Label: 
          default: Parameters for Subnet
        Parameters:
          - PublicSubnet01CIDR
      - Label: 
          default: Parameters for EC2
        Parameters:
          - MyIP
          - EC2VolumeSize
          - EC2VolumeIOPS
          - EC2AMI
          - VyOSAMI
          - EC2InstanceType

Parameters:
# ------------------------------------------------------------#
# Parameters
# ------------------------------------------------------------# 
  VPCCIDR:
    Default: 192.168.0.0/16
    Type: String

  PublicSubnet01CIDR:
    Default: 192.168.0.0/24
    Type: String

  MyIP:
    Type: String

  EC2VolumeSize:
    Default: 30
    Type: Number

  EC2VolumeIOPS:
    Default: 3000
    Type: Number

  EC2AMI:
    Default: ami-06ee4e2261a4dc5c3
    Type: AWS::EC2::Image::Id

  VyOSAMI:
    Default: ami-0965b2be3f08e4cc4
    Type: AWS::EC2::Image::Id

  EC2InstanceType:
    Default: t3.micro
    Type: String

Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------# 
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags: 
        - Key: Name
          Value: vyos-test-vpc

# ------------------------------------------------------------#
# InternetGateway
# ------------------------------------------------------------# 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: vyos-test-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------# 
  PublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PublicSubnet01CIDR
      MapPublicIpOnLaunch: true
      Tags: 
        - Key: Name
          Value: vyos-test-public-subnet-01
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------# 
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: vyos-test-public-rtb

  PublicRouteTableRoute1:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicRouteTable

  PublicRtAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet01

# ------------------------------------------------------------#
# Security Group
# ------------------------------------------------------------# 
  EC2SG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for ec2
      GroupName: vyos-test-sg-ec2-ping
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress: 
        - CidrIp: 172.16.0.0/16
          FromPort: -1
          IpProtocol: icmp
          ToPort: -1
      Tags: 
        - Key: Name
          Value: vyos-test-sg-ec2-ping
      VpcId: !Ref VPC

  VyOSSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for VyOS
      GroupName: vyos-test-sg-ec2-vpn
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          FromPort: -1
          IpProtocol: -1
          ToPort: -1
      SecurityGroupIngress: 
        - CidrIp: !Ref MyIP
          FromPort: 22
          IpProtocol: tcp
          ToPort: 22
        - CidrIp: 192.168.0.0/16
          FromPort: -1
          IpProtocol: icmp
          ToPort: -1
      Tags: 
        - Key: Name
          Value: vyos-test-sg-ec2-vpn
      VpcId: !Ref VPC

# ------------------------------------------------------------#
# KeyPair
# ------------------------------------------------------------# 
  KeyPair:
    Type: AWS::EC2::KeyPair
    Properties: 
      KeyName: vyos-test-ec2-key
      KeyType: rsa
      Tags: 
        - Key: Name
          Value: vyos-test-ec2-key

# ------------------------------------------------------------#
# EC2
# ------------------------------------------------------------# 
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            Iops: !Ref EC2VolumeIOPS
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp3
      DisableApiTermination: false
      EbsOptimized: true
      IamInstanceProfile: !ImportValue iam-instanceprofile-ec2
      ImageId: !Ref EC2AMI
      InstanceType: !Ref EC2InstanceType
      KeyName: !Ref KeyPair
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref EC2SG
          SubnetId: !Ref PublicSubnet01
      Tags:
        - Key: Name
          Value: vyos-test-ec2-ping

  VyOS1EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp2
      DisableApiTermination: false
      EbsOptimized: true
      ImageId: !Ref VyOSAMI
      InstanceType: t3.small
      KeyName: !Ref KeyPair
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref VyOSSG
          SubnetId: !Ref PublicSubnet01
      SourceDestCheck: false
      Tags:
        - Key: Name
          Value: vyos-test-ec2-vpn1

  VyOS2EC2:
    Type: AWS::EC2::Instance
    Properties:
      BlockDeviceMappings: 
        - DeviceName: /dev/xvda
          Ebs:
            DeleteOnTermination: true
            Encrypted: true
            VolumeSize: !Ref EC2VolumeSize
            VolumeType: gp2
      DisableApiTermination: false
      EbsOptimized: true
      ImageId: !Ref VyOSAMI
      InstanceType: t3.small
      KeyName: !Ref KeyPair
      NetworkInterfaces: 
        - AssociatePublicIpAddress: true
          DeleteOnTermination: true
          DeviceIndex: 0
          GroupSet: 
            - !Ref VyOSSG
          SubnetId: !Ref PublicSubnet01
      SourceDestCheck: false
      Tags:
        - Key: Name
          Value: vyos-test-ec2-vpn2

Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------# 
  VyOS1EC2PublicIP:
    Value: !GetAtt VyOS1EC2.PublicIp
    Export:
      Name: VyOS1EC2PublicIP

  VyOS2EC2PublicIP:
    Value: !GetAtt VyOS2EC2.PublicIp
    Export: 
      Name: VyOS2EC2PublicIP

こちらのCloudFormationテンプレートではVyOSのAMIを使用したEC2と疎通確認用のEC2が起動します。

以下のコマンドでデプロイします。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM --parameters ParameterKey=MyIP,ParameterValue=VyOSへSSHする接続元のIPアドレス

VyOSへSSHする接続元のIPアドレスは「/32」でサブネットマスクを設定します。
スタックが作成されたらカスタマーゲートウェイとVGW周りを作成していきます。
以下のCloudFormationテンプレートで作成します。

CloudFormationテンプレート (ここをクリックしてください)
AWSTemplateFormatVersion: "2010-09-09"

Description: VPN Stack

Resources:
# ------------------------------------------------------------#
# CustomerGateway
# ------------------------------------------------------------# 
  CustomerGateway1:
    Type: AWS::EC2::CustomerGateway
    Properties: 
      BgpAsn: 65000
      DeviceName: vyos-test-ec2-vpn1
      IpAddress: !ImportValue VyOS1EC2PublicIP
      Tags: 
        - Key: Name
          Value: vyos-test-ec2-vpn1
      Type: ipsec.1

  CustomerGateway2:
    Type: AWS::EC2::CustomerGateway
    Properties: 
      BgpAsn: 65000
      DeviceName: vyos-test-ec2-vpn2
      IpAddress: !ImportValue VyOS2EC2PublicIP
      Tags: 
        - Key: Name
          Value: vyos-test-ec2-vpn2
      Type: ipsec.1

# ------------------------------------------------------------#
# VPNGateway
# ------------------------------------------------------------# 
  VPNGateway:
    Type: AWS::EC2::VPNGateway
    Properties: 
      Tags: 
        - Key: Name
          Value: test-vgw
      Type: ipsec.1

  VPNGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !ImportValue VPCID
      VpnGatewayId: !Ref VPNGateway

# ------------------------------------------------------------#
# VPNConnection
# ------------------------------------------------------------# 
  VPNConnection:
    Type: AWS::EC2::VPNConnection
    Properties: 
      CustomerGatewayId: !Ref CustomerGateway1
      StaticRoutesOnly: false
      Tags:
        - Key: Name
          Value: test-vpn-connection
      Type: ipsec.1
      VpnGatewayId: !Ref VPNGateway

  VPNGatewayRoutePropagation:
    Type: AWS::EC2::VPNGatewayRoutePropagation
    DependsOn: VPNGatewayAttachment
    Properties: 
      RouteTableIds:
        - !ImportValue PublicRouteTableID
      VpnGatewayId: !Ref VPNGateway

こちらのCloudFormationテンプレートではカスタマーゲートウェイ、VGW、VPN接続が作成されます。
また、62行目~68行目でルートのルート伝播を有効にしています。
ルート伝播を有効にするとカスタマーゲートウェイ側(VyOS側)から広告されてきたルーティング情報をルートテーブルに自動で登録してくれます。

以下のコマンドでデプロイします。

aws cloudformation create-stack --stack-name CloudFormationスタック名 --template-body file://CloudFormationテンプレートファイル名 --capabilities CAPABILITY_NAMED_IAM

スタックが作成されたらVyOSにVPN設定を入れていきます。
以下の手順でマネジメントコンソールからVyOSのサンプルコンフィグをダウンロードしてきます。
マネジメントコンソールからVPCダッシュボードへ移動します。
移動したら画面左の項目から「Site-to-Site VPN 接続」をクリックします。

画面が遷移したら「Name」列が「test-vpn-connection」のものを選択して右上の「設定をダウンロードする」をクリックします。

クリックしたらベンダーなどの情報を以下の画像の通り選択して「ダウンロード」をクリックします。

ダウンロードしたサンプルコンフィグはそのままでは使用できないので少し修正します。

set vpn ipsec site-to-site peer ピアIPアドレス local-address 'VyOS EC2のグローバルIPアドレス'
set protocols bgp 65000 neighbor ネイバーIPアドレス soft-reconfiguration 'inbound'
set protocols bgp 65000 network 0.0.0.0/0

上記の部分を以下のように変更します。

set vpn ipsec site-to-site peer ピアIPアドレス local-address 'VyOS EC2のローカルIPアドレス'
set protocols bgp 65000 neighbor ネイバーIPアドレス address-family ipv4-unicast soft-reconfiguration 'inbound'
set protocols bgp 65000 address-family ipv4-unicast network 192.168.0.0/16

ローカルIPに修正しているのはEC2が直接グローバルIPを持っているわけではなくNATしてプライベートIPに紐づけているためです。
以下のドキュメントに記載されています。
パブリック IPv4 アドレス

サブネットで作成されたネットワークインターフェイスがパブリック IPv4 アドレス (このトピックではパブリック IP アドレスと呼ばれる) を自動的に受信するかどうかを判断する属性が、すべてのサブネットにあります。したがって、この属性が有効になっているサブネットに対してインスタンスを起動すると、パブリック IP アドレスがそのインスタンス用に作成されたプライマリネットワークインターフェイス (eth0) に割り当てられます。パブリック IP アドレスは、ネットワークアドレス変換 (NAT) によって、プライマリプライベート IP アドレスにマッピングされます。

修正したらVyOSのEC2へSSHします。
SSHに使用する鍵はSystems Managerのパラメータストアに保存されています。
SSHは以下のコマンドで行いました。

ssh -i 秘密鍵ファイル名.pem vyos@VyOS EC2のグローバルIPアドレス

設定をするには以下のコマンドでConfiguration Modeに移動します。

configure

Configuration Modeに移動したら修正したサンプルコンフィグの設定を入れていきます。
設定を入れたら以下のコマンドで保存します。

commit
save

設定が上手くできているとVPNトンネルが2つともUPになります。

UPになったことが確認できたらもう一台のVyOSにも設定を入れていきます。(IPアドレスは適所変更してください。)

ここまで完了したらVyOSが起動しているサブネットのルートテーブルを修正します。
マネジメントコンソールからVPCダッシュボードへ移動します。
移動したら画面左の項目から「ルートテーブル」をクリックします。

画面が遷移したら「Name」列が「vyos-test-public-rtb」のものを選択して右上の「アクション」から「ルートを編集」をクリックします。

クリックしたら「送信先」を「172.16.0.0/16」にして「ターゲット」をVPNを張っているVyOS EC2のENIのIDにしたルートを追加します。
これを入れることで疎通確認用のEC2から通信できるようになります。

疎通確認は以下のドキュメントの手順でNameタグが「vyos-test-ec2-ping」のEC2に接続して「vpn-test-ec2-ping」のプライベートIPアドレス宛にpingを実行します。
セッションを開始する (Amazon EC2 コンソール)
ここまででVPNの設定は完了です。

カスタマーゲートウェイの切り替え

ここから本題のカスタマーゲートウェイの切り替えを行います。
マネジメントコンソールから行う手順としては以下のドキュメントの通りです。
Site-to-Site VPN 接続のカスタマーゲートウェイの変更
今回はAWS CLIで切り替えてみます。
以下のコマンドで切り替えます。

aws ec2 modify-vpn-connection --vpn-connection-id VPN ID --customer-gateway-id 切り替え先のカスタマーゲートウェイID

実行するとVPN接続の「状態」列が「Modifying」になります。

少し待つと「状態」列が「Available」になります。
トンネルの「ステータス」列も少し待つとUPになります。
UPになったらVyOSが起動しているサブネットのルートテーブルを修正します。
今度は切り替え先のVyOS EC2のENI IDをターゲットにします。
ルート変更後、以下のドキュメントの手順でNameタグが「vyos-test-ec2-ping」のEC2に接続して「vpn-test-ec2-ping」のプライベートIPアドレス宛にpingを実行します。
セッションを開始する (Amazon EC2 コンソール)
疎通確認が成功すれば切り替え完了です。

さいごに

あまり発生しない作業かと思いますがネットワークに関わる作業なのでサクッと切り替えられるように手順は頭の片隅に入れておいてもよいと思いました。