openvpnを初めから　〜OTP(google autenticator)認証へ〜

# OpenVPNをgoogle authenticator認証で

検証環境の整備の関係でvpnでリモートから入ってこれるようにする必要が出てきました。
「VPN」というキーワードはなかなか刺激的な脆弱性も多いことから、監査の人たちにかなり注意して見られることもあり、認証系を現実的な範囲でガッチガチに固める、接続元制限などの他の対策や、無理なく続けられる運用ルールなどなど、かなり気を使って準備をしてあげないといけません。

VPNでググって、取り合えす色々できそうで、かつ歴史もあって信頼できそう、というミーハーなスタンスでOpenVPNを使うことにしました。
ちなみに、国産品をという意味だとsoftetherを使いたいところですが、、、今回の用途上はそもそもL3VPNにしたかったのと、MFAはやりようはあるけど手間が増える（別途radiusを作るなど）、ということで、今回はOpenVPNで整理してみようと思います。

で、OpenVPNを使うとして、認証はOTP(google authenticator)までは実装する前提で準備しないといけません。
素直にopenvpn、OTP、MFAなどでググると情報は沢山見つかるのですが、openvpnの設定は一通り理解している人向けの内容だったり、openvpn "AccessServer"(有償)の前提だったり、数年前の情報（対象としているOSがubuntu 18.04など古い、同じ方法を22.04で適用できない（pkgの依存で引っ張ってくるlibsslの名称が微妙に違う))などなど。
単独でゴールまでたどり着ける情報はなく、結構苦労しました。
そこら編のTipsをまとめておこうと思います。

# OpenVPNの種類とバージョン

openvpn関連は情報自体の「量」は充実してるのですが、古い情報もかなり多いです。
そこら編の目利きができるようにまず頭の整理しないといけません。。

## OpenVPNの種類

公式サイトの情報をざっとまとめると、、
https://openvpn.net/

- OpenVPN Cloud: クラウドサービス、有償
- OpenVPN Access Server: WebUIがついているOpenVPN、有償（同時接続２件までなら無償）
- OpenVPN Comunity Edition: 無償、Linuxディストリからも配布されているもの。
- OpenVPN Connect: クライアント側に導入するソフト。vpnuxなど、これ以外の選択肢もある。

こんな感じです。
WebUI操作になるAS(Access Server)と、ガリガリCUI操作になるCommunity Editionの２種類ある、ということを念頭においてググる、というのが注意でしょうか。
今回は価格面を鑑みてCommunity Editionで行くことにしたのですが、設定方法調べてググって、それっぽいのが見つかったなと思ったらASの手順でした、ということが結構あったので。

## OpenVPN Community EditionのバージョンとEOL

https://community.openvpn.net/openvpn/wiki/SupportedVersions
https://openvpn.net/community-downloads/

上記だとまだ記載されてないようなのですが、DLサイトを見るに、現時点の最新は2.5.8のようです。
Ubuntu22.04のディストリ配布版だと、2.5.5。

現時点で2.5系のEOLは提示されていませんが、過去のバージョンを見るに数年は使えそうです。
まぁ、Ubuntuのディストリ配布版をつかいますので、バックポート含みUbuntu頼りでいいですかね。

あと、version3というものもあるらしく、数年前から情報が出ているそうですが、、以前開発中らしく。
そのうち3系への切り替えが出てくるかもしれない、、ということで、一応記憶の片隅に置いときます。
https://github.com/OpenVPN/openvpn3

# OpenVPNを取り合えす理解する

なんだかんだ言っても、公式サイトのHow toページに従って、一通り流すのが一番な近道な気がします。

https://www.openvpn.jp/document/how-to/


## 「ルーティング」と「ブリッジ」

どちらもモードを使うかによって、コンフィグが結構変わります。

ルーティングは、左のように、リモートアクセスしたいセグメント（青）、サーバとクライアント間のトンネルに使うセグメント（緑）の２つがあり、OpenVPNサーバで２つのセグメントをルーティングすることになります。
下の図に入れてませんが、今回の構成ではこの青相当が複数あるので、OpenVPNサーバでルーティングする構成がわかりやすいので、こっちを採用します。

