AWS Client VPNを使用したパブリックEC2脱却作戦

## はじめに
2024年2月より、AWSのパブリックIPアドレスが有料化(課金対象)になるとの情報が下記のブログにてAWS社より発表されています。

https://aws.amazon.com/jp/blogs/news/new-aws-public-ipv4-address-charge-public-ip-insights/

これまで筆者の個人環境では、パブリック・サブネット上にEC2インスタンスをプロビジョニングして、外部からSSH接続してEC2インスタンスにアクセスしていましたが、今回の発表に伴いEC2インスタンスへのアクセス手段を見直すことにしました。現在は、EC2 Instance ConnectがプライベートIPアドレスを使用した接続ができるようにアップデートされているようですが、PC側のターミナルからプライベートIPアドレスでインスタンスに接続できればもっと便利かなと思い、色々模索していたところ、下記の<font color="Blue">AWS Client VPN</font>を使用すればそれが可能になるのではと考え、実際にClient VPN環境を構築してみました。

https://aws.amazon.com/jp/vpn/client-vpn/

本記事では、その構築例をご紹介します。

## AWS Client VPN環境構築
### 今回作成する環境
先日投稿した下記のQiita記事の中で、一度環境構築して稼働確認自体はできたのですが、今回は以下の点を考慮して構成の見直しを行いました。

https://qiita.com/duelist2020jp/items/090a98e5fa17fa96e319

* パッチ適用やツール等の稼働検証ができるように、プライベート・インスタンスのOutbound通信ができるようにする。

見直し後の構成図は下記になります。
![TestEnv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/5fc0d790-f6e7-f8de-9f9c-cf517aa20e2a.png)
* パブリック・サブネットを用意し、その中にNAT Gatewayを追加。
* Internet Gatewayを追加し、VPCにアタッチ。

なお、Client VPN接続した状態のプライベート・インスタンスにおいて、他のAWSサービスも使用できるか否かを確認するため、今回は1例として久々にEFSを使ってみることにしました。(re:Invent2023において、EFSにも新機能やアップデートがあり、使ってみたくなりました。)

また、接続元のClient PCには、Redhat Enterprise Linux V8.8を使用しています。

### 認証方式の選定
AWS Client VPNを使用する場合、認証方法にはActive Directory認証、証明書ベースの相互認証、SSO(SAMLベースのフェデレーション認証)の3種類があります。筆者は個人ユーザーですので、証明書ベースの相互認証方式を使用することにしました。

### 証明書作成
接続元のPCでクライアント証明書とサーバー証明書を作成して、AWS Certification Managerにアップロードする必要があります。証明書の作成方法は、下記AWS Client VPNのマニュアル記載を参考に実施しました。特に躓くことなく作成及びアップロードはできました。

https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/mutual.html

