Easy-RSA3を利用したOpenVPNサーバーの構築とクライアント証明書の配布

# はじめに

:::note
【2024/11/02】Ubuntu 24.04を前提として、easy-rsa v3.2.xでの設定例に変更しました。
:::

VPNは比較的低レイヤーで動作する仕組みであるため、侵害されると影響が広く深刻になりがちです。

しかし正しく利用できれば比較的安全で強力なツールでもあります。

OpenVPN等のVPN機能を利用する際、共通鍵を利用する方法は不特定多数のアクセスを許す事にもつながり、特にVPNサービスを提供する機器がインターネットに直接接続している場合には利用するべきではありません。

OpenVPNであればeasy-rsaを使うことで簡単に公開鍵証明書ベースの認証方式を導入することができます。

1名で利用する場合でも共通鍵を利用するOpenVPNサーバーは構築しないようにしましょう。

危険性は共通鍵方式を単独で利用する点にあります。

ヤマハのVPNルーターはよく使われていてIPsecの共通鍵での利用が主だと思います。IPsec双方で相手方のIPアドレスを固定しなければ危険で、ヤマハのVPNルーターでは相手方のIPアドレスが必須の入力項目になっていたはずです。

このような共通鍵方式によるVPNはIPアドレスが固定されている拠点間の接続に最適です。
OpenVPNと公開鍵方式の組み合せは、ユーザーが自宅や出張先など不特定の場所から接続するユースケースに最適です。

一方でeasy-rsaによる独自証明書の作成には、いくつかのリスクがあります。

1. 証明書が適切な利用者にのみ提供されるように、管理・運用を適切に行うこと
2. 利用者の退職などに合わせてCRLを更新・配布すること

使い易いインタフェースを供えた証明書の作成・再発行・CRL管理などを行うアプリケーションや、企業内CA局の運用サービスが提供されていますので、比較的大きな規模であれば、何かしらのアプリケーション・サービスを利用することをお勧めします。

以下は実験的・個人的な環境構築のため独自のCA局を構築しなければならない場合にのみ参考にしてください。

# 過去の類似の記事

WebサーバーでのTLS化のためのEasy-RSA3の利用については下記の記事の中で触れています。

