GCP で構築したインスタンスに VPN で接続する(Web開発の開発環境をGCE上に作る)

こんにちは！
本日はVue.jsやLaravelなどの開発環境をクラウドに移行するために、クラウド上にあるインスタンスにVPN接続してローカルのMacから開発画面にアクセスできるようにしてみます。
具体的にはGCEとローカルのMacをVPN接続して、GCE上で動作するVue.jsの開発画面にアクセスできるようにしていきます。

追記
必ずスナップショットを取ってから作業をしてください。
また自己責任でよろしくお願いします。

さらに追記
VPNを立てるよりもSSHポートフォワーディングを使った方が簡単で安全かもしれません。

## 前提

### サーバー環境(接続先)

- Google Compute Engine
- Ubuntu 18.04.3 LTS
- iptables v1.6.1
- OpenVPN 2.4.4
- Vue.js 3.0

### クライアント環境(接続元)

- Mac OS X Version 10.11.6
- Tunnelblick 3.8.2 beta01(VPN接続のクライアントソフト)

# GCEの作成
[Google Compute Engine](https://cloud.google.com/compute/?hl=ja)を使ってインスタンスを作成します。
詳細な手順は割愛しますが、***ip転送のところをオンにする***のを忘れないでください。ここをオンにしないとローカルのMacからGCE上のVue.jsの開発画面にアクセスできないと思います。
加えてあとからの変更はできないので、すでにオフで作成してしまっている方はスナップショットなどを作成して新しくインスタンスを生成してみるのがいいかと思います。必要な方はグローバルIPの固定もお忘れないようにお願いします。

<img src="https://soundoflaravel.s3.ap-northeast-1.amazonaws.com/2019/10/SS 2019-10-06 at 00.22.56.png" style="max-width: 500px;">

## ポートを開放してクライアント(ローカルのMac)から接続できるか確認する

次に VPN の構築をする前にポート開放を行い、この時点でサーバーとクライアントが接続できるかどうかを確認してきます。
まずは GCP 上のダッシュボードからポートを開放（特定の通信を許可）し、Linux 上のポートを開放していきます。

### GCP上でポート開放を行う

ではまずGCP上でポート開放を行います。

ダッシュボード右側のメニューから、VPNネットワーク > ファイアウォールルールを選択します。

<img src="https://soundoflaravel.s3.ap-northeast-1.amazonaws.com/2019/10/SS 2019-10-05 at 21.34.27.png" style="max-width: 500px;">

次に` ファイアウォールルール作成 ` をクリックし次のように設定します。

<img src="https://soundoflaravel.s3.ap-northeast-1.amazonaws.com/2019/10/SS 2019-10-05 at 21.39.13.png" style="max-width: 500px;">

<img src="https://soundoflaravel.s3.ap-northeast-1.amazonaws.com/2019/10/Screen Shot 2019-10-05 at 21.22.39.png" style="max-width: 500px;">

**名前**: なんでもいいのですが、`ネットワーク名-allow-vpn`とかがいいでしょう。 この場合だと `default-allow-vpn` になりますね。  
***トラフィックの方向***: 上り。受信トラフィックを受け入れることを意味します。  
**一致した時のアクション**: 許可  
**ターゲット**: ネットワーク上のすべてのインスタンス  
**ソース IP の範囲**: 今回はすべての通信を許可します。IP制限したい方はここのIPを変更してください。  
**プロトコルとポート**: tcp:1194。今回はTCPプロトコルを使います。UDPの方はUDPの方に、udp:1194と入力してください。ちなみにポート1194番はOpenVPNのデフォルトのポート番号になります。

この時点でインスタンスに対して、ポート1194番からのTCPパケット（通信）が許可されます。なので、Linux(今回は Ubuntu)がデフォルトで1194番を許可している場合は次のポート開放は行わなくて大丈夫です。逆に次のステップでうまくいかない場合はVPN接続に進む前にこのあたりの設定が正しいか見返してください。

### Linux上でポート開放を行う
現在のポートの状況を確認してみます。

```
$ sudo iptables -L
```
<img src="https://soundoflaravel.s3.ap-northeast-1.amazonaws.com/2019/10/SS 2019-10-05 at 21.59.01.png" style="max-width: 500px;">

ここでTCPのポート1194番を許可します。(UDPの方はtcpのところをudpにしてください)

```
$ sudo iptables -A INPUT -p tcp -m tcp --dport 1194 -j ACCEPT
```

今のままだと再起動すると元に戻ってしまうので、
再起動した後もiptablesの設定を保持するようにします。

```
$ sudo apt install iptables-persistent
```


設定を保存して再起動します。

```
$ sudo /etc/init.d/netfilter-persistent save
$ sudo /etc/init.d/netfilter-persistent reload
```

ここで一応インスタンス(GCE)を再起動しておきます。  
僕の場合、再起動せずに次のステップに行ってしまったため、クライアントからのメッセージがサーバー側で受信できずに悩みましたが、再起動することで接続できるようになりました。


### ポート開放が正しくできたか確認する

ncコマンドを使ってserver側（Linux 側）にTCPサーバーを立ててLISTENし、クライアント側(Mac 側)からTCPでメッセージを送信してみます。下の画像のようにクライアントから送ったメッセージをサーバーが受信できていれば、ポートが正しく開放できています。

サーバー(Linux)側

```bash
$ nc -kl 1194
```

クライアント(Mac)側

```
$ nc GCPのグローバルIPアドレス 1194
```

クライアント側に何か適当にメッセージを入力してエンターで送信してあげます。その結果サーバーで送信したメッセージが表示されれば成功です。***ここがうまくいかない人はポート開放がうまくできていないので、VPN 接続する前になんとかここをクリアできるように頑張ってみてください。***

<img src="https://soundoflaravel.s3.ap-northeast-1.amazonaws.com/2019/10/Screen Shot 2019-10-05 at 21.07.57.png">
(左側がサーバー、右側がクライアントになります）


## VPN 接続の環境を作っていく

上記のクライアントとサーバーの通信がうまくいったので、正しくポートが開放できていることがわかりました。なのでいよいよ VPN 接続の環境構築をしていきます。

まずはOpenVPNとEasyRSA(証明書を手軽に発行できるパッケージ)をインストールします。

```
$ sudo apt-get update
$ sudo apt-get install openvpn easy-rsa
```

まずは証明書を作っていきます。

```
$ make-cadir ~/openvpn-ca
$ cd ~/openvpn-ca
```

次に`varsファイル`を編集していきます。
下記の部分を見つけて適当に入力します。

```
export KEY_COUNTRY="JP"              # 国
export KEY_PROVINCE="Tokyo"          # 都道府県
export KEY_CITY="Chiyoda-Ku"         # 市町村
export KEY_ORG="xxxxxxxx"            # 会社名
export KEY_EMAIL="yyy@xxxx.com"      # メールアドレス
export KEY_OU="zzzzz zzzzzz"        # 所属部署
```


さらに`varsファイル`内に設定されている環境変数を次のように変更します。

```
# export KEY_NAME="EasyRSA"
export KEY_NAME="server"
```

変更を反映させます。

```
$ source ./vars
```

ここで次のようなエラーが出た場合は、シンボリックを作成して対処します。

```
openssl.cnf could not be found
```

```
$ ln -s openssl-1.0.0.cnf open-ssl.cnf
$ source ./vars
```


認証局を作成します。基本的に全てエンターで大丈夫です。

```
$ ./build-ca
```


僕の環境ではエラーが出てしまったので、一度認証局の情報をリセットしてから再度実行しました。

```
$ ./clean-all
$ ./build-ca
```

サーバーの証明書を発行します。こちらも基本的にエンターキーで、何か聞かれたら`y`でいいでしょう。

```
$ ./build-key-server server
```

共通鍵を生成します。少し時間がかかります。

```
$ ./build-dh
```

通信を暗号化するための鍵を発行します。

```
$ openvpn --genkey --secret keys/ta.key
```

次にクライアントの認証情報を作っていきましょう。

```
$ cd ~/openvpn-ca
$ source vars
```

クライアント鍵の生成はパスワードありの場合となしの場合で分かれます。今回はなしの方法で作っていきます。パスワードありの場合だと接続する際にパスワードが必要になると思うのですが、僕の場合パスワードありにするとOpenVPNの起動に失敗してしまったので、パスワードなしで行いました。
なお下記コマンドのclient部分は自分のユーザー名にしても構いません。

パスワードありの場合

```
$ ./build-key-pass client
```

パスワードなしの場合

```
$ ./build-key client
```

なおクライアントの証明書を失効させるために`dummy`を作成したりする場合があるのですが今回は割愛します。クライアントの証明書を失効させる必要があるケースがある方は他の記事を参考にしてください。

下の記事の2-6とかがわかりやすいかもです。

<a href="https://qiita.com/megadreams14/items/0d5201245ec33ae463f4" target="_blank">OpenVPNをAmazonLinuxにインストールして、VPN経由で外部ネットワークにアクセスする</a>

OpenVPNが起動した時に作成したこれらのファイルを読み込むようにするため、`/etc/openvpnディレクトリ`に配置します。

```
$ cd ~/openvpn-ca/keys
$ sudo cp ca.crt server.crt server.key ta.key dh2048.pem etc/openvpn/
$ gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz | sudo tee /etc/openvpn/server.conf
```

`/etc/openvpn/server.conf`を次のように変更します。

変更箇所だけ書いています。

```
tcp
;proto udp

key-direction 0 # 追加
cipher AES-128-CBC
auth SHA256 ＃追加
user nobody #コメントアウト解除
group nogroup #コメントアウト解除
push "redirect-gateway def1 bypass-dhcp" # コメントアウト解除
push "dhcp-option DNS 208.67.222.222" #コメントアウト解除
push "dhcp-option DNS 208.67.220.220" # コメントアウト解除</p>
log /var/log/openvpn/openvpn.log # コメントアウト解除
```

このままだと VPN接続に成功してもVue.js上の開発画面にアクセスできない(VPN接続したMacからインターネットにアクセス出来ない状況になってしまう)ため、IPを転送するための設定を行っていきます。

まずは`/etc/sysctl.conf`を修正します。

```
$ sudo vi /etc/sysctl.conf
```

次の行のコメントアウトを解除します。

```
net.ipv4.ip_forward=1
```

変更を反映させます。

```
$ sudo sysctl -p
```

さらにパケットを転送するための設定を行っていきます。正直ちょっとここらへんの理解ができていないです。。。

下記の記事がイメージとして参考になるかもしれません。  
<a href="https://techblog.raccoon.ne.jp/archives/47152095.html" target="_blank">iptablesを利用してNATサーバを構築</a>


おそらく仮想的なセグメントを作成してサーバーはそこに対してパケットを転送するようにしてあげるけど、実際はクライアントにそのパケットが転送されているということではないかと思っています。

まずはサーバーが今使っているネットワークインターフェースが何であるのかを知る必要があります。

```
ip route | grep default
```

僕の場合だと次のような結果になりました。

```
default via 10.146.0.1 dev ens4 ・・・省略
```

ここで`ens4`が今使っているネットワークインターフェースということが判明しました。
なのでこのインターフェースからIPを転送するようにしていきます。
（ここらへんは自信がないので、説明が間違っている可能性が高いです）

```
$ sudo vi /etc/ufw/before.rules
```

`Don't delete these required lines` と書かれた直前に次の行を挿入します。

```
# OPENVPN
# NAT Table
*nat
:POSTROUTING ACCEPT [0:0]
# OpenVPN client traffic
-A POSTROUTING -s 10.8.0.0/8 -o ens4 -j MASQUERADE
COMMIT
# OPENVPN
```

最後にIP転送を許可するようにファイアウォールのルールを変更します。

```
$ sudo vi /etc/default/ufw
```

```
# DEFAULT_FORWARD_POLICY="DROP"
DEFAULT_FORWARD_POLICY="ACCEPT"
```

ufwを使ってファイアウォールの設定を反映させます。本当はiptablesでやりたかったのですが、僕のスキルでは無理だったので、ufwを一度有効にして反映させたのちに再度無効にします。***ufwを有効にすると、ssh などの接続が切れる可能性があるので注意して下さい。***
おそらく有効にしてすぐにsshのポートだけ許可すれば端末操作は継続できると思います。これで設定が反映されると思います。

反映する前と後で `sudo iptables -L` を実行して変化があるか確認してみてください。

反映します。

```
$ sudo ufw enable 
$ sudo ufw allow 22
$ sudo ufw disable
```

設定を保存します。

```
$ sudo /etc/init.d/netfilter-persistent save
$ sudo /etc/init.d/netfilter-persistent reload
```

## VPN サーバーを起動

いよいよVPNサーバーを起動します。

```
$ sudo systemctl start openvpn@server
```

起動できているか確認します。

```
$ sudo lsof -i:1194
```

NAME のところにopenvpnと出ていれば起動に成功しています。（名前が違うかもしれませんが、何かしら TCP で LISTEN しているものが表示されていれば成功しています。)

起動しない場合はログを確認してみてください。

```
$ sudo less /var/log/openvpn/openvpn.log
```

もしくは `/etc/openvpn/server.conf`の次の行をコメントアウトしてみてください。

```
explicit-exit-notify 1
```

それでも起動しない場合は次のコマンドを実行して手動で起動してみて、出てきたエラーの内容を確認してみてください。

```
$ sudo openvpn /etc/openvpn/server.conf
```

例えば僕の場合はこれが原因でIPアドレスがすでに存在してしまっているのが原因だったので再起動したら直りました。

```
RTNETLINK answers: File exists 
```

## クライアントに鍵を転送する

さてあと少しです。  
まずはクライアントからVPNで接続できるように、サーバーサイドで鍵を作ってクライアントに渡します。

```
$ mkdir -p ~/clients/files
$ chmod 700 ~/clients/files
$ cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf ~/clients/base.conf
```

`~/clients/base.conf`を次のように変更します。

```
proto tcp
;proto udp
;remote my-server-1 1194
remote グローバルIPアドレス 1194
user nobody #コメントアウト解除
group nogroup #コメントアウト解除
# 後ほどの設定で自動で読み込ませるためコメントアウトする

;tls-auth ta.key 1
;ca ca.crt # コメントアウト
;cert client.crt # コメントアウト
;key client.key # コメントアウト

key-direction 1 # 追加。サーバーが 0,クライアントが 1
cipher AES-128-CBC
auth SHA256 　＃追加
```

次にクライアントで使用する鍵を生成するためのスクリプトを作成します。

```
vi ~/clients/gen_config.sh
```

次の内容を貼り付けてください。

```
#!/bin/bash</p>
KEY_DIR=~/openvpn-ca/keys
OUTPUT_DIR=~/clients/files
BASE_CONFIG=~/clients/base.conf
cat ${BASE_CONFIG} \
    <(echo -e '<ca>') \
    ${KEY_DIR}/ca.crt \
    <(echo -e '</ca>\n<cert>') \
    ${KEY_DIR}/ca.crt \
    <(echo -e '</cert>\n<key>') \
    ${KEY_DIR}/ca.key \</p>
    <(echo -e '</key>\n<tls-auth>') \
    ${KEY_DIR}/ta.key \
    <(echo -e '</tls-auth>') \
    > ${OUTPUT_DIR}/${1}.ovpn
```

次のコマンドを実行します。

```
$ chmod 700 ~/clients/gen_config.sh
$ cd ~/clients
$ ./gen_config.sh client
$ ls ~/clients/files
```

`client.ovpn`ファイルが生成されていれば成功です。
このファイルをクライアントにコピーします。  
方法はなんでもいいのですが、今回は`gcloudコマンド`を使ってコピーします。
ローカルのMacに[Google Cloud SDK](https://cloud.google.com/sdk/docs/?hl=ja)をインストールして、Macから下記のコマンドを実行してください。先ほど生成したクライアント用の鍵をダウンロードすることができると思います。

```
$ gcloud beta compute --project your-project-id scp --zone your-zone instance-name:~/clients/files/client.ovpn ./client.ovpn --scp-flag="-P 22"
```

MacにVPNクライアントである`Tunnelblick`をインストールしてください。


<a href="https://tunnelblick.net/downloads.html" target="_blank">TunnelblinckのDLページ</a>

インストールが完了したら、先ほどダウンロードした鍵である`client.ovpn`をMacのステータスバー上にあるtunnelblickのアイコンにドラッグする、もしくはターミナルから次のコマンドを実行します。

```
$ open client.ovpn
```
その後ステータスバーにある`tunnelblic`のメニューから`clientに接続`をクリックすると、GCEとのVPN接続が始まります。これでVPN接続が無事完了しました。
確認のため、Macのブラウザから下記のリンクにアクセスして、表示されるIPアドレスがGCEのグローバルIPアドレスになっていることを確認してみてください。

<a href="https://whatismyipaddress.com/" target="_blank">What Is My IPAdress</a>

僕の場合、最初VPNを繋いだ状態でインターネットに接続することができなかったのですが、Macの再起動とサーバー上で`ufw enable`をもう一度行うことでうまく接続することができました！

最終確認としてMacのターミナルから次のコマンドを使って、pingを送ってみます。

```
$ ping 10.8.0.1
```

問題なければ成功です。
あとはVue.jsをGCE上のインスタンスにインストールして、サーバーを立ち上げて、コンソールに表示されたIPアドレスにアクセスしてみて開発画面が表示されれば成功です！（LaravelやRailsもこの方法でいけると思います)
お疲れ様でした！


## 参考サイト

とても参考にさせていただきました！本当にありがとうございました。

<a href="https://medium.com/teendevs/setting-up-an-openvpn-server-on-google-compute-engine-9ff760d775d9" target="_blank">「Setting up an OpenVPN Server on Google Compute Engine
」</a> 

<a href="https://qiita.com/yas-nyan/items/e5500cf67236d11cce72" target="_blank">「Ubuntu で iptables を再起動後にも保持する方法」</a>

<a href="https://laysakura.github.io/2016/07/24/cir0gtu71000kibiu1skemk6c/#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E4%BD%9C%E6%88%90" target="_blank">「Amazon EC2 でクライアント証明書認証の VPN を構築」</a>

<a href="https://qiita.com/megadreams14/items/0d5201245ec33ae463f4" target="_blank">「OpenVPN を AmazonLinux にインストールして、VPN 経由で外部ネットワークにアクセスする」</a>

<a href="https://odashi.hatenablog.com/entry/20110916/1316190103" target="_blank">「Linux でポート開放する」</a>