* OpenVPN easy-rsaをダウンロード
* PKI(公開鍵暗号基盤)の作成 
```
$ ./easyrsa init-pki

Notice
------
'init-pki' complete; you may now create a CA or requests.

Your newly created PKI dir is:
* /home/testuser/data/easy-rsa/easyrsa3/pki

* IMPORTANT: Easy-RSA 'vars' file has now been moved to your PKI above.
```
* CA(認証局)の作成
```
$ ./easyrsa build-ca nopass 
* Using Easy-RSA configuration from: /home/kiyomasa/data/easy-rsa/easyrsa3/pki/vars
* Using SSL: openssl OpenSSL 1.1.1p  21 Jun 2022

EasyRSA DN 'commonName-Only' mode (cn_only)

* Current CA Distinguished Name fields:

  commonName                = Easy-RSA CA

Type the word 'yes' to continue, or any other input to abort.

  Create CA certificate with these DN settings ? yes

...........................................+++++
.....+++++

Notice
------
CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/home/testuser/data/easy-rsa/easyrsa3/pki/ca.crt
```
* サーバー証明書の作成
```
$ ./easyrsa build-server-full server nopass
* Using Easy-RSA configuration from: /home/kiyomasa/data/easy-rsa/easyrsa3/pki/vars
* Using SSL: openssl OpenSSL 1.1.1p  21 Jun 2022
Generating a RSA private key
............................+++++
........................+++++
writing new private key to '/home/testuser/data/easy-rsa/easyrsa3/pki/64e332b6/temp.0e912600'
-----

Notice
------
Keypair and certificate request completed. Your files are:
req: /home/testuser/data/easy-rsa/easyrsa3/pki/reqs/server.req
key: /home/testuser/data/easy-rsa/easyrsa3/pki/private/server.key

Using configuration from /home/testuser/data/easy-rsa/easyrsa3/pki/safessl-easyrsa.cnf.init-tmp
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'server'
Certificate is to be certified until Mar 21 07:46:20 2026 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

Notice
------
Certificate created at: /home/testuser/data/easy-rsa/easyrsa3/pki/issued/server.crt
```
* クライアント証明書の作成
```
$ ./easyrsa build-client-full testuser.RHEL.tld nopass
* Using Easy-RSA configuration from: /home/testuser/data/easy-rsa/easyrsa3/pki/vars
* Using SSL: openssl OpenSSL 1.1.1p  21 Jun 2022
Generating a RSA private key
.................+++++
...............................................................................................................................................+++++
writing new private key to '/home/testuser/data/easy-rsa/easyrsa3/pki/a7087a3c/temp.746a7203'
-----

Notice
------
Keypair and certificate request completed. Your files are:
req: /home/testuser/data/easy-rsa/easyrsa3/pki/reqs/testuser.RHEL.tld.req
key: /home/testuser/data/easy-rsa/easyrsa3/pki/private/testuser.RHEL.tld.key

Using configuration from /home/testuser/data/easy-rsa/easyrsa3/pki/safessl-easyrsa.cnf.init-tmp
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'testuser.RHEL.tld'
Certificate is to be certified until Mar 21 07:49:58 2026 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

Notice
------
Certificate created at: /home/testuser/data/easy-rsa/easyrsa3/pki/issued/testuser.RHEL.tld.crt
```
* 作成した証明書類を、/home/testuser/custom_crtkeyにコピー
* AWS Certification Managerへのサーバー証明書アップロード
```
$ aws acm import-certificate --certificate fileb://server.crt --private-key fileb://server.key --certificate-chain fileb://ca.crt
{
    "CertificateArn": "arn:aws:acm:ap-northeast-1:xxxxxxxxxxx:certificate/9ef809ca-5db0-45aa-b600-20f88a9a8f82"
}
```
* AWS Certification Managerへのクライアント証明書アップロード
```
$ aws acm import-certificate --certificate fileb://testuser.RHEL.tld.
crt --private-key fileb://testuser.RHEL.tld.key --certificate-chain fileb://ca.crt
{
    "CertificateArn": "arn:aws:acm:ap-northeast-1:xxxxxxxxxxxx:certificate/983954a7-52d2-4ef9-a342-5b7e8c8ef951"
}
```
アップロードされた証明書が、AWS Certification Managerの証明書一覧に表示されていることが確認できます。
![p45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/808803ed-1736-4d4a-e8b9-415bfa6d00e0.jpeg)

