AWS Client VPN の導入

## 1. AWS Clinent VPN 導入の目的
スマホからのアクセスを固定IP化したい。

## 2. AWS Clinetn VPN 導入の経緯
　とある受託案件の検証環境は、今まで`IP制限`をしていました。
　会社のIPはもちろん許可していたため、PCもスマホも会社のネットワークに繋ぐことで検証環境にアクセスできていました。
　しかしコロナウイルスによって`在宅勤務が強制`されることになったため、`自宅環境ではIPの固定化ができず、検証環境にアクセスすることができなくなってしまいました。`
  ※PCであれば会社が用意したVPNに接続することができるのですが、スマホは会社が用意したVPNに接続することができません。
　そのため案件専用のVPNとして`AWS Client VPN`を用意し、`スマホからのアクセスを固定IP化`することにしました。

## 3. AWSClientVPNのポイント

- VPC上に`Client VPN Endpoint`を作成することで、クライアント（PCやスマホ）とVPCの間でVPNを張ることができます。

- `OpenVPN`(オープンソースのVPNソフトウェア）を利用しています。

- 認証は `ActiveDirectoryによるアカウント管理`, `サーバ証明書・クライアント証明書による相互認証` のいづれか、もしかは `その両方` を利用できます。

- ActiveDirectory認証では `AWSDirectoryService` を利用することになります。オンプレのADと連携したい場合は `ADConnector` を噛ませることで実現可能です。

## 4. AWSClientVPNの料金

- A) ClientVPNEndpointに関連付けられているサブネットの数 × 利用時間(hrs)
- B) アクティブなクライアント接続の数 × 利用時間(hrs)

AとBの合算になります。

### 4-1. シミュレーション
**条件**
冗長構成にするために2つのサブネットに関連付けをし、営業日には10端末からそれぞれ2h利用する。

**金額計算**
 - A)  2   × 24 × 30 × 0.15 = `$216`
 - B)  10 ×   2 × 20 × 0.05 = `$20`

合計 `$236/月`


## 5. 今回構築した構成のアーキテクチャ
<img width="847" alt="スクリーンショット 2020-04-12 17.27.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306374/d604c971-b614-f25c-fc31-7b9ed98a811b.png">

### 5-1. 認証
"ActiveDirectoryによるアカウント管理" をするためには別途 "AWSDirectoryService" の費用がかかってしまうため、今回は`サーバ証明書・クライアント証明書による相互認証のみ`で構築しました。
`SimpleAD`で最低`$36/月`かかるっぽいです。

###5-2. 証明書の作成
もちろん自分自身のPCでもできますが、他のインフラ担当者も同じ認証局が使えるように、共用環境としてVPN Client Endpointを構築するVPCの`bastionサーバー`にて作成しました。


証明書は内部利用(いわゆるオレオレ証明書)に限られるので、有効期限を`100年`に設定しました。

###5-3. VPC
アクセス先はステージング環境のVPCとなりますが。Client VPN Endpointをステージング環境に構築しても基本的には問題ありませんが、なるべく外部からのアクセスっぽくしたかったのと、ステージング環境に極力手を入れたくなかったため、Client VPN Endpointは別のVPNに構築することにしました。

###5-4. 関連付けるサブネット
導入の経緯に書いたようにサービスにクリティカルな用途ではないため、冗長構成にせず１つのサブネットのみに関連付けさせるようにしました。

## 6. 構築手順
### 6-1. 証明書の作成
bastionサーバーログインし`OpenVPN Easy-RSA レポジトリのクローンを作成`します。
なお私はbastionサーバーのrootユーザーになりホームディレクトリをカレントディレクトリとして作業をしましたが、rootユーザーである必要性はありません。

```
$ cd ~
$ git clone https://github.com/OpenVPN/easy-rsa.git
```
作成した証明書や鍵をまとめて保管ためのディレクトリを作成しておきます。

```
$ mkdir ssl
```

ローカルリポジトリの easy-rsa/easyrsa3 フォルダに移動します

