外出先から自宅のネットワークに安全にアクセスしたい……そんなとき、選択肢の1つになるのがVPNです。
今回は、
- Raspberry Pi上にPiVPN(OpenVPN)を構築
- Google Authenticatorを使ったワンタイムパスワード認証を追加
という形で、証明書+OTPの二要素認証によるVPN環境を作っていきます。
ポイントは、
- 固定パスワードは使わず、毎回変わる6桁のコードで認証
- VPN専用ユーザーはシステムへの直接ログインを禁止
- 時刻同期が命(OTPは時刻ベースなので)
といったあたりです。
モグラ先生:
「証明書」(持っているもの)と「OTP」(知っているもの)の二要素認証で、パスワード固定のリスクを減らせるのがポイントだね 🐾
目次
- 1. 今回作る構成のざっくりイメージ
- 2. まずはRaspberry Piの準備から
- 時刻同期の確認(超重要)
- 3. PiVPNのインストール
- 4. VPNクライアント用の証明書を作る
- 5. VPN専用のLinuxユーザーを作成する
- 5-1. ユーザー作成
- 5-2. パスワードはランダムで
- 5-3. SSHログインを禁止する
- 6. Google Authenticator(OTP)を導入する
- 6-1. パッケージのインストール
- 6-2. OTPのシークレットを生成
- 6-3. QRコードを画像にする(オプション)
- 7. PAM認証の設定(固定パスワードを使わないのがポイント)
- 8. OpenVPNサーバーの設定を確認
- 9. systemdの「ProtectHome」問題を解決する
- 10. クライアント側の設定
- 11. 接続してみよう
- 12. うまくいかないときは
- 13. さいごに:スマホ紛失に備えて
1. 今回作る構成のざっくりイメージ
まず、今回作るVPN環境の全体像をまとめておきます。
| 項目 | 設定内容 |
|---|---|
| VPNソフトウェア | OpenVPN(PiVPN経由でインストール) |
| 認証方式 | クライアント証明書 + TOTP(時間ベースOTP) |
| 固定パスワード | 使用しない(OTPのみで認証) |
| VPN用ユーザー | SSHログイン禁止(VPN専用) |
ポイントは、
- クライアント証明書:これがないとそもそも接続できない(第一の壁)
- OTP(Google Authenticatorの6桁コード):証明書があっても、これがないと認証が通らない(第二の壁)
- 固定パスワードは使わない:万が一パスワードが漏れても、OTPがなければ突破できない
という、二段階の防御を張る構成です。
モグラ先生:
証明書だけだと、スマホやPCを落としたときに心配。
OTPだけだと、スマホ紛失で詰む。
両方を要求することで、片方が漏れても突破されにくくなるんだね ⛏️
2. まずはRaspberry Piの準備から
VPNサーバーにするRaspberry Piを、最新の状態に更新しておきましょう。
sudo apt update
sudo apt upgrade -y
sudo reboot
時刻同期の確認(超重要)
OTP認証は、サーバーとスマホの時刻がズレていると認証が通りません。
そのため、時刻同期が有効になっているか必ず確認してください。
timedatectl
出力例:
System clock synchronized: yes
yes と表示されていればOKです。
モグラ先生:
OTPは「時間ベース」のワンタイムパスワードだから、
サーバーとスマホの時刻が数分ズレただけで認証が通らなくなるよ。
時刻同期の確認は 「超重要」 だから、忘れずにね 🐾
小さな穴掘りポイント 🕳️
- Raspberry Piは、デフォルトで
systemd-timesyncdによる時刻同期が有効になっています。 - もし
noと表示される場合は、sudo timedatectl set-ntp trueで有効化できます。 - Wi-Fi接続の場合、再起動直後は時刻同期が完了するまで少し時間がかかることがあります。
3. PiVPNのインストール
PiVPNは、OpenVPNの設定を簡単にしてくれるツールです。
公式のインストールスクリプトを実行します。
curl -L https://install.pivpn.io | bash
インストール中に、いくつか選択肢が出てきます。
インストール時の主な選択肢
| 項目 | 推奨設定 | 補足 |
|---|---|---|
| VPN Type | OpenVPN | WireGuardもありますが、今回はOpenVPN |
| Protocol | UDP | TCPより高速 |
| Port | 1194 | デフォルトのまま(任意で変更可) |
| DNS | お好みで | Google DNSやCloudflareなど |
| Encryption | デフォルト | 特に変更不要 |
| Static IP | 設定する | 推奨(動的IPだと接続が切れる可能性) |
動作確認
インストールが終わったら、サービスが起動しているか確認します。
systemctl status [email protected]
active (running) と表示されていれば成功です。
モグラ先生:
PiVPNは、OpenVPNの設定ファイルを自動生成してくれる便利ツールだよ。
手動でやると結構面倒だから、初めての人にはありがたいね ⛏️
4. VPNクライアント用の証明書を作る
次に、VPNクライアント(PCやスマホ)用の証明書を作成します。
pivpn add nopass
名前を聞かれるので、ユーザー名(例:hoge)を入力します。
nopass をつけると、パスワードなしの証明書が作られます(OTPで認証するので、証明書にパスワードは不要)。
作成済み証明書の確認
pivpn -l
生成された .ovpn ファイルは、/home/pi/ovpns/ に保存されます。
5. VPN専用のLinuxユーザーを作成する
OTP認証用に、Linuxのユーザーアカウントを作ります。
このユーザーは、VPN認証専用として使うので、システムへの直接ログインはできないように設定します。
5-1. ユーザー作成
sudo adduser hoge
パスワードや名前などを聞かれますが、とりあえず適当に進めてOKです(後でパスワードは変更します)。
5-2. パスワードはランダムで
VPN接続では、このパスワードは使いません(OTPを使うので)。
ですが、セキュリティ上、推測困難な値を設定しておきましょう。
sudo passwd hoge
パスワードマネージャーで生成した、長いランダム文字列を設定してください。
覚える必要はありません。
5-3. SSHログインを禁止する
VPN専用ユーザーなので、システムへの直接ログインを禁止します。
sudo usermod -s /usr/sbin/nologin hoge
確認コマンド:
getent passwd hoge
末尾が /usr/sbin/nologin になっていればOKです。
モグラ先生:
このユーザーは「VPN認証のためだけ」に存在するから、
SSHで直接ログインできないようにしておくのが安全だね。
万が一パスワードが漏れても、システムには入れないよ 🐾
小さな穴掘りポイント 🕳️
/usr/sbin/nologinを設定すると、そのユーザーでSSHやコンソールログインしようとしても、「ログインできません」というメッセージが表示されます。- VPN認証には影響しません(認証だけに使われるので)。
6. Google Authenticator(OTP)を導入する
ここからが本題です。
Google Authenticatorを使って、スマホに表示される6桁の数字で認証できるようにします。
6-1. パッケージのインストール
sudo apt install -y libpam-google-authenticator qrencode
6-2. OTPのシークレットを生成
VPN用ユーザー(hoge)として、google-authenticator を実行します。
sudo -u hoge -H google-authenticator
実行すると、QRコードといくつかの質問が表示されます。
質問への回答
| 質問(だいたいこんな感じ) | 回答 | 理由 |
|---|---|---|
| ファイルを更新するか | y | 設定を保存するため必須 |
| 同じトークンの再利用を禁止するか | y | リプレイ攻撃を防止 |
| 時刻許容範囲を拡大するか | n | セキュリティを維持(デフォルトで十分) |
| レート制限を有効にするか | y | 総当たり攻撃を防止 |
重要:緊急用スクラッチコードを保存
質問に答えると、画面に5つのスクラッチコードが表示されます。
これは、スマホを紛失したときの最終手段です。
各コードは1回限り使えるので、必ず安全な場所に保存してください。
Your emergency scratch codes are:
12345678
87654321
...
モグラ先生:
スクラッチコードは、スマホを失くしたときの「緊急脱出口」だよ。
これを保存しておかないと、スマホが壊れたら詰むから、
パスワードマネージャーや紙に書いて金庫に入れるなど、必ず保管しよう 🐾
6-3. QRコードを画像にする(オプション)
端末上のQRコードが読み取りにくい場合は、画像ファイルとして生成できます。
SECRET=$(sudo head -n 1 /home/hoge/.google_authenticator)
LABEL="OpenVPN:hoge"
ISSUER="MyVPN"
qrencode -o /tmp/hoge-otp.png \
"otpauth://totp/${LABEL}?secret=${SECRET}&issuer=${ISSUER}"
生成された /tmp/hoge-otp.png を、スマホに転送してGoogle Authenticatorで読み取ってください。
権限の確認
sudo ls -l /home/hoge/.google_authenticator
-rw------- でユーザー hoge のみが読み書きできる状態になっていることを確認します。
小さな穴掘りポイント 🕳️
.google_authenticatorファイルには、OTPのシークレットキーが入っています。- このファイルが他人に読まれると、OTPを偽造される可能性があるので、権限は必ず
600になっていることを確認してください。
7. PAM認証の設定(固定パスワードを使わないのがポイント)
OpenVPNが、OTP認証を使うように設定します。
ここで重要なのは、固定パスワード認証(pam_unix.so)を含めないことです。
/etc/pam.d/openvpn を作成
sudo tee /etc/pam.d/openvpn <<'EOF'
#%PAM-1.0
auth required pam_google_authenticator.so
@include common-account
EOF
これにより、
- OpenVPN接続時は、Google Authenticatorの6桁コードだけを要求される
- 固定パスワード(
/etc/shadowに保存されているやつ)は使われない
という状態になります。
モグラ先生:
ここがこの設定の 最大のポイント だよ。
pam_unix.so(固定パスワード認証)を入れないことで、
「パスワードが漏れても意味がない」 状態を作れるんだ ⛏️
小さな穴掘りポイント 🕳️
- もし
pam_unix.soを入れると、「固定パスワード + OTP」の入力を求められます(より厳重ですが、不便)。 - 今回は「証明書 + OTP」で十分なので、固定パスワードは使わない設定にしています。
8. OpenVPNサーバーの設定を確認
/etc/openvpn/server.conf に、以下の設定が含まれていることを確認します。
plugin /usr/lib/aarch64-linux-gnu/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
verify-client-cert require
username-as-common-name
PiVPNでインストールした場合、通常は自動で設定されています。
プラグインのパスについて
アーキテクチャによって、プラグインのパスが異なります。
- ARM64(64ビット):
/usr/lib/aarch64-linux-gnu/openvpn/plugins/ - ARM32(32ビット):
/usr/lib/arm-linux-gnueabihf/openvpn/plugins/
自分のRaspberry Piのアーキテクチャは、uname -m で確認できます。
9. systemdの「ProtectHome」問題を解決する
ここが、この設定で一番ハマりやすいポイントです。
PiVPNのOpenVPNサービスは、デフォルトで ProtectHome=true が設定されています。
この状態だと、/home 以下のファイル(.google_authenticator)にアクセスできません。
override設定の作成
sudo mkdir -p /etc/systemd/system/[email protected]
sudo tee /etc/systemd/system/[email protected]/override.conf <<'EOF'
[Service]
ProtectHome=false
EOF
設定の反映
sudo systemctl daemon-reload
sudo systemctl restart [email protected]
確認
systemctl show [email protected] -p ProtectHome
ProtectHome=no と表示されればOKです。
モグラ先生:
これを忘れると、「OTPを入力しても認証が通らない」という謎現象に見舞われるよ。
journalctlのログを見ると、
「ファイルにアクセスできません」みたいなエラーが出てるはずだから、
必ずこの設定をしようね 🐾
小さな穴掘りポイント 🕳️
ProtectHome=trueは、systemdのセキュリティ機能の1つです。- サービスが
/home以下にアクセスできないようにして、ユーザーファイルを保護します。 - しかし今回は、OTPファイルが
/home/hoge/.google_authenticatorにあるため、これを無効化する必要があります。 - よりセキュアにしたい場合は、OTPファイルを
/etc以下に移動する方法もありますが、設定が複雑になります。
10. クライアント側の設定
クライアント用の .ovpn ファイルを編集します。
.ovpnファイルに1行追加
auth-user-pass
この1行を追加すると、接続時にユーザー名とパスワード(OTP)の入力を求められるようになります。
11. 接続してみよう
OpenVPN GUI(Windows)やその他のクライアントで接続する際、こんな感じで入力します。
| 項目 | 入力内容 |
|---|---|
| ユーザー名 | hoge(作成したユーザー名) |
| パスワード | Google Authenticatorに表示される6桁の数字のみ |
入力例:
ユーザー名: hoge
パスワード: 482913
ポイントは、
- パスワード欄には、スマホに表示されている6桁の数字だけを入力
- 固定パスワードは入力しない(そもそも使わない設定になっている)
という点です。
モグラ先生:
証明書(.ovpnファイル)と、OTPの6桁、両方が揃って初めて接続できるよ。
これが 二要素認証 の強みだね 🐾
12. うまくいかないときは
接続に問題がある場合は、サーバー側のログを確認します。
sudo journalctl -u [email protected] -n 50 --no-pager
成功時のログ例
Initialization Sequence Completed
こんな感じで表示されれば、接続成功です。
よくある問題
| 症状 | 原因と対処 |
|---|---|
| OTP認証が通らない | 時刻同期を確認(timedatectl)。サーバーとスマホの時刻がズレている可能性 |
/home にアクセスできない | ProtectHome対策を確認。override設定を忘れている可能性 |
| プラグインが見つからない | アーキテクチャに合ったパスを指定(ARM64なら aarch64-linux-gnu) |
小さな穴掘りポイント 🕳️
- OTP認証エラーの9割は、時刻同期のズレが原因です。
- サーバー側で
timedatectlを実行し、System clock synchronized: yesになっているか必ず確認してください。 - スマホの時刻も、自動設定になっているか確認しましょう。
13. さいごに:スマホ紛失に備えて
この構成により、以下のセキュリティを実現できました。
- 二要素認証: クライアント証明書(持っているもの)+ OTP(知っているもの)
- 固定パスワード不使用: 漏洩リスクを低減
- 最小権限の原則: VPN専用ユーザーはシステムにログイン不可
最後に、スマホを紛失した場合に備えて、必ずやっておくべきことをまとめておきます。
スクラッチコードの保管
Google Authenticator設定時に表示された、5つのスクラッチコードは必ず保存してください。
- パスワードマネージャーに保存
- 紙に書いて金庫に保管
- 暗号化したファイルとしてクラウドに保存
など、スマホと一緒に失われない場所に保管しましょう。
QRコードの再生成
もしスクラッチコードを使い切ってしまった場合、サーバー側で再度 google-authenticator を実行すれば、新しいQRコードとスクラッチコードが生成されます。
sudo -u hoge -H google-authenticator
ただし、これをやると古いOTPは使えなくなるので、新しいQRコードをスマホで読み取り直してください。
モグラ先生:
セキュリティを高めるほど、「スマホを失くしたときに詰む」リスクも上がるから、
スクラッチコードの保管は超重要 だよ。
これを忘れると、サーバーに直接ログインして設定をやり直す羽目になるからね 🐾
これで、証明書+OTPの二要素認証によるVPN環境が完成しました。
外出先から自宅ネットワークに安全にアクセスできる環境が整ったので、ぜひ活用してみてください ⛏️

