リモート環境にある Android 端末にリモートデバッグするために VPN を構築する

# はじめに
オークファン開発部の @Soogle_1729 です。
これまでは、主にバックエンドやフロントエンドなどを担当していましたが、今年はモバイルアプリを担当する機会がありました。
本記事では、モバイルアプリ開発時のデバッグ環境を整備した内容について記載します。

# 経緯
当社で、モバイルアプリの新規開発案件が開始した際に、下記の要望がありました。
- 開発中のアプリについて、**事業部側 (非エンジニア) の端末**でアプリの挙動を確認したい。
- エンジニアと事業部側の端末は、**リモート環境 (物理的に離れた場所)** にある。
- 事業部側は、開発環境がないので**自分ではソースコードから端末にビルドできない**。

Android アプリでは、Google Play Console の [内部テスト ](https://support.google.com/googleplay/android-developer/answer/9845334?hl=ja) によって関係者の端末にアプリを配信することが可能です。しかし、今回の場合は、Google Play Console にアップロードする前の段階からプロトタイプとしてアプリの挙動を確認したかったため、**VPN サーバーを経由して事業部側の端末へビルドする方法**を採用しました。(下図のイメージです)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661628/d4b99321-3fd2-2e3f-55be-b224af6cf26e.png)


以下では、AWS の EC2 インスタンス上に [OpenVPN](https://openvpn.net/) を構築して、リモート環境にある Android 端末へアプリをビルドするまでの手順について記載します。

なお、一般的に VPN サーバーの導入には、ハッキングやデータ漏洩などのセキュリティリスクが伴います。個人の判断で、社内リソースに対して VPN サーバーを構築するのは厳禁です。VPN サーバーの導入に際しては、必ず**ネットワーク管理者の許可**をもらってください。


# 環境
- AWS EC2 インスタンス
    - OS: Amazon Linux 2 AMI (HTM) - Kernel 5.10, SSD Volume Type
    - アーキテクチャ: 64ビット (arm)
    - インスタンスタイプ: t4g.micro
    - ストレージサイズ: 30 GiB
- ローカルPC
    - MacBook Air, M1 2020
    - OS: macOS 13.5.1  
- Android 端末
    - Google Pixel 6a
    - OS: Android 13

# 手順
- EC2 インスタンスを起動済みの前提とします。

## サーバーの基本設定
- EC2 サーバーに SSH接続します。

```console
$ ssh -i ~/.ssh/<キーペア> ec2-user@<IPアドレス>
```

### 日本時間の設定
- 社内でのみ使用するシステムのため、タイムゾーンを `Asia/Tokyo` に合わせます
```console
$ sudo timedatectl set-timezone Asia/Tokyo
```

### IP パケット転送の有効化
- `/etc/sysctl.conf` を編集して IPv4 のパケット転送を有効にします。
    - VPN クライアントからのトラフィックを転送することが可能となります。
```console
$ sudo sh -c "echo -e '\nnet.ipv4.ip_forward = 1' >> /etc/sysctl.conf"
$ sudo sysctl -p
net.ipv4.ip_forward = 1
```

### iptables によるマスカレードの有効化
- `iptables` を使用して、IP アドレスを変換するためのルールを追加します。
    - ここでは、`10.8.0.0/24` ネットワークを対象として、外部へ送信されるパケットの送信元 IP アドレスを `eth0` インターフェースの IP アドレスへ変更しています。
    

```console
$ sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
$ sudo iptables-save | sudo tee /etc/sysconfig/iptables
# Generated by iptables-save v1.8.4 on Thu Oct 12 11:59:23 2023
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [1:120]
:POSTROUTING ACCEPT [1:120]
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
COMMIT
# Completed on Thu Oct 12 11:59:23 2023

$ sudo chmod +x /etc/rc.d/rc.local | echo 'iptables-restore < /etc/sysconfig/iptables' | sudo tee -a /etc/rc.local
iptables-restore < /etc/sysconfig/iptables
```

- 続いて、システムの再起動時にも `iptables` のルールを維持するために `/etc/systemd/system/` へ `iptables-restore.service`  を作成します。
```/etc/systemd/system/iptables-restore.service
[Unit]
Description=Restore iptables rules
Before=network-pre.target
Wants=network-pre.target

[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /etc/sysconfig/iptables

[Install]
WantedBy=multi-user.target
```

- 作成した `iptables-restore.service` を有効化します。
```console
$ sudo systemctl enable iptables-restore.service
```


## 認証局（Certificate Authority）の構築
### easy-rsa のインストール
- VPN サーバーとクライアントそれぞれの証明書の発行・管理を行うための認証局を構築します。
- 認証局を構築するために、[easy-rsa](https://github.com/OpenVPN/easy-rsa) をインストールします。
```console
$ sudo amazon-linux-extras install epel
Installing epel-release
// 略
完了しました!

$ sudo yum install easy-rsa
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
225 packages excluded due to repository priority protections
// 略
完了しました!
```

### 認証局の作成
- 公開鍵インフラストラクチャ (pki) の初期設定を行います。
- 認証局の証明書を作成します。
    - 今回はサンプルなので、`nopass` を指定して秘密鍵にはパスフレーズを設定していませんが、セキュリティ面では設定をした方が良いです。
- 鍵交換に使用される DH パラメータを生成します。
- 失効・無効になった証明書を管理する証明書失効リスト (CRL) を生成します。
```console
$ sudo /usr/share/easy-rsa/3/easyrsa init-pki
init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /home/ec2-user/pki

$ sudo /usr/share/easy-rsa/3/easyrsa build-ca nopass
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
// 略
Your new CA certificate file for publishing is at:
/home/ec2-user/pki/ca.crt

$ sudo /usr/share/easy-rsa/3/easyrsa gen-dh
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
// 略
DH parameters of size 2048 created at /home/ec2-user/pki/dh.pem

$ sudo /usr/share/easy-rsa/3/easyrsa gen-crl
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
// 略
CRL file: /home/ec2-user/pki/crl.pem
```

### VPN サーバーの証明書作成
- VPN サーバーの証明書と秘密鍵を生成します。
    - 今回はサンプルなので、`nopass` を指定して秘密鍵にはパスフレーズを設定していませんが、セキュリティ面では設定をした方が良いです。
```console
$ sudo /usr/share/easy-rsa/3/easyrsa build-server-full server nopass
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
// 略
Write out database with 1 new entries
Data Base Updated
```

### クライアントの証明書作成
- VPN サーバーへ接続するクライアント用の証明書を生成します。
    - 今回は `test-vpn` という名称を指定しています。
    - `--days=365` で証明書の有効期限を 365 日に設定しています。
    - 今回はサンプルなので、`nopass` を指定して秘密鍵にはパスフレーズを設定していませんが、セキュリティ面では設定をした方が良いです。
- `/pki` ディレクトリをホームディレクトリの配下から `/usr/share/easy-rsa/3` の配下に移動させます。
```console
$ sudo /usr/share/easy-rsa/3/easyrsa --days=365 build-client-full test-vpn nopass
Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
// 略
Certificate is to be certified until Oct 11 01:32:31 2024 GMT (365 days)

$ sudo mv ~/pki /usr/share/easy-rsa/3
```


## OpenVPN サーバーの構築
### OpneVPN のインストール
- OpenVPN をインストールします。

```console
$ sudo yum install openvpn
// 略
完了しました!
```

### 共有秘密鍵の作成
- `/etc/openvpn` ディレクトリに移動します。
- OpenVPN 用の共有秘密鍵を生成します。
    - 生成された `ta.key` は、OpenVPN サーバーと全てのクライアントで共有される必要があります。
```console
$ cd /etc/openvpn
$ sudo openvpn --genkey --secret ta.key
```

### 証明書失効リスト (CRL) を OpenVPN 内に配置
- 作成していた証明書失効リスト (CRL)  を `/etc/openvpn` へコピーします。
- `crl.pem` に読み取り権限を付与します。
```console
$ sudo cp /usr/share/easy-rsa/3/pki/crl.pem /etc/openvpn
$ sudo chmod +r /etc/openvpn/crl.pem
```


### OpneVPN の設定
- [OpenVPN.JP のファイル](https://www.openvpn.jp/document/how-to/#SampleConfigurationFiles:~:text=sample%2Dconfig%2Dfiles/server.conf)を参考に `server.conf` を編集して、設定内容を記述します。
```console
$ sudo vi /etc/openvpn/server.conf
```

```server.conf
port 1194
proto udp
dev tun
user nobody
group nobody
# 各証明書・秘密鍵の指定
ca /usr/share/easy-rsa/3/pki/ca.crt
cert /usr/share/easy-rsa/3/pki/issued/server.crt
key /usr/share/easy-rsa/3/pki/private/server.key
dh /usr/share/easy-rsa/3/pki/dh.pem
# 証明書失効リスト
crl-verify /etc/openvpn/crl.pem
# VPN クライアントに割り当てる IP アドレス
server 10.8.0.0 255.255.255.0
# クライアントが再接続時に同じ IP アドレスを使用する
ifconfig-pool-persist ipp.txt
# 全てのクライアントトラフィックを VPN 経由とする
push "redirect-gateway def1"
push "dhcp-option DNS 8.8.8.8"
keepalive 10 120
cipher AES-256-GCM
max-clients 20
persist-key
persist-tun
tls-crypt /etc/openvpn/ta.key
status openvpn-status.log
log-append  /var/log/openvpn.log
verb 3
```

### OpenVPN の起動と自動起動の有効化
- サービスを起動し、`enable` コマンドで自動起動を有効化します。
```console
$ sudo systemctl start openvpn@server
$ sudo systemctl enable openvpn@server
Created symlink from /etc/systemd/system/multi-user.target.wants/openvpn@server.service to /usr/lib/systemd/system/openvpn@.service.

```

- ここまでで、サーバー側の設定は完了です。

## OVPN ファイルの作成
- OpenVPN サーバーと接続に必要となる `.ovpn` ファイルを作成します。
- 各クライアントに、生成した `.ovpn` ファイルを配布する必要があります。

### ファイルの作成
- クライアントごとに `.ovpn` ファイルを作成します。
- 今回は、クライアント名を `test-vpn-client` としています。

```console
$ client_name="test-vpn-client"
$ vpn_server_ip="<VPN サーバーのグローバル IP アドレス>"
$ cat > ${client_name}.ovpn << EOF
client
dev tun
proto udp
remote ${vpn_server_ip}
port 1194
cipher AES-256-GCM
resolv-retry infinite
nobind
persist-key
persist-tun
ca [inline]
cert [inline]
key [inline]
tls-crypt [inline]
verb 1
keepalive 10 120
remote-cert-tls server
<ca>
`sudo cat /usr/share/easy-rsa/3/pki/ca.crt`
</ca>
<cert>
`sudo cat /usr/share/easy-rsa/3/pki/issued/${client_name}.crt`
</cert>
<key>
`sudo cat /usr/share/easy-rsa/3/pki/private/${client_name}.key`
</key>
<tls-crypt>
`sudo cat /etc/openvpn/ta.key`
</tls-crypt>
EOF

$ ls -l
合計 12
-rw-rw-r-- 1 ec2-user ec2-user 8220 10月 12 10:52 test-vpn-client.ovpn
```

### ローカルへのダウンロード
- クライアントへ `.ovpn` ファイルを配布するために、ローカル環境へダウンロードします。
- ダウンロードした `.ovpn` ファイルを任意の方法でクライアントへ配布します。
```console
// EC2 から ローカルPC へダウンロード
$ scp -i ~/.ssh/<キーペア> ec2-user@<IP アドレス>:./test-vpn-client.ovpn /Users/Downloads
test-vpn-client.ovpn                                                                      100% 8220   143.4KB/s   00:00  
```


## Mac でのクライアントの設定
- 私が Mac を使用しているので、Mac でのクライアントの設定方法について記載します。
- macOS 用の OpenVPN クライアントソフトの [Tunnelblick](https://tunnelblick.net/) をインストールします。

```console
$ brew install --cask  tunnelblick
Running `brew update --auto-update`...
==> Homebrew collects anonymous analytics.
// 略
🍺  tunnelblick was successfully installed!
```

- インストールされた Tunnelblick を開きます。
- 画面に従って、進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661628/ff126a7a-43d3-c3a7-201b-80b67855b2d0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661628/6283c199-0fb9-ee41-205a-b153099d7199.png)


- 画面上部に追加されたアイコンから `VPNの詳細` を開きます。
- クライアント用の設定ファイル `test-vpn-client.ovpn` を 左側の `接続先` のエリアにドラッグします。

- `設定` から画像のように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661628/7d467b5d-1487-6941-7656-ca89a7834ed9.png)

- `設定 > 詳細設定...` を選択して、画像のように設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661628/d6cdaf00-a87e-9d75-43d0-85704965f9b8.png)


- 元の画面に戻り、`接続` をクリックすると VPN 接続が開始します。

### VPN 接続の確認
- 下記にアクセスします。
    - https://tunnelblick.net/ipinfo
- VPN サーバーの IP アドレスが表示されることを確認します。
- ブラウザから、任意の Web サイトにアクセスが可能であることを確認します。


## Android でのクライアントの設定
- クライアント用の `.ovpn` ファイルを管理者から受け取り端末にダウンロードします。
- Google Play Store から [OpenVPN Connect](https://play.google.com/store/search?q=openvpn+connect&c=apps&hl=ja&gl=US) をインストールします。
- OpenVPN Connect を開きます。
- `Import Profile > Upload File` から `Browz` を選択します。
- `.ovpn` ファイルを選択します。

__注意事項__
- Android 用の`.ovpn` ファイルは、 下記の4行は削除する必要があります。
    - ca [inline]
    - cert [inline]
    - key [inline]
    - tls-crypt [inline]

- ここまでで、開発者の PC と Android 端末を それぞれ VPN 接続する環境が整いました。

## 開発者 PC と Android 端末を VPN 経由でリモートデバッグする
### 開発者 PC と Android 端末 のペアリング
- 開発者 PC と Android 端末を VPN サーバーへ接続します。
- 端末側で `設定 > 開発者オプション > ワイヤレスデバッグ から ワイヤレスデバッグの使用` を有効にします。
- `ペア設定コードによるデバイスのペア設定` をタップし、下記を確認します。
    - Wi-Fi ペア設定コード: `6桁の数字`
    - IP アドレスとポート: `<IP アドレス>:<ポート番号>`
- ターミナル上で、下記のコマンドで開発者 PC と Android 端末をペアリングします。
    - `adb` コマンドは、`android_sdk/platform-tools` に格納されています。([参考](https://developer.android.com/studio/command-line/adb?hl=ja))
```console
$ abd pair <IP アドレス>:<ポート番号> <ペア設定コード>
Successfully paired to xxxxxxx [guid=xxxxxxx]
```

### 開発者 PC と Android 端末 の接続
- PC と端末がペアリングできたことを確認します。
    - 端末の `ペア設定済みのデバイス` に 開発者 PC が追加されます。
- IP アドレスとポートを再度確認します。(**ポート番号がペアリング時と異なります**)
- 下記のコマンドで PC と端末を接続します。
```console
$ adb connect <IP アドレス>:<ポート番号>
connected to xxxxxxx
```

- ここまでで、**開発者 PC から Android 端末に対してリモートデバッグが可能**になります。
- 任意のアプリを端末にビルドしてください。


### 接続の解除
- 接続を解除する場合は、下記のコマンドを使用します。
```console
$ adb disconnected
disconnected everything
```

# まとめ
- 少し長くなりましたが、VPN 経由で Android 端末へリモートデバッグする方法についてまとめました。
- VPN を利用したことはありましたが、VPN サーバーを構築したことはなかったので、勉強になったと思います。
- 本来は、iOS 端末についても同様にリモートデバッグしたかったのですが、PC に登録済みの iPhone に対して VPN 経由で接続することがどうしてもできませんでした。。。
    - どなたかご存知であれば、共有いただければ幸いです。

オークファングループでは、未経験者を含めてエンジニアを募集しています！興味のある方は、[コチラ](https://aucfan.co.jp/recruit/)をご覧ください。

# 参考資料
- [Amazon Linux2 でOpenVPNを構築する](https://qiita.com/kapioz/items/5b75c3470a4e57d916ba)
- [AWS環境にAmazon Linux 2でOpenVPNサーバを構築・運用する](https://toripiyo.hatenablog.com/entry/2020/05/14/212154)
- [OpenVPN 2.4によるVPN接続環境をAWSで構築する](https://dev.classmethod.jp/articles/openvpn24-aws/)