右はブリッジ構成。
こちらはリモートアクセスしたいセグメント（青）をクライアント側に張り出す感じになります。
サーバ側がちょっとややこしく、OpenVPNのトンネルで作るIFとサーバの持っているIFをブリッジで束ねて使う方法になります。
青相当が複数ある場合も、青→別青のルーティングをOpenVPNサーバにやらせればいいっちゃいいのですが、 IPtables制御がややこしくなるので、今回は使うの避けます。

![types(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587269/48fa172e-9b7d-74ee-a134-5c64064bf487.png)

## 認証方式

以下に、そのものズバリの記載があります。
https://www.openvpn.jp/document/authentication-methods/

引用するとこんな。
1. 静的鍵（Static Key）
2. 証明書認証
3. ID/パスワード認証（プラグイン認証）
4. 二要素認証（PKCS#12）

最初に引用したHowtoだと2と3の方法が紹介されてます。
1の方法も、Howtoの最初の方にリンクが入っていて、以下で紹介されてます。
https://www.openvpn.jp/document/statickey-mini-howto/

ちなみに3ですが、（私の理解が誤ってたら恥ずかしいのですが）Howtoで紹介されているのは2の証明書認証をした上で、更にID/パスワード認証をする方法になるようです。
上記のURLを見ると、2と3は別物（3では証明書を使わない）というように読めるのですが、Howtoのサイトを見ると「あえてclient-cert-not-requirdedを設定しない限りは、鍵＋ID/パスワード認証」と読める記載になっています。
。。。バージョンアップに伴ってHowtoの仕様になっていったけど、上記のページは更新されてない、、とかでしょうか。
OpenVPNの情報発信は結構そういうのが多い感じがあって、眉にツバつけて見る必要があります。

で、今回のゴールであるOTP認証は上記のどこになるかというと、4ではなく3になります。
ID/パスワード認証はpam連携での実装となるのですが、そのpam側でOTP認証の方法を取る、という動きです。

ということで、2FA、、ではあるんですけど、OpenVPN用の証明書認証＋パスワード認証＋OTP認証という、３段階・２要素認証となるわけですね。
ただし、それだけやってりゃ認証強度は十分かというとそうでもないです。
そもそも、クライアント側には実質鍵と証明書をまとめたovpnファイル（後述）を配って使わせる運用になりますし、そんなファイルはかんたんにコピーできるわけですから、証明書認証の認証強度は実質無いに等しいでしょう。
で、OTPも後述しますが、シードの情報がサーバ側のユーザディレクトリ配下に置きっぱになるので、What you haveと言いつつ複製はかんたんにできる、と。
そもそも2FAも突破される事例増えてますし、「2FAにしたから安心」と言うわけにもいかんわけで。
まぁ、他の方法との併用は別途ちゃんと考えましょう。

# 構築

今回は「ルーティング」＋「証明書認証＋ID/パスワード認証＋OTP」の構成で作っていってみます。

## インストール

サイトではDebianなどの場合はaptを使え、と書いてあるでけですが、以下でOK。

```
$ sudo apt install openvpn
```

で、起動（＋常駐化）は以下の通り。

```
$ sudo systemctl start openvpn-server@<設定ファイル名>
```

と簡単に書きましたが、頭の整理が超必要。
使い方がopenvpnサイトの情報と異なるので、要注意です。

systemctl化するにあたって、openvpn-serverのサービス情報は/lib/systemd/system/openvpn-server\@.serviceに定義されてます。
systemctlについては既習済みの前提で恐縮ですが、WorkingDirectory=/etc/openvpn/serverも設定されているのでコンフィグファイルは/etc/openvpn/server配下を探しに行く、そのコンフィグファイルの中でファイル名のみ指定で鍵や証明書の名称を書くと、それらも上記のディレクトリ直下を探しに行く、という動きをします。

ざっくりですが、こんな感じのコンフィグ書く場合は、こんな感じの配置になります。

```
$ cat /etc/openvpn/server/server.conf
...
ca ca.crt
cert server.crt                                                                      key server.key
dh dh.pem                                                                            tls-auth ta.key 0
...
```

```
$ tree /etc/openvpn/server
/etc/openvpn/server
├── ca.crt
├── dh.pem
├── server.conf
├── server.crt
├── server.key
└── ta.key
```

この状態でOpenVPNサーバとして起動する場合は、

```
$ sudo systemctl start openvpn-server@server.service
```

となります。

ググって出てくる情報だと/etc/openvpn直下にファイルをおいたり、/etc/openvpn/keysの下に鍵をおいたりするのですが、ubuntuでopenvpn入れて、systemctlで常駐化して、てすると上記のフォルダ配置になるので注意です。

逆に、クライアント側は、

```
$ sudo openvpn-client@<設定ファイル名>
```

として、設定ファイルや証明書等々は/etc/openvpn/client/に配置します。

ただし、これはクライアントもUbuntuだった場合の話。
普通クライアントはWindowsPCやMAC PCになるでしょうから、別途ovpnファイルを作成して、PCに配置したOpenVPN Connectで読み込む方法を取ることになるとは思いますけど。

## 証明書の作成

先にディレクトリの配置を紹介してしまいましたが、改めて証明書配置。

方針として「証明書認証」を使いますので、証明書の発行を行う認証局を作って、それからその認証局でサーバとクライアントの証明書を発行する、ということをしていかないといけません。
。。。やることはそんな難しくないのですが、一気にめんどくさい気がしてしまうのはなんともですね。。

### 認証局の作成

easy-rsaを使います。
ただし、Howtoサイトに記載されている方法は古いようで、Ubuntu 22.04でディストリ配布のeasy-rsaを使う場合はうつコマンドが変わります。
なんともなぁ、、、

```
$ sudo apt install easy-rsa
$ make-cadir ./myca
$ cd myca
$ ./easyrsa init-pki
$ ./easyrsa build-ca nopass
```

/usr/share/doc/easy-rsa/README.Debianに使い方が書いてあるので、それに従うことになります。
上記のコマンドで、認証局が使う鍵ファイルと証明書（./myca/pki/private/ca.key, ./mydc/pki/ca.crt)が生成されます。