### CloudFormationサンプルコード
今回見直し後のAWS Client VPN環境をプロビジョニングするためのCloudFormationのサンプルコードは下記のcreate_ClientVPN.yamlになります。
<details><summary>create_ClientVPN.yaml</summary>

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample Script for Creating Client-VPN Environment
    
Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: 'true'
      EnableDnsSupport: 'true'
      Tags: 
        - Key: Name
          Value: My-VPC
  MyPublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: My-Public-Subnet
    DependsOn: MyVPC         
  MyPrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: ap-northeast-1a
      Tags:
        - Key: Name
          Value: My-Private-Subnet
    DependsOn: MyVPC 
  MyIGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: My-Internet-Gateway
    DependsOn: MyVPC
  MyIGWAttachToVPC:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref MyIGW 
  MyEIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
    DependsOn: MyIGWAttachToVPC
  MyNGW:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt MyEIP.AllocationId
      ConnectivityType: public
      SubnetId: !Ref MyPublicSubnet
      Tags:
        - Key: Name
          Value: My-NAT-Gateway
    DependsOn: MyIGWAttachToVPC
  MyPublicRtbl:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-Public-Rtbl
    DependsOn: MyIGW
  MyPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: MyPublicRtbl
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyIGW
  MyPublicSubnetRouteTblAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRtbl
      SubnetId: !Ref MyPublicSubnet
  MyPrivateRtbl:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-Private-Rtbl
    DependsOn: MyVPC
  MyPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: MyPrivateRtbl
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref MyNGW
    DependsOn: MyNGW 
    DependsOn: MyIGWAttachToVPC
  MyPrivateSubnetRouteTblAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRtbl
      SubnetId: !Ref MyPrivateSubnet
  MyClientVPNSecGrp:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group for Client VPN Endpoint
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-ClientVPN-Security-Group
  MyPrivateSecGrp:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security Group for VPC Private Subnet
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref MyClientVPNSecGrp
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
        - SourceSecurityGroupId: !Ref MyClientVPNSecGrp
          IpProtocol: icmp
          FromPort: 8
          ToPort: -1     
      Tags:
        - Key: Name
          Value: My-PrivateSubnet-Security-Group
  MyKey:
    Type: AWS::EC2::KeyPair
    Properties:
      KeyName: MyEC2Key
      PublicKeyMaterial: '{{resolve:ssm:publickeymaterial}}'
      Tags:
        - Key: Name
          Value: My-Key
  MyPrivateInstance:
    Type: AWS::EC2::Instance
    Properties:
      CreditSpecification: 
        CPUCredits: standard
      InstanceType: t2.micro
      KeyName: !Ref MyKey
      ImageId: ami-078296f82eb463377
      NetworkInterfaces:
        - AssociatePublicIpAddress: "false"
          DeviceIndex: "0"
          SubnetId: !Ref MyPrivateSubnet
          GroupSet:
            - !Ref MyPrivateSecGrp
      Tags:
        - Key: Name
          Value: My-Private-Instance

  MyClientVPNEndpoint:
    Type: AWS::EC2::ClientVpnEndpoint
    Properties: 
      AuthenticationOptions: 
        - Type: certificate-authentication
          MutualAuthentication: 
            ClientRootCertificateChainArn: '{{resolve:ssm:RootCertARN}}'
      ClientCidrBlock: 192.168.0.0/22
      ConnectionLogOptions: 
        Enabled: false
      ServerCertificateArn: '{{resolve:ssm:ServerCertARN}}'
      VpcId: !Ref MyVPC
      VpnPort: 443
      SecurityGroupIds:
        - !Ref MyClientVPNSecGrp
      TransportProtocol: udp
      TagSpecifications:
        - ResourceType: client-vpn-endpoint
          Tags:
            - Key: Name
              Value: My-Client-VPN-Endpoint

  MyClientVPNTargetNetworkAssociation:
    Type: AWS::EC2::ClientVpnTargetNetworkAssociation
    Properties:
      ClientVpnEndpointId: !Ref MyClientVPNEndpoint
      SubnetId: !Ref MyPrivateSubnet

  MyClientVPNAuthorizationRule:
    Type: AWS::EC2::ClientVpnAuthorizationRule
    Properties:
      AuthorizeAllGroups: true
      ClientVpnEndpointId: !Ref MyClientVPNEndpoint
      TargetNetworkCidr: 10.0.2.0/24