```
$ cd easy-rsa/easyrsa3
```

証明書の有効期限を100年に変更します。

```shell
$ cp vars.example vars
$ vim vars
$ diff vars.example vars
125c125
< #set_var EASYRSA_CA_EXPIRE	3650
---
> set_var EASYRSA_CA_EXPIRE	36500
129c129
< #set_var EASYRSA_CERT_EXPIRE	825
---
> set_var EASYRSA_CERT_EXPIRE	36500
134c134
< #set_var EASYRSA_CRL_DAYS	180
---
> set_var EASYRSA_CRL_DAYS	36500
```



新しい PKI 環境を初期化します。

```shell
$ ./easyrsa init-pki
```

#### 6-1-1. ルート証明書
1つだけ作成します。ACMに登録する必要があります。
まずは新しい認証機関(CA)を構築します。

```
$ ./easyrsa build-ca nopass
```
画面の指示に従って、CA を構築します。

作成されたルート証明書とルート鍵を保管用ディレクトリにコピーします。

```
$ cp pki/ca.crt ~/ssl/
$ cp pki/private/ca.key ~/ssl/
```

ACMに登録します。その際中間証明書(chain)は指定無しで大丈夫です。

```
$ cd ~/ssl
$ aws acm import-certificate --certificate file://ca.crt --private-key file://ca.key --region ap-northeast-1
```

#### 6-1-2. サーバー証明書
サーバー証明書は1つだけ作成します。ACMに登録する必要があります。
サーバー証明書とキーを生成します。

```
$ ./easyrsa build-server-full server nopass
```

作成されたルート証明書とルート鍵を保管用ディレクトリにコピーします。

```
$ cp pki/issued/server.crt ~/ssl/
$ cp pki/private/server.key ~/ssl/
```

サーバー証明書をACMに登録します。その際中間証明書(chain)はルート証明書を指定します。

```
$ cd ~/ssl
$ aws acm import-certificate --certificate file://server.crt --private-key file://server.key --certificate-chain file://ca.crt --region ap-northeast-1
```

#### 6-1-3. クライント証明書
ルート証明書をベースに作成します。`ユーザー単位、もしくは端末単位など、管理したい単位ごとに作成します。`
クライント証明書は`ACMに登録する必要はありません。`

ここでは例として、`山田太郎さんのandroid端末用`としてクライント証明書を発行します。

```
$ ./easyrsa build-client-full taro.yamada.android nopass
```

これでクライント証明書とクライント鍵が以下に作成されました。