で、サーバ用の鍵・証明書のペアと、クライアント用の鍵・証明書の生成はこんな。
mycaディレクトリの中で。

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

上記で、CommonNameが"server"になっている証明書・鍵のペアと、"client"になっている証明書・鍵のペアが生成されます。
証明書は./pki/issued、鍵は./pki/private/に生成されます。
Client側のCommonNameは重要で、OpenVNPサーバではクライアントが使ってきたクライアント証明書のCommonNameでクライアントを識別して、付与するIPを変えたり教える経路を変えたりします。

サーバ用の鍵である./pki/priveate/server.key、証明書である./pki/issued/server.crtと、証明書を署名したCAの証明書である./pki/ca.crtは/etc/openvpn/serverにコピー。
クライアント用の./pki/private/client.key、./pki/issued/client.crt、あと./pki/ca.crtはクライアトへ。ubuntuをクライアントにしている場合は、クライアントマシンの/etc/openvpn/clientへ。WindowsやMACの場合はovpnファイルに加工して配布（後述）。

通常、１ユーザ/１PCごとに１枚証明書を払い出すことになります。
ここらの処理は別途スクリプトにしておいたほうが良さげですね。

ちなみに、Howtoでもサーバ側でクライアントの鍵/証明書を作ってしまって、そのペアをクライアントにコピーする、という手順になってます。
本来の鍵/証明書認証のTOBEを考えると、クライアント側で鍵を作って、証明書の作成依頼をサーバ側に出して、サーバで署名して証明書を作ってクライアントに返す、となるべきです。
。。べきですが、それやるとクライアントの利用者向けの手順をあれこれ作らないといけなくなるので、かなり面倒さがますんですよね、、後日考えよう。

### dh.pen, ta.keyの作成

証明書を生成したら完了、、というわけでもないです。
サンプルコンフィグを使う場合、更にta.keyと、dh.pemを作る必要があります。