Outputs:
  MyPrivateInstanceIPaddr:
    Description: My Private Instance's Private IP address 
    Value: !GetAtt MyPrivateInstance.PrivateIp
  MyPrivateSubnetSecurityGroupID:
    Description: My Private Subnet Security Group's ID 
    Value: !Ref MyPrivateSecGrp
    Export:
      Name: !Sub "${AWS::StackName}--PrivateSubnetSecGrpId"
  MyPrivateSubnetID:
    Description: My Private Subnet's ID 
    Value: !Ref MyPrivateSubnet
    Export:
      Name: !Sub "${AWS::StackName}--PrivateSubnetId"
  MyVPCID:
    Description: My VPC's ID
    Value: !Ref MyVPC
    Export:
      Name: !Sub "${AWS::StackName}--VPCId"
```

</details>

改良コードのポイントとしては下記のとおりです。
* パブリック・サブネットを追加し、そこにNAT Gatewayを配置。
* NAT Gatewayを使用するため、EIPを用意。
* Internet Gatewayを作成し、VPCにアタッチ。
* 後続で紹介するEFS作成用のCloudFormationコードで、VPC、プライベート・サブネット、プライベート・サブネットのセキュリティーグループのIDが必要になるため、それらの値をエクスポート

Client VPN固有の設定ポイントとしては下記のとおりです。

* <font color="Blue">MyClientVPNSecGrp</font>
  * Client VPN Endpointに紐づける専用のセキュリティーグループを用意する。
  * インバウンド・アクセスのルールは不要です。

* <font color="Blue">MyPrivateSecGrp</font>
  * プライベート・サブネットに紐づくセキュリティーグループのインバウンド・アクセスのルールに下記の指定を行います。
    * Client VPN EndpointのセキュリティーグループからのSSHアクセス(Port=22)を許可する。
    * Client VPN Endpointのセキュリティーグループからのping発行を許可する。 

* <font color="Blue">MyClientVPNEndpoint</font>
  * Client VPN Endpoint作成時に、既にCertification Managerにアップロード済のクライアント証明書とサーバー証明書各々のARNを指定する。
    * 直接指定するのは煩雑ですので、Systems Manager Parameter Storeに各々のARNを保管する専用のパラメータを作成し、コードの実行時に呼び出すようにしています。 
  * Client VPNの送信元のIPアドレス範囲(自身のPCのIPアドレス範囲)を指定する。
  
* <font color="Blue">MyClientVPNTargetAssociation</font>
  * Client VPNの宛先となるサブネットを指定します。

* <font color="Blue">MyClientVPNAuthorizationRule</font>
  * Client VPNの宛先となるサブネットのIPアドレスのCIDR範囲を指定します。



### 構築後のAWS Client VPN画面イメージ
* VPC → 仮想プライベートネットワーク(VPN)の下に「クライアントVPNエンドポイント」というものがありますので、そこから確認することができます。
![p23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/0292a0d7-48dd-a993-4f91-a049e69c55bd.jpeg)
「クライアントCIDR」列のところに、接続元のPCのIPアドレス範囲が表示されています。

* 「クライアントVPNエンドポイントID」直下の青字(ID部分)をクリックすると詳細が表示されます。
![p24.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/a8dc47d3-2fb1-5ddc-4383-612e354a1c19.jpeg)
当画面の赤枠で囲んでいる「クライアント設定をダウンロード」ボタンをクリックして、VPN接続に必要な設定ファイルをPCにダウンロードします。ファイル名は「downloaded-client-config.ovpn」という名前でダウンロードされますが、ダウンロード後はファイル名をリネームして問題ありません。ただし、OpenVPN準拠の設定ファイルですので、拡張子のovpnは変更しないでください。

### AWS Client VPN設定ファイル
ダウンロードした中身は下記のようになっています。

```ovpn:config.ovpn
client
dev tun
proto udp
remote cvpn-endpoint-0cad7eb018ce257d1.prod.clientvpn.ap-northeast-1.amazonaws.com 443
remote-random-hostname
resolv-retry infinite
nobind
remote-cert-tls server
cipher AES-256-GCM
verb 3
<ca>
-----BEGIN CERTIFICATE-----
～
～
-----END CERTIFICATE-----
</ca>