###6-2. Client VPN Endpoint の作成
AWSマネジメントコンソールにログインし下記の手順で Client VPN Endpoint を作成します。
[ステップ 2: Client VPN エンドポイントを作成する](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-getting-started.html#cvpn-getting-started-endpoint)

エンドポイント名前、クライアントに割り当てるCIDR、利用するサーバ証明書・ルート証明書などを選択します。(選択する証明書はACMに登録されている必要があります)

接続ログを `CloudWatch Logs` に残すかどうかも選択できますが、ロググループは自動生成してくれないため、事前に作成したうえで指定するあります。ログストリームはおそらく自動生成してくれるかも。

またクライアントが使用するDNSサーバやTLSセッションにTCP/UDPのどちらを利用するかを選択することができますが、特にこだわりはないのでデフォルトのままとしています。

なお Client VPN Endpoint を作成した時点では`まだ利用料金は発生しません`

###6-3. Client VPN Endpoint をVPCのサブネットに関連付ける
Client VPN Endpointをサブネットに関連付けすると、サブネットごとにENIが作成されます。ENIが作成された時点から料金が発生することになります。
[ステップ 3: クライアントの VPN 接続を有効にする](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-getting-started.html#cvpn-getting-started-target)

作成されたENIには、`自動的にそのVPCのデフォルトセキュリティグループがアタッチされます。`通信元/先IPアドレスやポートを制限したい場合は、別のセキュリティグループをアタッチすることができます。
本来であれば専用のセキュリティグループをアタッチすべきですが、今回はちょっとサボってデフォルトのままとしました。


またクライアントPCからVPN経由でVPC内のリソース等にアクセスすると、接続元IPアドレスはENIのIPになっています（NATされている）。接続先リソースのセキュリティグループ設計はそれを考慮する必要があります。

###6-4. クライアントのアクセスの「承認」
クライアント端末からVPC内のどのIPレンジへの通信を許可するか、CIDR形式で許可する必要があります。
[ステップ 4: クライアントのネットワークへのアクセスを承認する](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-getting-started.html#cvpn-getting-started-rules)

`認証タブ`を選択すると、クライアント端末から接続可能な接続先一覧が表示され、許可を追加することができます。デフォルトではどのCIDRも許可されていないので、接続先を追加します。

VPC内の特定のCIDRのみを許可すれば、クライアント端末からはその範囲までにしかアクセスできません。

今回はNATGateway経由でインターネットに出る必要があるため`0.0.0.0/0`への通信を許可する必要があります。

###6-5. 追加のネットワークへのアクセスを有効にする
Client VPN Endpoint には「ルートテーブル」の設定があります。
[ステップ 5: (オプション) 追加のネットワークへのアクセスを有効にする](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-getting-started.html#cvpn-getting-started-routes)

クライアント端末からVPC内にだけ通信する用途であれば、デフォルト設定で問題ありません。
今回はクライアント端末からNATGateway経由でインターネットに出たいので、追加の設定が必要になります。

Client VPN Endpointのルートテーブルにレコードを追加する際には、`宛先のCIDR`と`ターゲットサブネット`の2つを指定します。クライアント端末から通信がClient VPN Endpointに到達すると、その後パケットは、`宛先CIDR`情報をもとに`ターゲットサブネット`に振られ、そのサブネットのルートテーブルに従います。

そのため今回の用途のように、NATGateway経由でインターネットに出たい場合は、以下のレコードをClient VPN Endpointのルートテーブルに追加することになります。

`宛先のCIDRが「0．0．0．0/0」。ターゲットサブネットが「Client VPN Endpointに関連付けたプライベートサブネット」`

※そもそもターゲットサブネットは、関連付けたサブネットからしか設定することができません。今回は１つしか関連付けていないため一択となります。

###6-6. クライアント用の設定プロファイルをダウンロードする

クライアント用の設定プロファイルをダウンロードして、クライアントソフトに設定します。
[ステップ 6: Client VPN エンドポイントの設定ファイルをダウンロードする](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/cvpn-getting-started.html#cvpn-getting-started-config)

マネコンで`クライアント設定のダウンロード`ボタンを押せば`.ovpn`という拡張子の設定プロファイルをダウンロードできます。

中身は以下のようになっているはずです。

```
client
dev tun
proto udp
remote cvpn-endpoint-xxxxxxxxxxxxxxxxx.prod.clientvpn.ap-northeast-1.amazonaws.com 443
remote-random-hostname
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
verb 3
<ca>
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXX
-----END CERTIFICATE-----

</ca>
reneg-sec 0
```
しかし、このプロファイルそのままではVPN接続ができないため、少しイジってあげる必要があります。

####6-6-1. 接続先エンドポイントの細工
接続先のエンドポイントとしては、`*.[エンドポイント名].prod.clientvpn.ap-northeast-1.amazonaws.com` でIPを取得できますが、ダウンロードしたクライアント設定プロファイルは以下のようになっていました。

```
remote cvpn-endpoint-xxxxxxxxxxxxxxxxx.prod.clientvpn.ap-northeast-1.amazonaws.com 443
```

そこで、接続先に適当なホスト部を追記します。

```
remote dummy.cvpn-endpoint-xxxxxxxxxxxxxxxxx.prod.clientvpn.ap-northeast-1.amazonaws.com 443
```

####6-6-2. クライント証明書と鍵の埋め込み
作成したクライアント証明書の`—–BEGIN CERTIFICATE—–`から`—–END CERTIFICATE—–`までと、
クライアント鍵の`—–BEGIN PRIVATE KEY—–`から`—–END PRIVATE KEY—–`までを追記します。

```
<cert>
-----BEGIN CERTIFICATE-----
YYYYYYYYYYYYYYYYYYYY
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
ZZZZZZZZZZZZZZZZZZZZ
-----END PRIVATE KEY-----
</key>
```

設定プロファイルは最終的には以下のようになります。

```
client
dev tun
proto udp
remote *.cvpn-endpoint-XXXXXXXXXXXXXXXX.clientvpn.ap-northeast-1.amazonaws.com 443
remote-random-hostname
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
verb 3
<ca>
-----BEGIN CERTIFICATE-----
XXXXXXXXXXXXXXXXXXXX
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
YYYYYYYYYYYYYYYYYYYY
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
ZZZZZZZZZZZZZZZZZZZZ
-----END PRIVATE KEY-----
</key>
reneg-sec 0
```

##7. 運用フェーズ
###7-1. クライアント証明書の新規発行
ここでは例として、`山田太郎さんのios端末用`クライント証明書を新規発行します。

```
$ CLIENTNAME=taro.yamada.ios
$ cd ~/easy-rsa/easyrsa3
$ ./easyrsa build-client-full ${CLIENTNAME} nopass
$ cp pki/issued/${CLIENTNAME}.crt ~/ssl/
$ cp pki/private/${CLIENTNAME}.key ~/ssl/
```

###7-2. クライアント証明書の無効化
端末を紛失した場合や、漏洩の可能性がある場合には、クライアント証明書を無効化する必要があります。

`CRL(クライアント証明書失効リスト)`を使用すると、特定のクライアント証明書をブラックリストに入れることができます。ブラックリストに入れたクライアントは、クライアント VPN エンドポイントにアクセスできなくなります。

ここでは例として`山田太郎さんのios端末用`クライント証明書を失効させます。

ルート証明書・サーバー証明書・クライアント証明書などを作成した`bastionサーバー`にログインし、`easy-rsa/easyrsa`フォルダに移動します。

```
$ CLIENTNAME=taro.yamada.ios
$ cd ~/easy-rsa/easyrsa3
```

失効リスト用の前準備をします。プロンプトが表示されたら`yes`と入力します。

```
$ ./easyrsa revoke ${CLIENTNAME}

Note: using Easy-RSA configuration from: /root/easy-rsa/easyrsa3/vars
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017


Please confirm you wish to revoke the certificate with the following subject:

subject=
    commonName                = taro.yamada.ios


Type the word 'yes' to continue, or any other input to abort.
  Continue with revocation:
```

クライアント証明書失効リストを生成します。

```
$ ./easyrsa gen-crl
Note: using Easy-RSA configuration from: /root/easy-rsa/easyrsa3/vars
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
Using configuration from /root/easy-rsa/easyrsa3/pki/easy-rsa-17745.1UBzCm/tmp.XrqfAj

An updated CRL has been created.
CRL file: /root/easy-rsa/easyrsa3/pki/crl.pem
```

この`crl.pem`は、今まで失効されたクライアント証明書情報も入っています。
Cleient VPN Endpointに適用します。

```
$ aws ec2 import-client-vpn-client-certificate-revocation-list --certificate-revocation-list file:/root/easy-rsa/easyrsa3/pki/crl.pem --client-vpn-endpoint-id ${endpoint_id} --region ap-northeast-1
```

これ以降、該当のクライアント証明書からはVPN接続ができなくなります。


##8. AWS Clinetn VPNの制限

| Key       | Value |
|:-----------------|------------------:|
|アカウントあたりの クライアント VPN エンドポイント数|5|
|クライアント VPN エンドポイントあたりの承認ルールの数|50|
|クライアント VPN エンドポイントあたりのルート数|10|
|クライアント VPN エンドポイントあたりの同時クライアント接続数|2000|