dh.pemはサーバとクライアントの間の暗号通信を行う際のDiffie–Hellman鍵交換のパラメタ設定で、サーバ側だけにおいておけばOK。
dhの鍵交換の詳細は追っかけてませんが、、パラメタはサーバ側に置いとけば、都度クライアント側にそのパラメタを渡してくれるようです。
https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%83%95%E3%82%A3%E3%83%BC%E3%83%BB%E3%83%98%E3%83%AB%E3%83%9E%E3%83%B3%E9%8D%B5%E5%85%B1%E6%9C%89
ta.keyは、サーバとクライアントで両方が同じものもっとかないといけない鍵で、HMAC認証（メッセージが改ざんされてないかチェック）で使うのだそうです。

生成方法は以下の通り。

```
$ cd myca # 上の手順のmake-cadirで作ったディレクトリ
$ ./easyrsa --keysize=4096 gen-dh # keysizeは鍵長。指定しないと、2048bitで生成。
$ cp ./pki/dh.pem /etc/openvpn/server

$ cd /etc/openvpn/server
$ openvpn --genkey secret ta.key
```

ta.keyの方はクライアントにもコピります。
ubuntuでクライアントも実装している場合は/etc/openvpn/clientに配置。

## 設定

設定ファイルのそれぞれの行の意味の理解については、腹をくくってHowtoを読んでください。
で、ルーティング/証明書認証のモードで取り合えす動く設定については、サンプルファイルを使えばOKです。

### サーバ側

```
# cat /usr/share/doc/openvpn/examples/sample-config-files/server.conf | egrep -v '^[#;]' | sed '/^$/d' > /etc/openvpn/server/server.conf
```

サンプルファイルの中の設定に簡単な説明をつけるとこんな感じ。

```
port 1194　# 待受ポートの指定。
proto udp # L4プロトコルでudpを使うか、tcpを使うかの指定。
dev tun # ルーティングモード or ブリッジモードの指定、tunはルーティング。
ca ca.crt # 証明書認証を使うときの、「認証局の」証明書。
cert server.crt # 証明書認証使うときの、サーバの証明書（ca.crtと対になる鍵で署名してもらってるもの)。
key server.key  # 証明書認証使うときの、サーバ証明書の鍵。
dh dh.pem # dhパラメタ。サーバ側にだけおけばOK。
server 10.8.0.0 255.255.255.0 # ヘルパー（簡単に書くけど、複数の設定に内部で変換されるやつ)で、サーバモードで起動する、10.8.0.0のレンジでトンネルのIPを付与する、自分は10.8.0.1を取る、クライアントには10.8.0.4〜251のどれかを付与する、等々。詳細はman openvpnしてください。
ifconfig-pool-persist /var/log/openvpn/ipp.txt # 特定のクライアント（クライアント証明書のCommonNameで特定）に特定のIPを割り当てたいときに使うリストのファイルを指定、Howtoの手順ではこれ作らないので、存在しないファイルを指定していることになる。特に問題にはならない。
keepalive 10 120 # クライアントとの死活確認感覚。
tls-auth ta.key 0 # HMAC認証するときの鍵ファイルの指定。末尾の数字は、サーバ側は0、クライアント側は1を指定せよと説明されている。
cipher AES-256-CBC # 通信内容を暗号化するときの方式。
persist-key # セキュリティ強化の観点でプロセスをnobody権限で動かすときに、権限が足りなくて鍵が読めない、にならないための設定。でもサンプル設定には当該の user nobodyの設定が入っていないので、なくても問題ない。
persist-tun # 同上
status /var/log/openvpn/openvpn-status.log # ログ保存先
verb 3 # ログすつ力のときの粒度。一番細かいのは11。なんも出さないのは0。
explicit-exit-notify 1 # UDPで接続しているときに、サーバかクライアントかどちらかが接続を切る際に、相手にその旨伝えるときの振る舞いを指定。サーバの場合は、１を指定すると、「一回切って、もう一回自分に接続トライしてくれ」、2は「別のサーバにつなぎに言ってくれ」になるらしい。
```

上記の例では証明書や鍵をパス無しで指定しているので、該当のファイル群は/opt/openvpn/serverの下にバラバラとおいておく必要があります。

で、起動はこんな。

```
$ sudo systemctl restart openvpn-server@server.service; sudo journalctl -f -u openvpn-server@server.service
```