reneg-sec 0

verify-x509-name server name
```
下記マニュアル記述に従い、PC側のクライアント証明書とクライアント秘密鍵を絶対パス形式で、上記ファイルの「reneg-sec 0」の真上に記載します。

https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/cvpn-working-endpoint-export.html

追記するのは以下の2行です。
```
cert /home/testuser/custom_crtkey/testuser.RHEL.tld.crt
key /home/testuser/custom_crtkey/testuser.RHEL.tld.key
```
## AWS Client VPN接続確認
### 接続開始
接続元のクライアントPCのターミナルから下記のコマンドを実行します。
```
sudo openvpn --config client_config.ovpn
```
コマンド実行後、VPN接続が開始されます。下記は出力例です。
```
Tue Dec 19 16:50:03 2023 OpenVPN 2.4.12 x86_64-redhat-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Nov 10 2023
Tue Dec 19 16:50:03 2023 library versions: OpenSSL 1.1.1k  FIPS 25 Mar 2021, LZO 2.08
Tue Dec 19 16:50:03 2023 TCP/UDP: Preserving recently used remote address: [AF_INET]52.193.69.78:443
Tue Dec 19 16:50:03 2023 Socket Buffers: R=[212992->212992] S=[212992->212992]
Tue Dec 19 16:50:03 2023 UDP link local: (not bound)
Tue Dec 19 16:50:03 2023 UDP link remote: [AF_INET]52.193.69.78:443
Tue Dec 19 16:50:03 2023 TLS: Initial packet from [AF_INET]52.193.69.78:443, sid=903f2c53 ebafd4b6
Tue Dec 19 16:50:03 2023 VERIFY OK: depth=1, CN=Easy-RSA CA
Tue Dec 19 16:50:03 2023 VERIFY KU OK
Tue Dec 19 16:50:03 2023 Validating certificate extended key usage
Tue Dec 19 16:50:03 2023 ++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication
Tue Dec 19 16:50:03 2023 VERIFY EKU OK
Tue Dec 19 16:50:03 2023 VERIFY X509NAME OK: CN=server
Tue Dec 19 16:50:03 2023 VERIFY OK: depth=0, CN=server
Tue Dec 19 16:50:03 2023 Control Channel: TLSv1.2, cipher TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384, 2048 bit RSA
Tue Dec 19 16:50:03 2023 [server] Peer Connection Initiated with [AF_INET]52.193.69.78:443
Tue Dec 19 16:50:05 2023 SENT CONTROL [server]: 'PUSH_REQUEST' (status=1)
Tue Dec 19 16:50:05 2023 PUSH: Received control message: 'PUSH_REPLY,redirect-gateway def1 bypass-dhcp,block-outside-dns,dhcp-option DOMAIN-ROUTE .,route-gateway 192.168.0.161,topology subnet,ping 1,ping-restart 20,echo,ifconfig 192.168.0.162 255.255.255.224,peer-id 0,cipher AES-256-GCM'
Tue Dec 19 16:50:05 2023 Options error: Unrecognized option or missing or extra parameter(s) in [PUSH-OPTIONS]:2: block-outside-dns (2.4.12)
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: timers and/or timeouts modified
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: --ifconfig/up options modified
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: route options modified
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: route-related options modified
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: --ip-win32 and/or --dhcp-option options modified
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: peer-id set
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: adjusting link_mtu to 1624
Tue Dec 19 16:50:05 2023 OPTIONS IMPORT: data channel crypto options modified
Tue Dec 19 16:50:05 2023 Outgoing Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Tue Dec 19 16:50:05 2023 Incoming Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Tue Dec 19 16:50:05 2023 ROUTE_GATEWAY 192.168.20.2/255.255.255.0 IFACE=bridge0 HWADDR=00:0c:29:27:53:90
Tue Dec 19 16:50:05 2023 TUN/TAP device tun0 opened
Tue Dec 19 16:50:05 2023 TUN/TAP TX queue length set to 100
Tue Dec 19 16:50:05 2023 /sbin/ip link set dev tun0 up mtu 1500
Tue Dec 19 16:50:05 2023 /sbin/ip addr add dev tun0 192.168.0.162/27 broadcast 192.168.0.191
Tue Dec 19 16:50:05 2023 /sbin/ip route add 52.193.69.78/32 via 192.168.20.2
Tue Dec 19 16:50:05 2023 /sbin/ip route add 0.0.0.0/1 via 192.168.0.161
Tue Dec 19 16:50:05 2023 /sbin/ip route add 128.0.0.0/1 via 192.168.0.161
Tue Dec 19 16:50:05 2023 WARNING: this configuration may cache passwords in memory -- use the auth-nocache option to prevent this
Tue Dec 19 16:50:05 2023 Initialization Sequence Completed
```
上記出力の内、ポイントは下記の行になります。
```
Tue Dec 19 16:50:05 2023 TUN/TAP device tun0 opened
```
tun0というネットワーク・デバイスがオープンされます。接続中はこのデバイスが使用されます。
```
Tue Dec 19 16:50:05 2023 /sbin/ip addr add dev tun0 192.168.0.162/27 broadcast 192.168.0.191
```
VPN接続用に「192.168.0.162/27」というCIDR範囲のアドレスがアサインされます。この範囲でのIPアドレスが使用されていることが、AWSコンソールのクライアントVPNエンドポイントの「接続」タブから確認できます。
![p31.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/5f70533f-ad6c-4c70-db3e-b3426482ffde.jpeg)
また、VPN接続に伴い「イングレスバイト数」「エグレスバイト数」「イングレスパケット」「エグレスパケット」数もカウントアップしています。
![p30.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/af90f7ac-4bb7-de3c-1aeb-5b2e3e2f1b03.jpeg)
### プライベートEC2インスタンスへの接続確認
CloudFormationの下記「出力」タブに記載されているプライベートEC2インスタンスのプライベートIPアドレスは、10.0.2.10となっていることがわかります。
![p45.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/087b920b-52d7-f821-8066-dbf7dcb6cefb.jpeg)
PCのターミナルから、このアドレスに対してまずはPINGコマンドを発行してみます。
```
$ ping 10.0.2.10
64 bytes from 10.0.2.10: icmp_seq=1 ttl=254 time=7.69 ms
64 bytes from 10.0.2.10: icmp_seq=2 ttl=254 time=9.17 ms
64 bytes from 10.0.2.10: icmp_seq=3 ttl=254 time=10.2 ms
:
:
```
問題なく応答が返ってきます。続いて、SSH接続を試みます。
```
$ ssh -i ~/.ssh/MyEC2Key.pem ec2-user@10.0.2.10
The authenticity of host '10.0.2.10 (10.0.2.10)' can't be established.
ECDSA key fingerprint is SHA256:kIihAMZQYWdMuBfk1I74IjM6JGqIWaVrF09KSkrmhR4.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.2.10' (ECDSA) to the list of known hosts.

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

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-10-0-2-10 ~]$
```
無事にプライベートIPアドレスを使って、プライベートEC2インスタンスにSSH接続することができました!

### プライベートEC2インスタンスからのOutbound通信確認
後続のEFS関連で必要になるマウント・ヘルパーのユーティリティーをダウンロードします。
```
[ec2-user@ip-10-0-2-10 ~]$ sudo yum install -y amazon-efs-utils
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                      | 3.6 kB  00:00:00
amzn2extra-docker                                                               | 2.9 kB  00:00:00
amzn2extra-kernel-5.10                                                          | 3.0 kB  00:00:00
(1/7): amzn2-core/2/x86_64/group_gz                                             | 2.7 kB  00:00:00
(2/7): amzn2-core/2/x86_64/updateinfo                                           | 760 kB  00:00:00
(3/7): amzn2extra-docker/2/x86_64/updateinfo                                    |  13 kB  00:00:00
(4/7): amzn2extra-docker/2/x86_64/primary_db                                    | 105 kB  00:00:00
(5/7): amzn2extra-kernel-5.10/2/x86_64/updateinfo                               |  42 kB  00:00:00
(6/7): amzn2extra-kernel-5.10/2/x86_64/primary_db                               |  21 MB  00:00:00
(7/7): amzn2-core/2/x86_64/primary_db                                           |  69 MB  00:00:01
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ amazon-efs-utils.noarch 0:1.35.0-1.amzn2 を インストール
--> 依存性の処理をしています: stunnel5 のパッケージ: amazon-efs-utils-1.35.0-1.amzn2.noarch
--> トランザクションの確認を実行しています。
---> パッケージ stunnel5.x86_64 0:5.58-1.amzn2.0.1 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