* [Harborのコンパイルとデバッグ with easy-rsa3 - https://qiita.com/YasuhiroABE/items/ec90c2bf50f2724fd340](https://qiita.com/YasuhiroABE/items/ec90c2bf50f2724fd340)

Webサーバー用のTLS公開鍵はclientファイルとCAの公開鍵ファイルだけあれば良かったのですが、今回は、OpenVPNで利用するための手順についてメモを残します。

# 環境

## サーバー#1

* CentOS 7

## サーバー#2

* Ubuntu 24.02.1 (ホスト名: openvpn.example.com)
* openvpn (2.6.12)

## クライアント環境

* Ubuntu 22.04.05 
* openvpn (2.5.11)

easyrsaはこちらの環境で実行しています。


# 基本的な設定

過去の記事から設定を転載します。

```bash:easy-rsa-v3によるCA局の準備

$ git clone https://github.com/OpenVPN/easy-rsa.git
$ cd easy-rsa
## その時点で最新のタグを確認し、利用する
$ git tag
## タグ名 v3.0.7 を利用する場合、"my_v3.2.1"の文字列部分は任意に変更可能
$ git checkout refs/tags/v3.2.1 -b my_v3.2.1
$ cd ..
$ easy-rsa/easyrsa3/easyrsa init-pki
$ easy-rsa/easyrsa3/easyrsa build-ca
## Passphraseには出来るだけ長い文字列を指定、CN(Common Name)には対象となるドメインが分かる名称を指定する - e.g. "EXAMPLE.COM Easy-RSA CA"
$ ls 
easy-rsa/  pki/
```

``.../easyrsa build-ca``を実行して最後に``Build-ca completed successfully.``のメッセージが表示され、プロンプトが戻ってくれば正常にCA関連のファイルが生成されています。

## dh.pemファイルの生成とhmac共通鍵の作成

ここで、dh.pemファイルを生成しておきます。

```bash:dh.pemファイルの生成
$ easy-rsa/easyrsa3/easyrsa gen-dh
```

続いて、hmac共通鍵ファイル(ta.key)を生成します。openvpn.jpにある2.2系列のドキュメントで説明されている方法とはオプション名が違うので注意してください。

```bash:tls-auth用のta.keyファイルの作成
## openvpnが稼動しているUbuntu 18.04上でta.keyを生成する際のコマンドライン
$ openvpn --genkey --secret ta.key
## Ubuntu 22.04上のOpenVPN 2.5では上記のコマンドがdeprecatedとなっているので、将来エラーになった時には次のように実行してください
$ openvpn --genkey secret ta.key
$ ls -F
easy-rsa/  pki/  ta.key
```

## サーバー証明書の生成

ここではCA局の秘密鍵が格納されている同一pki/ディレクトリを利用して、サーバーのreqファイル生成と署名処理を行っています。本来は別々にgit cloneした、easy-rsa/ディレクトリを利用して、reqファイルの作成と署名処理を別サーバーに分離するべきですが、ここではそのような処理を行っていない点に注意してください。

:::note
手順上の``opnevpn.example.com``は実際のサーバー名に変更してください。
:::

```bash:鍵ファイルを生成し、CA局に提出する署名要求ファイル(CSR)を作成する
## easy-rsa/, pki/ が存在するディレクトリで作業を実施
$ easy-rsa/easyrsa3/easyrsa gen-req openvpn.example.com
...
Keypair and certificate request completed. Your files are:
req: .../pki/reqs/openvpn.example.com.req
key: .../pki/private/openvpn.example.com.key
```

続けてCA局側の立場でサーバー証明書を生成します

```bash:サーバー証明書の生成
$ easy-rsa/easyrsa3/easyrsa sign-req server openvpn.example.com
$ find pki -type f | grep openvpn.example.com
pki/issued/openvpn.example.com.crt
pki/private/openvpn.example.com.key
pki/inline/private/openvpn.example.com.inline
pki/reqs/openvpn.example.com.req
```

client証明書を発行する時には引数を"server"を"client"に変更するだけで、基本的な操作は同じです。

:::note warn
OpenVPN用の証明書についてはいまのところ、この手順で問題ありませんが、golangなどTLSクライアントによってはCommon Name(CN)の使用が非推奨となっている場合があります。([RFC2818日本語対訳](https://tex2e.github.io/rfc-translater/html/rfc2818.html))

OpenVPN以外の用途でeasyrsaを利用する場合には、gen-reqとsign-req server時に``--san="DNS:openvpn.example.com"``のようなオプションを追加することで対応可能です。
:::

# OpenVPNサーバーのセットアップ

CentOS上で、EPELのopenvpnパッケージを導入します。
EPELの導入などはここでは説明しません。

```bash:
$ sudo yum install openvpn
```

RedHat上ではパッケージによって空の/etc/openvpn/server/が作成されているので、server.conf以外のファイルを配置していきます。

```bash:pki/以下からサーバーにコピーするファイル
## scpでコピーしていますが、openvpn.example.com上の/etc/openvpn/serverに書き込み権限が必要です
$ scp ta.key openvpn.example.com:/etc/openvpn/server/ta.key
$ scp pki/ca.crt openvpn.example.com:/etc/openvpn/server/ca.crt
$ scp pki/dh.pem openvpn.example.com:/etc/openvpn/server/dh.pem
$ scp pki/issued/openvpn.example.com.crt openvpn.example.com:/etc/openvpn/server/openvpn.example.com.crt
## reqファイルを作成する時に設定したkeyファイルのパスワードについては解除します
$ mkdir server
$ openssl rsa < pki/private/openvpn.example.com.key > server/openvpn.example.com.nopass.key
$ scp openvpn.example.com.nopass.key openvpn.example.com:/etc/openvpn/server/openvpn.example.com.nopass.key
```

インターネットからの接続は別のVPN製品で別途に行っており、今回の説明の範囲外です。Gatewayに指定する192.168.1.1はローカルネット上でopenvpnを走らせているサーバーです。この192.168.1.1のマシンから別NICで192.168.100.0/24を構築しているので、このネットワークにアクセスできるようにrouting情報をクライアントに通知します。DNSサーバーは内部ネットワークの状態を192.168.1.1上のdnsmasqで管理しているため、これを参照するようにします。

```text:/etc/openvpn/server/server.conf
port 1194
proto udp
tls-server
## $ openvpn --show-ciphers
cipher AES-256-GCM
ncp-ciphers AES-256-GCM
tls-version-min 1.2
tls-cipher TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
dev tun
tls-auth ta.key
ca ca.crt
cert openvpn.example.com.crt
key openvpn.example.com.nopass.key
dh dh.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
keepalive 10 120
compress
persist-key
persist-tun
status openvpn-status.log
verb 3

## network definition
## Gateway: 192.168.1.1 (openvpn.example.com)
## Internal Network: 192.168.100.0/24
topology subnet
push "route 192.168.1.1 255.255.255.255 net_gateway"
push "route 192.168.100.0 255.255.255.0"
```

実際のopenvpnサーバーへのファイル配布は、Ansibleを利用しています。設定ファイルの配布が終了したら、systemctlでサーバープロセスの設定を行います。

## firewall-cmdによるoenvpn設定の変更

firewalldが稼動していると思われるので、"service"に"openvpn"を追加することが必要です。
1194/udpポート以外で稼動させている場合には、別途そのポート番号を指定して追加することが必要になります。

```bash:firewall-cmdによるopenvpnサービスの追加例
$ firewall-cmd --add-service=openvpn --zone=public --permanent
```

どのzoneを利用しているかは、```$ sudo firewall-cmd --get-active-zones``などで自身の環境をよく確認してください。

## systemctlによるopenvpnサービスの設定

CentOS上でopenvpnのパッケージを導入していれば、/usr/share/doc/openvpn-2.4.8/README.systemd にsystemctlの利用についてガイドがあります。

/etc/openvpn/server/server.conf が準備されていれば、次のようなコマンドが有効になります。

```bash
## プロセスを一時的に稼動させ、異常終了などしないか確認します
$ sudo systemctl start openvpn-server@server
## 問題がなければ、常時稼動するようにします
$ sudo systemctl enable openvpn-server@server
```

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

もしUbuntuを利用していれば、.confファイルの配置場所とサービス名が異なる点に注意してください。
/etc/openvpn/server.confを配置し、systemctlでは、"openvpn@server.service" を指定するといった点で違いがあります。

# クライアント証明書の生成とログインに必要なファイルの配布

OpenVPNにログインを許可するために証明書を発行する際には、サーバーと同様に手続きを行います。
Windows/MacOS/Linux/Android/iOSを念頭に必要なファイルをZIPアーカイブにまとめて配布します。

## クライアント用設定ファイルの準備

ここではスクリプトでホスト名などを指定するので、テンプレートを作成します。
easy-rsaやpkiディレクトリのある場所で、clientディレクトリを作成(mkdir client)し、作業をまとめます。

```bash:clientディレクトリの作成
$ ls -F
easy-rsa/ pki/ ta.key
$ mkdir client
$ ls -F
client/ easy-rsa/ pki/ ta.key
```

OpenVPNのサーバーIP(192.168.1.1)は、適切なIPアドレスやホスト名に変更してください。

```bash:client/client.ovpn.tmpl
client
tls-auth __MY_DIR__/ta.key
dev tun
proto udp
remote 192.168.1.1 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ca __MY_DIR__/ca.crt
cert __MY_DIR__/__MY_CERT__
key __MY_DIR__/__MY_KEY__
dh __MY_DIR__/dh.pem
compress
verb 3
```

ユニークなID"user01"に対してクライアント証明書を発行するスクリプトを次のように作成します。
なお内部でさらに2つのrubyスクリプトを呼んでいます。あ

```bash:create-client.sh
#!/bin/bash

BASEDIR="$(dirname $0)"
CLIENT_TOPDIR="${BASEDIR}/client"
PKI_TOPDIR="${BASEDIR}/pki"
PKI_ISSUED_DIRPATH="${PKI_TOPDIR}/issued"
PKI_ISSUED_SUFFIX="crt"
PKI_PRIVATE_DIRPATH="${PKI_TOPDIR}/private"
PKI_PRIVATE_SUFFIX="key"
PKI_REQS_DIRPATH="${PKI_TOPDIR}/reqs"
PKI_REQS_SUFFIX="req"
PKI_CA_FILENAME="ca.crt"
PKI_TA_FILENAME="ta.key"
PKI_CLIENT_TEMPLATE_FILE="${CLIENT_TOPDIR}/client.ovpn.tmpl"
PKI_CLIENT_FILENAME="client.ovpn"


function usage {
    echo ""
    echo "[Usage] $  $0 <client name>"
    echo ""
    echo "    e.g. $ $0 user01"
    echo ""
    exit
}

## check the number of arguments
test "$#" -ne 1 && usage

OUTPUT_DIRPATH="${CLIENT_TOPDIR}/${1}"
OUTPUT_CERT_FILENAME="${1}.${PKI_ISSUED_SUFFIX}"
OUTPUT_KEY_FILENAME="${1}.${PKI_PRIVATE_SUFFIX}"
OUTPUT_FILELIST="${PKI_ISSUED_DIRPATH}/${OUTPUT_CERT_FILENAME}
                ${PKI_PRIVATE_DIRPATH}/${OUTPUT_KEY_FILENAME}
                ${PKI_TOPDIR}/${PKI_CA_FILENAME}
                ${BASEDIR}/${PKI_TA_FILENAME}"

## prepare the output directory
if test -d "${OUTPUT_DIRPATH}"; then
    echo "[error] the output directory already exists!"
    usage
fi
mkdir -p "${OUTPUT_DIRPATH}"

## generate req and sign certs
CLIENT_SECRET="$(openssl rand -hex 8)"

"${BASEDIR}/client/gen-req.rb" "${1}" "${CLIENT_SECRET}"
if test "$?" != "0"; then
    echo "[error] failed to execute gen-req"
    exit
fi
"${BASEDIR}/client/sign-req.rb" "client" "${1}"
if test "$?" != "0"; then
    echo "[error] failed to execute sign-req"
    exit
fi

## copy generated file
for file in ${OUTPUT_FILELIST}
do  
    echo "copying... ${file}"
    cp "${file}" "${OUTPUT_DIRPATH}"
done

## copy the ovpn conf file
sed -e "s!__MY_CERT__!${OUTPUT_CERT_FILENAME}!" \
    -e "s!__MY_KEY__!${OUTPUT_KEY_FILENAME}!" \
    -e "s!__MY_DIR__!${OUTPUT_DIRPATH}!" \
    "${PKI_CLIENT_TEMPLATE_FILE}" > "${OUTPUT_DIRPATH}/${PKI_CLIENT_FILENAME}"

## create zip file
( cd "${CLIENT_TOPDIR}" ; zip -r "${1}.zip" "${1}" )

echo ""
echo "[IMPORTANT] Your KEY Passphrase is : ${CLIENT_SECRET}"
echo ""
```

このcreate-client.shの内部では自動的にkeyファイルのpassphraseを生成し、クライアント証明書を生成するためにeasy-rsaをexpect.rbでwrapする2つのrubyスクリプトを呼び出しています。


```ruby:client/gen-req.rb
#!/bin/env ruby
##
## Usage: gen-req.rb <filename_base>
##
require "pty"
require "expect"

## debug mode is on if true
# $expect_verbose = true

PTY.spawn("easy-rsa/easyrsa3/easyrsa gen-req #{ARGV[0]}") do |r,w,pid|
  w.sync = true
  r.expect(/Enter PEM pass phrase:/) { |result|
    w.puts "#{ARGV[1]}"
  }
  r.expect(/Verifying - Enter PEM pass phrase:/) { |result|
    w.puts "#{ARGV[1]}"
  }

  r.expect(/Common Name .*\]:/) {|result|
    w.puts "#{ARGV[0]}"
  }
  
  r.expect(/key:.*.key./) { |result|
    puts result
  }
end
```

```ruby:client/sign-req.rb
#!/bin/env ruby
##
## Usage: sign-req.rb [client|server] <filename_base>
##
require "pty"
require "expect"
require "io/console"

## debug mode is on if true
# $expect_verbose = true

## save CA passphrase
capass = IO::console.getpass "Please enter the CA's passphrase: "

PTY.spawn("easy-rsa/easyrsa3/easyrsa sign-req #{ARGV[0]} #{ARGV[1]}") do |r,w,pid|
  w.sync = true
  r.expect(/ Confirm requested details: /) { |result|
    w.puts "yes"
  }
  r.expect(/.*ca.key:/) { |result|
    w.puts "#{capass}"
  }
  
  r.expect(/*#{ARGV[1]}.crt./) { |result|
    puts result
  }
end
```

:::note
EasyRsaの出力メッセージがv3.2.1で変化しているので、それに合わせて変更しています。
:::

# ディレクトリ構造

テンプレートファイルやスクリプトを配置したので少し複雑になりましたが、最終的に次のようなディレクトリ構造になりました。

```bash:treeコマンドの出力
.
├── client
│   ├── client.ovpn.tmpl
│   ├── gen-req.rb
│   └── sign-req.rb
├── create-client.sh
├── easy-rsa
│   └── *
├── pki
│   └── *
├── server
│   └── openvpn.example.com.nopass.key
└── ta.key
```

# クライアント証明書の発行手順

CA局のパスフレーズはコマンドの引数に指定することもできますが、historyやpsコマンドから漏洩してしまう可能性があるため実行時に入力するスタイルを採用しています。

```bash:クライアント証明書"user01"を発行する手順
$ ./create-client.sh user01
```

画面は次のように流れていきます。

```text:
## "Please enter the CA's passphrase: "に続いてCA局のパスフレーズを入力してください

Keypair and certificate request completed. Your files are:
req: .../pki/reqs/user01.req
key: .../pki/private/user01.key
Please enter the CA's passphrase: 
Using SSL: openssl OpenSSL 1.1.1  11 Sep 2018


You are about to sign the following certificate.
Please check over the details shown below for accuracy. Note that this request
has not been cryptographically verified. Please be sure it came from a trusted
source or that you have verified the request checksum with the sender.

Request subject, to be signed as a client certificate for 825 days:

subject=
    commonName                = user01


Type the word 'yes' to continue, or any other input to abort.
  Confirm request details: yes
Using configuration from .../pki/easy-rsa-22476.c8z4Q6/tmp.1uAR03
Enter pass phrase for .../pki/private/ca.key:
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'user01'
Certificate is to be certified until Jul 10 09:06:48 2022 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

Certificate created at: .../pki/issued/user01.crt
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'user01'
Certificate is to be certified until Jul 10 09:06:48 2022 GMT (825 days)

Write out database with 1 new entries
Data Base Updated

Certificate created at: .../pki/issued/user01.crt
copying... ./pki/issued/user01.crt
copying... ./pki/private/user01.key
copying... ./pki/ca.crt
copying... ./ta.key
  adding: user01/ (stored 0%)
  adding: user01/client.ovpn (deflated 28%)
  adding: user01/ta.key (deflated 40%)
  adding: user01/user01.crt (deflated 45%)
  adding: user01/user01.key (deflated 24%)
  adding: user01/ca.crt (deflated 25%)

[IMPORTANT] Your KEY Passphrase is : d8e5925dcb2a9885
```

最終的に **client/user01.zip** ファイルが生成されています。
zipファイルにはパスワードは設定していません。

## Windows10でのOpenVPNクライアントと証明書の利用

[OpenVPN Communityの公式サイト](https://openvpn.net/community-downloads/)から、最新のクライアントプログラムをダウンロードし、インストールします。

作成したZIPファイル(user01.zip)を入手し、Windows10のローカルディスクにコピーします。

**C:\Windows\Program Files\OpenVPN\config** の下か、各ユーザーのホームディレクトリにある**\OpenVPN\config** の下にディレクトリを展開します。

自動的にフォルダの中にある.ovpnファイルが認識されるので、特別の操作をせずに、OpenVPNを起動するとインジケータ領域にあるアイコンをクリックし、メニューを表示すると、フォルダ名がメニューリストに表示されるので、"接続"を選択することで

## macOSでのOpenVPNとクライアント証明書の利用

1. あらかじめ、[Tunnelblickの公式サイト](https://tunnelblick.net/)からクライアントアプリをダウンロードし、インストールします。
2. 画面上部のトンネルアイコンのメニューから"VPNの詳細”を選択します。
3. ZIPファイルを展開し、”接続先”の管理画面の左側に.ovpnファイルをドロップダウンします。

## Ubuntu 18.04/20.04/22.04(LTS)でのOpenVPNとクライアント証明書の利用

1. openvpnパッケージを導入します。(``$ sudo apt install openvpn``)
2. /etc/openvpn/でZIPファイルを展開します。
3. ディレクトリに含まれる.ovpnファイルを/etc/openvpn/直下に移動し、suffixをconfに変更します。
4. 移動した.confファイルを編集し、各ファイルへのパスを追加します。
5. systemctlを利用し、openvpnサービスを開始します。

```bash:一連の作業の流れ
$ sudo apt install openvpn
$ cd /etc/openvpn/
$ sudo unzip ~somewhere~/user01.zip
$ sudo cp /etc/openvpn/user01/client.ovpn /etc/openvpn/user01.conf
$ sudo vi user01.conf
## ta.key .crt, .key, ca.crt, dh.pem ファイルのパスを変更
$ sudo systemctl start openvpn@user01.service
```

実際の変更内容は次のようになります。

```diff:diff出力
$ sudo diff -u user01/client.ovpn user01.conf 
--- user01/client.ovpn        2020-04-06 21:42:02.904140715 +0900
+++ user01.conf       2020-04-07 09:38:59.816535055 +0900
@@ -1,5 +1,5 @@
 client
-tls-auth ta.key
+tls-auth user01/ta.key
 dev tun
 proto udp
 remote 192.168.1.1 1194
@@ -7,9 +7,9 @@
 nobind
 persist-key
 persist-tun
-ca ca.crt
-cert user01.crt
-key use0r1.key
+ca user01/ca.crt
+cert user01/user01.crt
+key user01/user01.key
 compress
 verb 3
```

## CentOS 7でのOpenVPNとクライアント証明書の利用

ZIPファイルを/etc/openvpn/に展開し、.ovpnファイルを.confファイルに名前を変更します。
パッケージの同様はサーバーと同様です。あらかじめ、EPELを有効にして、openvpnパッケージを導入してください。

```bash:CentOS7での作業概要
$ cd /etc/openvpn
$ sudo unzip ~somewhere~/user01.zip
$ sudo mv user01/client.ovpn user01/client.conf
$ sudo systemctl start openvpn-user01@client.service
```

## iOSデバイスでのOpenVPNとクライアント証明書の利用

あらかじめOpenVPNが配布している公式クライアント(OpenVPN Connect by OpenVPN Technologies)を導入します。

【macOSを利用する場合】
1. MacにZIPファイルをコピーし、展開しておきます。
2. MacにiOSデバイスを有線(USBケーブル)で接続します。
3. ファインダー左のメニューから、iPadを選択し、ファイルタブを選択し、OpenVPN Connectアプリを選択します。
4. ZIPファイルを展開したフォルダーに含まれるファイルを全てOpenVPN Connectアプリへコピーします。

【Windows10を利用する場合】
1. Microsoft Storeアプリを起動し、iTunesをダウンロードします。
2. iTunesアプリを起動し、メニュー下段のiOSデバイスのアイコンをクリックします。
3. 左側のメニューからファイル共有を選択し、OpenVPNアプリをクリックします。
4. ZIPファイルを展開してできたフォルダを、OpenVPNの書類へコピーします。

【macOS/Windows10共通の手順】
1. iOSデバイス側で、OpenVPNアプリを起動し、コピーしたフォルダに含まれる.ovpnファイルを選択します。
2. 必要に応じてSave Private Key Passwordオプションを選択し、Keyファイルのパスワードを入力します。

## AndroidデバイスでのOpenVPNとクライアント証明書の利用

まずZip形式などでファイルを転送し、Downloadフォルダなど書き込み可能な場所に展開します。
その次にOpenVPNクライアントに設定ファイルとクライアント証明書等一式を読み込ませます。

### OneDriveなどのファイル共有アプリの利用
PC(Windows/macOS)などと共通で利用できるファイル共有アプリを利用し、ZIPファイルをAndroidデバイスに転送します。

1. GoogleのPlayストアから、あらかじめ公式のOpenVPNアプリ(OpenVPN Connect by OpenVPN)をインストールしておきます。
2. OneDriveなどからZIPファイルを選択し、ローカルストレージ(SDカードを含む)のDownloadsディレクトリにダウンロードします。
3. Google FilesアプリなどからダウンロードしたZIPファイルをタップし、解凍します。

### Zipファイルを本体に保存した後の操作手順
1. OpenVPNアプリを起動し、"+"ボタンをクリックします。
2. Downloadディレクトリを選択し、展開したZIPファイルの内容から.ovpnファイルを選択します。
3. "Import"ボタンをクリックします。
4. 必要に応じて"Save Private Key Password"をチェックし、パスワードを入力します。
5. 右上の"Add"ボタンをクリックし、保存します。

基本的には.ovpnファイルを指定すれば、関連するkey,certファイルなどは自動的に取り込まれます。

# 考慮点

今回の作業の中で、次のような課題に遭遇しました。

1. Tunnelblickが.ovpnファイル中のcomp-lzo設定に警告を出したため、compress設定にサーバー側を含めて変更した点
2. UbuntuのOpenVPNではsystemctlとの統合のため、/etc/openvpn/直下に、.ovpnファイルのsuffixをconfに変更して配置しなければいけない点。又、CentOSで行なった/etc/openvpn/server/server.confのような配置方法はNG。(CentOSではサブディレクトリに.confファイルを配置する必要があるが、Ubuntu(Debian系)ではサブディレクトリに配置した.confファイルは認識されない)


バージョンアップなどでサポートされる暗号化形式などは変化していくと思われます。

# UbuntuをOpenVPNサーバーとして利用する際のufw, iptables設定について

Ubuntu 22.04をOpenVPNサーバーとして構成する際に、ufw, iptables をansibleを通して設定しています。
Firewall関連の設定についてはCentOSの方が簡単だと思いますが、Ubuntuであれば細かな指定を起こなうことができて(必要があって)、勉強にはなりそうです。

この時に注意が必要なのは次のような点です。トポロジーが違うと設定箇所も変化しますので、通信を良くイメージする必要があります。

* ufwのデフォルト設定では、FORWARDのポリシーはDROPに指定されるため、内側から外に出ていくルールも設定する必要がある点
* OpenVPNのトンネルデバイスに割り当てられているIPアドレス(10.8.x.x)との通信を適切にMASQUERADEする点

多くの説明では、/etc/default/ufwを操作してFORWARDのデフォルトポリシーをACCEPTにするケースが多いと思いますし、これまでは自分もFORWARDルールは作成しないできました。

今回はINPUT, FORWARDのデフォルトポリシーをDROPにしたままで操作をしていきます。
OUTPUTのポリシーはデフォルトのままACCEPTにしています。

2022/08/30にサーバーを構築した際の手順をまとめておきます。
システムの設定を一元的に管理するためのAnsible RoleをAnsible Galaxyに登録しているので、このツールを使って説明していきます。

## システムの構成

192.168.100.1/24を通して、OpenVPNサーバーの内側にある192.168.200.0/24ネットワークに接続するよう設定していきます。

OpenVPNサーバーの他に、NginxサーバーをReverse Proxyサーバーとして、192.168.200.0/24のネットワークにあるKubernetes/Ingressサーバーなどに接続をしてサービスを提供する構成になっています。

また192.168.200.0/24ネットワーク内部へのDNS, DHCPdサービスを提供するために、dnsmasqのサーバーとしても動作します。




## 準備作業

```bash:準備作業
## ansibleを稼動させるための基本的なテンプレートのダウンロード
$ git clone https://github.com/YasuhiroABE/myansible-playbook-skelton.git
$ cd myansible-playbook-skelton

## ansible.cfg, hostsファイルの修正

## 稼動確認
$ make ping

## Galaxy roleのダウンロード
$ make setup-roles
```

ここまでで、v1.0.9の roles/YasuhiroABE.myfavorite-settings/ がダウンロードされているはずで、これを前提に説明していきます。

## playbook/default.yaml ファイル

ansible-playbookに動作を指示するYAMLファイルは以下のようになっています。

```yaml:playbook/default.yamlファイル
---
- hosts: all
  vars:
    mfts_hostname: "ovpngw"
    mfts_sshd_listen_ipaddr: 192.168.200.1
    mfts_sysctl_rules:
      - { name: net.ipv4.ip_forward, value: 1 }
    mfts_additional_packages:
      - iptables-persistent
      - make
      - git
      - openvpn
      - dnsmasq
    ## ufw and iptables firewall settings
    mfts_ufw_enable: True
    mfts_ufw_enable_logging: True
    mfts_ufw_allow_rules:
      - { type: "allow", from_ip: "192.168.200.0/24" }
      - { type: "allow", from_ip: "10.8.1.0/24" }
    mfts_ufw_service_rules:
      - { type: "allow", port: "22", from_ip: "192.168.200.0/24", to_ip: "192.168.200.1/32" }
      - { type: "allow", port: "53", from_ip: "192.168.200.0/24", to_ip: "192.168.200.1/32" }
      - { type: "allow", port: "53", from_ip: "192.168.200.0/24", to_ip: "192.168.200.1/32", proto: "udp" }
      - { type: "allow", port: "80", from_ip: "0.0.0.0/0", to_ip: "192.168.100.1/32" }
      - { type: "allow", port: "123", from_ip: "192.168.200.0/24", to_ip: "192.168.200.1/32", proto: "udp" }
      - { type: "allow", port: "443", from_ip: "0.0.0.0/0", to_ip: "192.168.100.1/32" }
      - { type: "allow", port: "1194", from_ip: "192.168.100.0/24", to_ip: "192.168.100.1/32", proto: "udp" }
    mfts_ufw_broadcast_rules:
      - { type: "allow", port: "67", interface: "enp3s0", direction: "in", proto: "udp" }
    mfts_ufw_incoming_forward_rules:
      - { type: "allow", to_ip: "192.168.200.0/24", to_port: "22" }
      - { type: "allow", to_ip: "0.0.0.0/0", to_port: "80" }
      - { type: "allow", to_ip: "0.0.0.0/0", to_port: "443" }
      - { type: "allow", to_ip: "192.168.100.2", to_port: "636" } ## ldap server
    mfts_ufw_outgoing_forward_rules:
      - { type: "allow", from_ip: "192.168.200.0/24", from_port: "22" }
      - { type: "allow", from_ip: "0.0.0.0/0", from_port: "80" }
      - { type: "allow", from_ip: "0.0.0.0/0", from_port: "443" }
      - { type: "allow", from_ip: "192.168.100.2", from_port: "636" } ## ldap server
    mfts_iptables_masquerade_rules:
      - { interface: "enp1s0", source: "192.168.200.0/24" }
      - { interface: "enp3s0", source: "10.8.1.0/24" }
    mfts_setup_directory:
      - { path: "/etc/openvpn/ovpngw", state: "directory", owner: "root", group: "root", mode: "0750" }
    mfts_copy_files:
      ## openvpn
      - { src: "{{ inventory_dir }}/files/openvpn/ca.crt", dest: "/etc/openvpn/ovpngw/ca.crt", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/openvpn/dh.pem", dest: "/etc/openvpn/ovpngw/dh.pem", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/openvpn/ipp.txt", dest: "/etc/openvpn/ovpngw/ipp.txt", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/openvpn/ovpngw.example.com.crt", dest: "/etc/openvpn/ovpngw/ovpngw.example.com.crt", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/openvpn/ovpngw.example.com.nopass.key", dest: "/etc/openvpn/ovpngw/ovpngw.example.com.nopass.key", owner: "root", group: "root", mode: "0600" }
      - { src: "{{ inventory_dir }}/files/openvpn/ta.key", dest: "/etc/openvpn/ovpngw/ta.key", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/openvpn/ovpngw.conf", dest: "/etc/openvpn/ovpngw.conf", owner: "root", group: "root", mode: "0644" }
      ## dnsmasq
      - { src: "{{ inventory_dir }}/files/dnsmasq/default.conf", dest: "/etc/dnsmasq.d/default.conf", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/dnsmasq/dnsmasq.hosts", dest: "/etc/dnsmasq.hosts", owner: "root", group: "root", mode: "0644" }
      - { src: "{{ inventory_dir }}/files/dnsmasq/dnsmasq.resolv.conf", dest: "/etc/dnsmasq.resolv.conf", owner: "root", group: "root", mode: "0644" }
    mfts_lineinfile_after_copyfiles:
      - { path: "/etc/default/ufw", regexp: "^IPV6=", line: "IPV6=no" }
      - { path: "/usr/lib/systemd/system/systemd-networkd-wait-online.service", regexp: "^ExecStart=/lib/systemd/systemd-networkd-wait-online", line: "ExecStart=/lib/systemd/systemd-networkd-wait-online --any" }
    mfts_command_after_copyfiles:
      - { command: "netplan apply", become: "yes" }
    mfts_systemd_rules:
      - { name: "systemd-resolved.service", state: "stopped", enabled: "no", daemon_reload: "yes" }
      - { name: "dnsmasq.service", state: "restarted", enabled: "yes", daemon_reload: "yes" }
      - { name: "openvpn@ovpngw.service", state: "restarted", enabled: "yes", daemon_reload: "yes" }
    mfts_command_atlast:
      - { command: "/usr/sbin/iptables-save | tee /etc/iptables/rules.v4", become: "yes" }
  roles:
    - YasuhiroABE.myfavorite-setting
```

このYAMLファイルを配置して、修正などを加えた後で次の要領で、ansible-playbookを起動します。

```bash:ansible-playbookの起動
$ make all

## ansible-playbook site.yaml コマンドの起動
```

## OpenVPNを構成する際の考慮点

FORWARDのデフォルトポリシーがDROPであると、192.168.200.0/24ネットワークから外部に接続するためのルールも明示的に設定する必要があります。

今回は、192.168.200.0/24からインターネットに向けては、HTTP(80,443)、LDAP(636)ポートだけを通すようになっていて、DNSへの接続は192.168.200.1に接続し、dnsmasqから情報の提供を受けます。

またovpngwが192.168.200.0/24内部にあるサーバーのdefault gwであるという点も、ufw/iptablesを設定する上では注意しなければいけません。

### ufw/iptablesのデバッグ作業

iptablesの内容を保存するために、iptables-persistentを利用しています。ansible-playbookでは最後に設定した内容を保存するようになっているので、ネットワーク接続ができないような構成にしてしまった場合には、キーボード・ディスプレイやシリアルポートからアクセスできるようにしておく準備が必要です。

/etc/iptables/rules.v4の内容を削除しないと、再起動してもネットワーク接続ができないままとなってしまうので、ファイルを削除したり、問題のある行を削除するといった対応が必要です。

この他にufwをデバッグモードにする方法も活用した方が良いでしょう。

```bash:ufwのデバッグログを有効にする
$ sudo ufw logging low
```

"low"の部分を、"medium"や"high"にするとより詳細なログが/var/log/syslogに出力されます。
通信ができない場合には問題がないか、通過させたいパケットがDROPされていないか、確認するために利用すると良いでしょう。

以上