余計なものがついているって？
。。接続が安定化するまで散々ログ見ることになりますから、ログを垂れ流すコマンドもセットにしてあります。。

#### サービス設定の注意点

この時点では顕在化しないのですが、最終的に.google_authenticatiorを作ったあたりで問題にぶつかります。
クライアントから接続する際にこんな感じのエラーが出て、接続させてくれないんですね。。

```
Jan 15 12:18:10 openvpn openvpn(pam_google_authenticator)[2907]: Failed to read "/home/user10/.google_authenticator" for "user10"
```

これググるとわかるのですが、systemdのセキュリティ機能で、ディフォでは各homeのデータにアクセスさせない、というのが入っているせいのようです。

```
$ sudo systemctl cat openvpn-server@server.service
...
[Service]
...
ProtectHome=trueKillMode=process
...
```

上記がそれ。

流石に/lib/systemd/system/の領域書き換えるわけにも行きませんから、drop inで/etc/systemd/systemでうわがいてやるか、.google_authenticatorを配置する場所を各自の/homeではなくしてしまうか、と言った対応が必要になります。

どの方法とるか決めきれてないので、申し訳ありませんがここでの記載は省略で。

### クライアント側

クライアントは、PCで実施する方法は後でまとめますので、とりあえずubuntnでクライアントの役割を担わせると、こんな感じになります。

```
# cat /usr/share/doc/openvpn/examples/sample-config-files/client.conf | egrep -v '^[#;]' | sed '/^$/d' > /etc/openvpn/client/client.conf
```

```
client # クライアントモードで動かすという宣言。これもヘルパー。
dev tun # 同、サーバ。
proto udp # 同、サーバ。
remote my-server-1 1194 # OpenVPNサーバのIPかホスト名。これは絶対書き換えないと駄目。
resolv-retry infinite # remoteをホスト名で指定した場合で、そのホスト名の名前解決に失敗したときに何回リトライするか。
nobind # 意味がよくわかりませんでした。使うポートを指定しないらしいのですが、普通クライアント側のポートはハイポートで動的に使うもんでしょうに、なんで明に書くのかなと、、
persist-key # 同、サーバ。
persist-tun # 同、サーバ。
ca ca.crt # 同、サーバ。
cert client.crt # 同、サーバ、、ではないですね。サーバ側のサーバ証明書・鍵と同じように、クライアント証明書と鍵を指定します。
key client.key # 同上。
remote-cert-tls server # 接続相手（クライアント側からすると、サーバ側）のサーバ側証明書の書式が、ちゃんとサーバ証明書用の書式になっていることを確認する、、みたいなのですが、証明書の書式というものがそもそもわかってないので、自信ないです。
tls-auth ta.key 1 # 同、サーバ。
cipher AES-256-CBC # 同、サーバ。
verb 3 # 同、サーバ。こっちはログファイルの指定はないですね。
```

で、起動はこんな。

```
$ sudo systemctl restart openvpn-client@client.service; sudo journalctl -f -u openvpn-client@client.service
```

接続に成功すれば、以下のように新しいIFがみえるはずです。
で、10.8.0.1にpingが通るはずです。
通らなかったら、journalctlで流れてくるログを丁寧に調べてください。

```
$ ip addr
    link/none 
    inet 10.8.0.6 peer 10.8.0.5/32 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::dffe:d99c:44f1:20c5/64 scope link stable-privacy 
       valid_lft forever preferred_lft forever
```

ちなみに、見慣れない10.8.0.5というIPが登場します。
サーバから配布される経路のnexthopもこのIPになります。
トンネルを貼るときはダミーみたいなIPを振って使う、、というのは漠然と認識しているのですが、詳細はよくわかってません。

## 経路制御とiptables

さて次はOTP認証、、といきたいところですが、その前に通信がちゃんと通るように整理します。
pingで10.8.0.0の間の疎通は問題なく取れたわけですが、リモート接続したい先のセグメント（青色, 10.66.0.0/24）に届かせるためにはもう一手間必要です。
また、現実的なサーバ運用をするにあたってはUFW/iptablesを稼働させることは必須なわけで、そいつらにOpenVPN通信が邪魔されないための整理というのも必要です。
順々にやってきましょう。