=======================================================================================================
 Package                     アーキテクチャー  バージョン                  リポジトリー           容量
=======================================================================================================
インストール中:
 amazon-efs-utils            noarch            1.35.0-1.amzn2              amzn2-core             57 k
依存性関連でのインストールをします:
 stunnel5                    x86_64            5.58-1.amzn2.0.1            amzn2-core            165 k

トランザクションの要約
=======================================================================================================
インストール  1 パッケージ (+1 個の依存関係のパッケージ)

総ダウンロード容量: 221 k
インストール容量: 527 k
Downloading packages:
(1/2): amazon-efs-utils-1.35.0-1.amzn2.noarch.rpm                               |  57 kB  00:00:00
(2/2): stunnel5-5.58-1.amzn2.0.1.x86_64.rpm                                     | 165 kB  00:00:00
-------------------------------------------------------------------------------------------------------
合計                                                                   1.5 MB/s | 221 kB  00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : stunnel5-5.58-1.amzn2.0.1.x86_64                                       1/2
  インストール中          : amazon-efs-utils-1.35.0-1.amzn2.noarch                                 2/2
  検証中                  : amazon-efs-utils-1.35.0-1.amzn2.noarch                                 1/2
  検証中                  : stunnel5-5.58-1.amzn2.0.1.x86_64                                       2/2

