LoginSignup
7
9

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

Last updated at Posted at 2020-04-07

はじめに

OpenVPNを利用する時に、共通鍵を利用した方法は不特定多数のアクセスを許す事にもつながり、推奨されません。easy-rsaを使うことで簡単に公開鍵証明書ベースの認証方式をOpenVPNに導入することができます。

個人1名で利用する場合でもインターネットからアクセスできるサーバーには、共通鍵を利用するOpenVPNサーバーは構築しないようにしましょう。IPsecのように共通鍵を設定する場合、相手方のIPアドレスを固定しなければいけません。これはIPアドレスが固定されている拠点間の接続に最適です。OpenVPNと証明書ベースの組み合せは、移動するユーザーが接続するユースケースに最適です。IPアドレスが固定できないのであれば共通鍵ベースのVPNは利用するべきではありません。

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

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

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

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

過去の類似の記事

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

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

環境

  • CentOS7 (7.7, ホスト名: openvpn.example.com)
  • openvpn (2.4.8 EPEL RPM package)

基本的な設定

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

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

$ git clone https://github.com/OpenVPN/easy-rsa.git
$ cd easy-rsa
## その時点で最新のタグを確認し、利用する
$ git tag
## タグ名 v3.0.7 を利用する場合、"my_v3.0.7"の文字列部分は任意に変更可能
$ git checkout refs/tags/v3.0.7 -b my_v3.0.7
$ 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/

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

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

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

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

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ファイルの作成と署名処理を別サーバーに分離するべきですが、ここではそのような処理を行っていない点に注意してください。

サーバー証明書の生成・署名
## 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

$ easy-rsa/easyrsa3/easyrsa sign-req server openvpn.example.com
$ find pki -type f | grep openvpn.example.com
pki/reqs/openvpn.example.com.req
pki/issued/openvpn.example.com.crt
pki/private/openvpn.example.com.key

client証明書を発行する時との違いは引数を"client"から"server"に変更しただけです。

クライアント用にclient証明書も発行しますが、まずはサーバーの設定を行っていきます。

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

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

$ sudo yum install openvpn

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

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で管理しているため、これを参照するようにします。

/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ポート以外で稼動させている場合には、別途そのポート番号を指定して追加することが必要になります。

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 が準備されていれば、次のようなコマンドが有効になります。

## プロセスを一時的に稼動させ、異常終了などしないか確認します
$ 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)し、作業をまとめます。

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アドレスやホスト名に変更してください。

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

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

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}!" \
    "${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スクリプトを呼び出しています。

client/gen-req.rb
#!/usr/bin/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
client/sign-req.rb
#!/usr/bin/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 request details:/) { |result|
    w.puts "yes"
  }
  r.expect(/pki\/private\/ca.key:/) { |result|
    w.puts "#{capass}"
  }
  
  r.expect(/Certificate created at:.*#{ARGV[1]}.crt./) { |result|
    puts result
  }
end

easy-rsa自体がopensslコマンドのwrapperなので、直接にopensslコマンドを制御するといった方法もありますが、そうなってくると他のアプリケーション・サービスを利用するべきだと思います。

ディレクトリ構造

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

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コマンドから

クライアント証明書"user01"を発行する手順
$ ./create-client.sh user01
## "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の公式サイトから、最新のクライアントプログラムをダウンロードし、インストールします。

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

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

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

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

  1. あらかじめ、Tunnelblickの公式サイトからクライアントアプリをダウンロードし、インストールします。
  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サービスを開始します。
一連の作業の流れ
$ 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出力
$ 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パッケージを導入してください。

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のサーバーとしても動作します。

準備作業

準備作業
## 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ファイルは以下のようになっています。

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を起動します。

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をデバッグモードにする方法も活用した方が良いでしょう。

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

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

以上

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9