### 経路制御

クライアントからリモート接続先セグメントに接続するために必要な作業は、以下になります。

- （行きの通信）クライアントに、10.66.0.0/24に通信したい際は10.8.0.1(サーバ)をNexthopにしろ、という経路を学ばせる。
- （帰りの通信）リモート接続先のセグメントの機器郡に、10.8.0.0/24に通信したい際は10.66.0.1(サーバ)をNexthopにしろ、という経路を学ばせる。

前者は、openvpnのサーバ側のコンフィグで1行書き足してあげればOKです。
後者は、クライアントから機器群にパケット送った際の応答通信の帰り道の設定ですね。
これ素直にやると非常にめんどくさい。機器群Allに該当の経路を学ばせないと行けないので。
なので、サーバで10.8.0.0からの通信を10.66.0.1のIPからNATして送り出す、という構成にすることにします。
これなら、クライアントから各機器に届いた通信は、ソースIPが10.66.0.1になりますから、経路いじらなくても接続元の10.66.0.1に応答が帰ってこれて、サーバがNATの帰り通信ということで宛先を10.8.0.64に書き直して、ということができるので。
（ちなみに先の例ではクライアントのIPは10.8.0.6になってましたが、図では10.8.0.64になってます。これはサーバ側の設定でクライアントに付与するIPを絞り込めるので、それを実施した後のものと思ってください）

![nat.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587269/810cedd5-afb6-59ce-0f13-0a9a8b26f3f3.png)

で、この構成にするための設定はこんな。

まず、行きの通信のフォロー。クライアント向けの経路配信。
OpenVPNサーバの設定ファイルに以下を書き足します。

```
$ cat /etc/openvpn/server/server.conf
...
push "route 10.66.0.0 255.255.255.0"
```

本体は「route 10.66.0.0 255.255.255.0」の部分。
この設定をクライアント側に書いてもいいのですが、サーバ側に書いてその設定をクライアント側に流し込むことができて、その流し込みをするのがpush、ということのようです。

次に、帰りの通信のフォロー。OpenVPNサーバのiptablesでNATの設定を入れます。
最近のUbuntuではiptablesを直にいじらず、ufwコマンドでいじるのですが、、NATの設定は設定ファイルに書き込む方法を取るみたいです。
ufwのルールは適用順序によって小分けして定義されているようなのですが、NATの場合はbefore.rulesに書き込みます。

```
$ cat /etc/ufw/before.conf
...
*nat-F
:POSTROUTING ACCEPT
-A POSTROUTING -s 10.8.0.0/24 -o enpY -j MASQUERADE
COMMIT
```

先にNATの設定してなければ、末尾に付け足す感じです。
で、ufwを再起動。

```
$ sudo systemctl restart ufw
```

これで疎通ができます。
サーバでパケット転送させる場合「net.ipv4.ip_forward = 1」の設定が必要なのですが、Ubuntu 22.04ではあえて設定変えてなければ1になっているようですので、そこはパスで。

青のセグメントが複数ある場合は、そのセグメントに足を出しているIFのぶんだけ、ーA ...の部分の行を追記する、、でできるはず。
そこはまだ実機で試してないので、後で試しておきます。

### iptables

NATの説明ですでに登場しているので変な感じですが、改めて不要通信遮断の意味でのiptables対応。
ethXでOpenVPN用のUDP 1194（proto tcpにしているならTCP 1194、ポート変えてるなら1194ではなく指定しているポート）を開けてあげます。

コマンドで開けるならこんな。

```
$ sudo ufw allow in on ethX to any port 1194 proto udp
```

で、次にNATしてルーティングしている部分の制御ですが、試験環境ではufwの制御しっかり作り込んでないので、開ける必要あるかどうかが確認しきれてません。
これも後で試しておきます。

## ID/パスワード認証 + OTP

さてさて、ようやくOTP認証の話に入ります。
先に説明したとおり、証明書認証をやめるのではなくて、証明書認証に対してID/パスワード認証（OTP付き）を追加する、というような作業になります。

ここで使うID/パスワードはOpenVPN用に専用で作るわけではなく、OpenVPNサーバ上のアカウントとそのパスワードをそのまま使うことになります。