インストール:
  amazon-efs-utils.noarch 0:1.35.0-1.amzn2

依存性関連をインストールしました:
  stunnel5.x86_64 0:5.58-1.amzn2.0.1

完了しました!

[ec2-user@ip-10-0-2-10 ~]$
```
無事に外部へのOutbound通信が実行できて、必要なパッケージをダウンロードすることができました!

## 他サービスへの接続確認
Client VPN接続することで、外部からSSHでプライベートEC2インスタンスにアクセスすることが可能になりました。ここではVPN接続状態のプライベートEC2インスタンスにおいて、他サービスへの接続もできるかを確認してみることにします。今回は構成図にも記載しましたが、EFSを使ってみます。
### EFS作成
EFS作成用サンプルコードは、下記のcreate_EFS.yamlです。
<details><summary>create_EFS.yaml</summary>

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample Script for Creating Elastic File System

Parameters:
  MyClientVPNStackName:
    Description: This stack name is already created Network Stack for provisioning Client VPN Environment
    Type: String
    Default: MyClientVPN

Resources:
  MyMountTargetSecGrp:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow access to MyEFS from MyPrivateSubnet
      VpcId:
        Fn::ImportValue:
          !Sub "${MyClientVPNStackName}--VPCId"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '2049'
          ToPort: '2049'
          SourceSecurityGroupId:
            Fn::ImportValue:
              !Sub "${MyClientVPNStackName}--PrivateSubnetSecGrpId"
  MyEFS:
    Type: AWS::EFS::FileSystem
    Properties:
      Encrypted: true
      FileSystemTags:
        - Key: Name
          Value: My-EFS
  MyEFSMountTarget:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId: !Ref MyEFS
      SubnetId:
        Fn::ImportValue:
          !Sub "${MyClientVPNStackName}--PrivateSubnetId"
      SecurityGroups:
        - !Ref MyMountTargetSecGrp

```