以下の方法だと専用のID/パスワード群を別途作ってsqliteで管理するようですが、、、
https://www.openvpn.jp/document/openvpn-sqlite-auth/
サーバのアカウントと独立した管理のほうが気分はいいのですが、そこは将来課題ということで。

ということで手順としてはサーバにアカウントを作る、そのアカウントでOTPを発行する、OpenVPNでID/パスワード/OTP認証を有効化する、となります。

### サーバのアカウント作成と、アカウントへのOTP発行

アカウント作成は流石に省きます。
useraddしてもらうとして。
ただし、ホームディレクトリが必要になるので、その作成を忘れないように。

で、OTP発行ですが、まず２つパッケージを追加しないといけません。

```
$ sudo apt install libpam-google-authenticator 
$ sudo apt install libqrencode4
```

で、対象のユーザの権限で以下を実行。

```
$ google-authenticator
```

libqrencode4が入っていると、ターミナル画面に白黒でQRコードが表示されます。
そりゃ白黒の箱並べればできるわけですが、実物見ると「そう来るか！」という感動がありました。
それをスマホなどのGoogle Authenticatorに登録します。

で、サーバ側で使うシードや設定の情報は対象ユーザのホームディレクトリに直下に.google_authenticatorとして配置されます。
。。。このファイルが盗まれたらOTPいくらでも生成できるわけですので、厳密に扱いたいところなのですが、ホームフォルダ直置きかぁ、、、
これ安全に扱うTipsがないか、これも後日調べて見ようと思います。

### OpenVPNでのOTPの有効化

ぐぐるといくつかヒットするのですが、結構設定がバラバラ、、
結論としては、クライアント側で「パスワード打ち込む欄しか無い」というケースがあるので、そのフォローをする設定と、普通にパスワードとOTPと２回打ち込めるケースの２通りの実装方法があるみたいです。

それぞれについてまとめます。

#### パスワードとOTPの数字をつなげて打ち込ませる方法の場合

クライアント側で「パスワード」の欄に、「＜パスワード＞＜OTP6桁＞」とまとめて入力してやればモジュール側でパスワードとOTPに分解してうまいこと認証してあげる、ということができるようです。
その場合の設定はこんな。

```
$ cat /etc/openvpn/server/server.conf
...
plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn
```

```
$ cat /etc/pam.d/openvpn
auth    required    pam_env.so
auth    required    pam_google_authenticator.so forward_pass
auth    required    pam_unix.so use_first_pass
account required    pam_unix.so
```

server.confの方で、/etc/pam.d/openvpnのファイルを呼び出してます。

pamの方ですが、ざっと書くと、１行目が環境変数のセット（なぜ必要なのかはわかってません、、）、２行目がOTP認証、３行目がサーバのアカウント情報使って認証となります。
４行目は認証とは別に、アカウントが期限切れてないかなどのチェックなのだそうですが、、細かくは理解しきれてません。

で、２行目の部分ですが、forward_passというオプションがついているのですが、ここはOTP部を切り取ってOTPの6ケタ数字の認証をしつつ、残りのパスワードの文字列を３行目のモジュールに渡す、ということをするそうです。
https://wiki.archlinux.jp/index.php/Google_Authenticator
https://github.com/google/google-authenticator-libpam
で、３行目の方はuse_first_passというオプションがついていて、これが前の行から受け取った文字列をパスワードとして使う、というものなのだそうです。

ということで、「パスワード」の欄に打ち込んだ「＜パスワード＞＜OTP＞」の文字列が内部でうまいこと分解されて、OTP認証とID/パスワード認証が通るようになるみたいです。

実際にクライアントで試す方法は後述でまとめますので、後回しとさせてください。

#### パスワードとOTPと、別々に入力させる方法の場合

こうなります。

```
$ cat /etc/openvpn/server/server.conf
...
plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so 
"openvpn login USERNAME password PASSWORD pin OTP"
```

```
$ cat /etc/pam.d/openvpn
auth    required    pam_env.so
auth    required    pam_unix.so
auth    required    pam_google_authenticator.so authtok_prompt=pin
account required    pam_unix.so
```