</details>

このコードはClient VPNのスタックがアクティブ状態で実施する必要がある点だけが注意です。当コード実行完了後のEFSの状態は下記のとおりです。
![p37.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/b77d05df-9542-c9f7-f96d-5ae937a14b02.jpeg)
![p38.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/741562b3-9342-2f8a-79d0-e5d63e05a0de.jpeg)

### EFSのマウント実施
![p39.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/b8473c61-637f-0053-1ab3-ecc6de579dd1.jpeg)
上記画面の右上にある「アタッチ」ボタンを押下すると、マウントするために必要なコマンド等を参照することができます。
![p41.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/7e3bbdbe-3942-6d29-9212-10780c637eed.jpeg)
赤枠で囲んでいる箇所が今回のEFSマウント用のコマンドになります。これをターミナルから実行します。
※事前にマウントポイントのefsディレクトリを作成しておくことを忘れないように注意。
```
[ec2-user@ip-10-0-2-10 ~]$ sudo mkdir efs
[ec2-user@ip-10-0-2-10 ~]$ sudo mount -t efs -o tls fs-0f782368482ed0ef5:/ efs
[ec2-user@ip-10-0-2-10 ~]$
```
無事にエラーなくプロンプトが返ってきましたので、EFSのマウントは成功です。mountコマンドを実行すると、最下行に下記のメッセージが出力されており、コマンド上からもマウントできていることが確認できます。
```
127.0.0.1:/ on /home/ec2-user/efs type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,noresvport,proto=tcp,port=20873,timeo=600,retrans=2,sec=sys,clientaddr=127.0.0.1,local_lock=none,addr=127.0.0.1)
```
後はefsディレクトリに移動すれば普通にファイル作成などが可能です。

### EFSアンマウント実施
親ディレクトリに戻り、umountコマンドを実行します。
```
[ec2-user@ip-10-0-2-10 efs]$ cd
[ec2-user@ip-10-0-2-10 ~]$ sudo umount efs
[ec2-user@ip-10-0-2-10 ~]$
```
## さいごに
今回は2024年2月にAWSのパブリックIPアドレスが有料化になることへの対策として、下記2点の検証を行い、問題ないことを確認できました。
* Client VPNを使用すれば、パブリックEC2インスタンスを作成しなくても、外部のPC端末からプライベートIPを使用して、プライベートEC2インスタンスにSSH接続できること。
* VPN接続状態のプライベートEC2インスタンスから他のサービスも使用できること。

結果的には問題ないものの、個人的には以下の点が辛いところだと感じています。
* Client VPN環境のプロビジョニングには、CloudFormationのコード開始から終了までの処理時間が<font color="red">10分近く</font>要する。
  * パネルからClient VPN Endpointを作成しても同じように時間がかかります。接続先のネットワークのアタッチに時間を要するようです。
  * 以前のパブリックEC2インスタンスのプロビジョニングであれば、コード開始から終了まで<font color="blue">1分強</font>で完了していたことを思うと、待ち時間が長い。

 * 有料の為、今まで使用を避けてきた、NAT GatewayやEIPの利用が必要になる。
   * オブジェクトストレージであるS3に対してプライベート・インスタンスからOutbound通信がしたいのであれば、VPC Gateway Endpointを使用することで無料作成は可能。ただし、VPC内からインターネットへ向けてのOutbound通信を使用したい場合には避けては通れない。

実際に2024年2月に有料化された後、今回の構築の費用対効果を検証する意味で、パブリックIP使用する場合とそうでない場合との比較をしてみようと思います。