まずserver.conf側の呼び出し方から違ってきます。
クライアント側からパスワードとOTPを回収してくるのですが、それらの値を入れる変数名？を指定するようです。
ドキュメントはあるんですけど、正しく理解できてるか自信が持てない。。
https://github.com/OpenVPN/openvpn/blob/master/src/plugins/auth-pam/README.auth-pam

pam.dの方は、順序を変えていて、まずパスワード認証をして、次にOTP(pam_google_authenticator.so)をオプション付きで呼び出します。
authtok_promptはgitの説明文では Override default token prompt. とあるのですが、そのプロンプトがクライアント側から見えることはないです。

https://github.com/google/google-authenticator-libpam/blob/0b02aadc28ac261b6c7f5785d2f7f36b3e199d97/man/pam_google_authenticator.8.md

で、どうやら、ここで指定している値（上記だと"pin"）が、OTPの変数名の指定として使われるみたいで、server.conf側の記載と対応してないと行けないみたいです。
（試しに片方の"pin"を別の文字に書き換えると、認証通らなくなりました）

。。。ちゃんと動いているっぽいのですが、なんか気持ちの良くない決着だなぁ、、
まぁまた後日調べて見ようと思います。

## OTPを使う場合のクライアントの設定

Ubuntuで実施する場合は、client.confに付け足しが必要です。
これも、上記の２パターンで方法が変わります。

### パスワードとOTPの数字をつなげて打ち込ませる方法の場合

```
$ cat /etc/openvpn/client/client.conf
...
auth-user-pass
```

上記を追加します。
ただし、systemctlは使えなくなります。
対話でユーザ名とかOTPとか打ち込めないので。

```
$ cd /etc/openvpn/client
$ sudo openvpn ./client.conf
```

という感じで接続かけることになります。

### パスワードとOTPと、別々に入力させる方法の場合

```
$ cat /etc/openvpn/client/client.conf
...
auth-user-pass
static-challenge pin 1
```

１行増えます。
static-challengeというのがpinを入力せるための設定です。
引数が２つで、１つ目はコンソールに表示させる文字列、２つ目は入力した数字を表示する(1)か、*で潰すか(0)です。
１つ目の引数は"pin"にしていますが、これは上記のサーバ側の設定とは全然関係なくて、全然別の文字列にしても全く問題ありません。

# Windows/MACからOpenVPNを使う場合（=ovpnファイルの作り方）

とりあえずの動作確認はubuntu使ったほうが楽なので、上記ではubuntuにクライアントの役割担わせてました。
で、実際に使うときはWindowsPCやMACから接続しますので、それ用の方法を用意しないといけません。

具体的には、OpenVPN ConnectかなにかのクライアントソフトをPCに導入して、それにovpnファイルを読み込ませる、という方法をとなります。

## ovpnファイルの作り方

OpenVPNのサイトを探すとsacliというツールを使う方法が紹介されているのですが、これはOpenVPN Access Serverの同梱物らしく、Community側には入ってません。
じゃあ別のツールを、、とする必要はなくて、Ubuntuのときに/etc/openvpn/client/client.confとして使っていたファイルをちょっと加工するだけで作れます。

基本的な考え方としては、client.confの中から呼び出してた「ca ca.csr」などの証明書と鍵の情報をclient.conf自体に埋め込んでしまうだけです。

```
ca ca.crt # 削除
->
<ca> # 追加
<ca.crtの中身そのまんまコピー>
</ca>
```

これと同じことを以下でもやります。
- cert client.crt
- key client.key
- tls-auth ta.key
- ca ca.crt # 同、サーバ。

あと、以下2行も追加。
tls-authの第２引数が上記の貴方だとかけなくなるので、別の記法で書く、ということのようです。

```
tls-client
key-direction 1
```

これで、ファイルの拡張子を.ovpnにして、保存すればOK。
これをPCに配ります。

# まとめ

とりあえず自分で試してみて、動いた方法をまとめました。
いろいろなサイトの情報切り貼りして、自分で試して、、で、結構手間かかりました。
まだまだ荒っぽい箇所があるので、丁寧に整理しないといけないですが、今日はこんなところで。