4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ConoHaAdvent Calendar 2023

Day 11

ConoHa VPS に Docker で OpenVPN サーバを構築する

Last updated at Posted at 2023-12-10

この記事は ConoHa Advent Calendar 2023 の11日目の記事です。10日目はAyutsukiさんでした。12日目はめんどい 太郎さんです。

本記事では,ConoHa VPS 上に Docker で OpenVPN サーバを構築する手順を解説します。

前提

  1. ConoHa VPS 上に Linux サーバが構築されており,SSHでログインできるようになっている
  2. Docker がインストールされており,sudo なしで Docker が実行できるようになっている

この準備がまだの方は,次の記事でその手順を詳しく解説していますので,必要に応じてこちらを設定してから,続きをお読みください。(下記記事では Ubuntu でサーバ構築していますが,他の Linux でも大差ありませんので大丈夫です。)

VPNサーバを構築する目的

旅が好きな自分は,海外を含む空港やホテルが提供する公衆WiFiを使ってネット接続する場面が多々あります。それにあたり,ConoHa VPS上にVPNサーバを構築し,踏み台サーバとして用いると便利です。その目的は大きく4つあります。

目的①. 公衆WiFi接続時のセキュリティ確保

公衆WiFiのセキュリティに不安を持つ人も多いでしょう。そこで,ConoHa上のVPNサーバを踏み台として,全通信を暗号化されたVPNトンネルを通して行うことで,公衆WiFiを通す通信を暗号化し,セキュリティを確保できます。

目的②. 国内からしかアクセスできないサイトに海外から接続する

動画配信サイトや金融機関のサイトなどで,日本国外からのアクセスを,IPアドレスで判定して遮断しているサイトがあります。そこで,国内にある ConoHa VPS を踏み台としてアクセスすることで,サイト側からは日本国内からのアクセスと判定させて,海外からでも日本国内にいるときのようなアクセスを確保できるようになります。

image.png

なお,これを目的として,有料で提供されている既存の大手VPNサービスを使う手もありますが,サイトによっては,有名な大手VPN提供業者のIPアドレスがNGリストに登録されており,そのVPN業者からのアクセスを遮断しているケースもあります。自分でVPNサーバを構築しておけば,そのようなIPアドレス規制にも引っかからず確実です。

目的③. ファイアウォールの回避

空港やホテルのファイアウォール設定によっては,HTTP(S)やDNSなどの必要最低限のプロトコル以外のアウトバウンド接続を規制していたり,あるいは帯域制限等の目的で動画配信サイトへのアクセスが遮断されていたりすることがあります。VPS上に構成されたVPNサーバを踏み台とし,全ての通信をそのVPNトンネルを経由して接続することで,そのような制限をかいくぐることができます。特に,HTTPS通信と同じ 443/tcp でVPNサーバを待ち受けさせ,そこに向けてTLSで暗号化したVPN通信を通せば,通常のウェブブラウザによるHTTPS通信と区別を付けることは困難なので,ブロックされる可能性は極めて小さいと言えるでしょう。

image.png

目的④. 自宅LAN内へのアクセス確保

自宅LAN内の端末と出先の端末を,ともに ConoHa VPS 上のVPNサーバに接続しておきます。すると,その仮想ネットワークにおいては,自宅の端末と出先の端末が「同一LAN内」に存在することとなります。よって,SSH, SMB, VNCをはじめ,自宅LAN内の端末に自由に接続できるようになります。さらに,その自宅端末を踏み台にすることで,自宅LAN内の他の端末への接続も可能となるわけです。

image.png

この方法で自宅LANへの接続を確保する長所は次の通りです。

  • 自宅のルータのポートフォワーディング設定などを変えなくてよい。
  • インバウンド接続が全て拒否されるようなNAT内に対しても外からの接続を確保できる。
  • ケーブルTVネットワークなどで,そもそも契約者にグローバルIPアドレスが与えられず,外部ネットワークから unreachable な自宅LAN環境であっても,外部からの接続が可能となる。

VPNサーバの種類の選択

PPTP

古いVPNプロトコルの一つで,設定が比較的簡単ですが,セキュリティが弱いので,現在では非推奨となっています。

L2TP/IPsec

定番のVPNプロトコルで,各OSでも標準でクライアント機能が用意されているので便利です。ただしポート番号が固定(UDP 500/1701/4500,IP 50 (ESP))であるため,公衆WiFiのファイアウォールでアウトバウンドポートが制限されている場合は使用不可となってしまいます。

OpenVPN

TLSを利用したオープンソースで提供されるVPNソリューションです。デフォルトでは 1194/udp を利用しますが,ポート番号を変更したり,UDPではなくTCPを使用することも可能です。各OSにはデフォルトでクライアント機能は用意されていないので,クライアントには自前で OpenVPN クライアントをソフトウェアをインストールする必要があります。

結論:OpenVPN サーバを Docker で構築する

これらを比較検討した結果,上述の目的①~④を全て満たすことができる,OpenVPN を選ぶことにしましょう。OpenVPN サーバの構築は本来それほど簡単ではありませんが,Docker を利用してイメージを展開することで,かなり楽に OpenVPN サーバ構築ができるようになります。

プロトコル・ポートの選択:UDP vs. TCP

OpenVPN の場合,デフォルトの待ち受けポートは 1194/udp です。多くの場合このままで問題はないでしょう。UDPはTCPに比べオーバーヘッドが少ないので,高速になると期待されます。ですが,次のようなケースで問題になり得ます。

  • 航空機内WiFiなど,特に回線が貧弱でパケットロスが起こりやすい場合,UDPの信頼性の低さが問題となる。オーバーヘッドを許容してでも,TCPの信頼性が欲しいケースもある。
  • 目的③に関連して,厳しいファイアウォールによって HTTP(S) 以外のアウトバウンド接続が制限されているようなケースだと接続できない。
  • また,1194/udp に対して接続していると,「OpenVPN サーバへ接続している」ということが検知されやすく,ブロックされる可能性がある。(中国の金盾とか?)

このようなケースに対処するためには,OpenVPN サーバを 1194/udp ではなく 443/tcp で待ち受けさせるという手が考えられます。443/tcp はHTTPSのポートなので,これがファイアウォールでブロックされている可能性は限りなく小さいです。443/tcp で待ち受ける OpenVPN サーバに対してTLSで暗号化したVPNトンネルを張れば,ウェブブラウザの利用による通常のHTTPS通信と区別が付かないため,ブロックすることは困難でありましょう。

結論:UDPとTCPでデュアルスタンバイとする

一方,通常の利用であれば,オーバーヘッドの少ない 1194/udp で十分です。そこで,Dockerの強みを活かして,

  • 同じ設定ファイル一式を元に,OpenVPNサーバを二重に起動させる(Dockerコンテナを2つ起動させる)。
  • 片方はUDP,もう片方はTCPで待ち受けさせる。
  • 通常利用時はUDPの方を使い,特殊なケースならTCPの方に接続する

というデュアルスタンバイ体制を整えてみましょう。

image.png

OpenVPNにおける仮想ネットワークトポロジーの選択:net30 vs. subnet

結論:subnetを選んでおけばよい

詳細説明:net30 と subnet の違い

デフォルトでは,OpenVPN サーバが構築する仮想ネットワークのトポロジー設定は,net30 というものになっています。これは,IPv4 の 32bit のIPアドレスにおいて,上位 30bit をネットワークアドレスとし,下位 2bit のみをホストアドレスとして,接続クライアントごとに別々のネットワーク空間に属するように設定するものです。

net30 トポロジーの例

クライアントAとOpenVPNサーバとの接続

  • ネットワークアドレス:192.168.255.0(2進法で書けば11000000.10101000.11111111.00000000
  • サブネットマスク:255.255.255.252(2進法で書けば 11111111.11111111.11111111.11111100

➡ サブネット内のIPアドレスは4個しかないことになります。しかも,うち2つはネットワークアドレスとブロードキャストアドレスになるため,ホストに使えるIPアドレスは2個のみ。その2つを,サーバ側とクライアント側で分け合います。

  • 192.168.255.0(下位2bitが00):ネットワークアドレス
  • 192.168.255.1(下位2bitが01):OpenVPNサーバ側のIPアドレス
  • 192.168.255.2(下位2bitが10):クライアントAのIPアドレス
  • 192.168.255.3(下位2bitが11):ブロードキャストアドレス

クライアントBとOpenVPNサーバとの接続

  • ネットワークアドレス:192.168.255.4(2進法で書けば11000000.10101000.11111111.00000100
  • サブネットマスク:255.255.255.252(2進法で書けば 11111111.11111111.11111111.11111100

同様に,サブネット内の4個のIPアドレスは,ネットワークアドレス・サーバのIPアドレス・クライアントのIPアドレス・ブロードキャストアドレスで使用します。

  • 192.168.255.4(下位2bitが00):ネットワークアドレス
  • 192.168.255.5(下位2bitが01):OpenVPNサーバ側のIPアドレス
  • 192.168.255.6(下位2bitが10):クライアントBのIPアドレス
  • 192.168.255.7(下位2bitが11):ブロードキャストアドレス

このように,net30 トポロジーの場合,1クライアントごとにIPアドレス空間を4個ずつ消費してゆくので,アドレス空間の使い方として効率が低いです。また何より,クライアントごとに別々のサブネットに所属しているのは,目的④(外部から自宅LAN内の端末と同一ネットワーク内として接続したい)のためには分かりづらいです。そこで,もっとシンプルに,OpenVPNサーバと,接続中の各クライアントとが,全て同一ネットワーク内に所属するような仮想ネットワーク構成としたいです。それを実現するのが subnet というネットワークトポロジー設定です。これであれば,

  • ネットワークアドレス:192.168.255.0
  • サブネットマスク:255.255.255.0
  • OpenVPNサーバ側のIPアドレス:192.168.255.1
  • クライアントAのIPアドレス:192.168.255.2
  • クライアントBのIPアドレス:192.168.255.3
     ⋮
  • ブロードキャストアドレス:192.168.255.255

という,ごく普通の 192.168.255.0/24 の仮想ネットワークが実現でき,アドレス空間が有効活用できますし,何よりシンプルで分かりやすいです。

古いOpenVPNクライアントだと subnet トポロジーに対応していないという理由で net30 トポロジーがデフォルト設定となっているようですが,今から新規にOpenVPNサーバを構築するなら,subnet トポロジーで構築しておくのがよいでしょう。

作業工程

前提

VPSのドメイン名

ConoHa VPS のサーバに与えられたドメイン名が VPS.SERVERNAME.COM であるとします。独自ドメイン名を取得していない場合も,ConoHa VPS のコントロールパネルを見ると,vXXX-XXX-XXX-XXX.fxcu.static.cnode.jp といったドメインが与えられていることが分かりますので,それを使えばOKです。

VPSのSSHサーバへの接続法

ConoHa VPS へのSSH接続ができるようになっているとします。前記事で設定したように,

  • パスワード認証を無効化して ~/.ssh/id_ed25519 による公開鍵認証に
  • ~/.ssh/config において __conoha という名前でエイリアス設定

としておくと,安全かつ便利です。

[クライアント上の]~/.ssh/config
Host __conoha
    User USERNAME
    Hostname VPS.SERVERNAME.COM
    Port 12345
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

OpenVPNサーバの設定ファイルの保存場所

VPSのUbuntu上において,$HOME/ovpn-data というディレクトリを設定ファイルの保存場所とします。

工程1:OpenVPN の Docker イメージを pull して初期設定

[VPS上で]設定ファイル保存ディレクトリを作成
$ mkdir $HOME/ovpn-data
$ cd $HOME/ovpn-data

OpenVPN サーバの Docker イメージとして,kylemanna/openvpn というイメージを使います。

これを pull しましょう。

[VPS上で]kylemanna/openvpn をpull
$ docker pull kylemanna/openvpn

まずはデフォルト通り 1194/udp で待ち受ける設定,かつ subnet トポロジーで,設定ファイルを生成します。

[VPS上で]設定ファイルを生成
$ docker run --rm -v $HOME/ovpn-data:/etc/openvpn \
    kylemanna/openvpn ovpn_genconfig \
      -u udp://VPS.SERVERNAME.COM:1194 \
      -e 'topology subnet'

これで ~/ovpn-data/openvpn.conf という設定ファイルが生成されます。必要に応じてこのファイルを次のように手動編集しましょう。

[VPS上で]設定ファイルを手動編集
$ sudo vim ~/ovpn-data/openvpn.conf

デフォルトで生成される openvpn.conf において,

route 192.168.254.0 255.255.255.0

という行は,subnet トポロジを採用する結果不要となりますので,# でコメントアウトしておくとよいでしょう。

また,他には,

duplicate-cn

という行を加えておけば,同一証明書を持つ複数の端末からの同時接続を許可できます。ただし,本来,セキュリティ上は端末ごとに別々の証明書を発行して分離すべきです。同一端末扱いの複数のクライアントが同時に接続すると,ログ上での追跡が難しくなりますし,目的④のために端末ごとにIPアドレスを固定する運用をする場合には障害になりますので,避けておいた方がよいでしょう。ここでは duplicate-cn は指定しないものとします。

工程2:PKI初期設定

次に,PKI(公開鍵基盤)の初期設定を行います。CA(認証局)のパスフレーズを決めてください。これは忘れぬよう記録しておきましょう。Common Name の質問はそのままリターンを押せばよいです。

[VPS上で]PKI初期設定
$ docker run --rm -it -v $HOME/ovpn-data:/etc/openvpn \
    kylemanna/openvpn ovpn_initpki

init-pki complete; you may now create a CA or requests.
(中略)
Enter New CA Key Passphrase:(ここでCAのパスフレーズを入力)
Re-Enter New CA Key Passphrase: (確認のためもう一度入力)
(中略)
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:(リターン入力)
(中略)
Enter pass phrase for /etc/openvpn/pki/private/ca.key:(CAのパスフレーズを再入力)
(中略)
Enter pass phrase for /etc/openvpn/pki/private/ca.key:(CAのパスフレーズを再入力)
(後略)

工程3:クライアントごとの鍵ペアと設定ファイルを生成

VPN接続したいクライアント(Mac,Windows,iPhoneなど)ごとに,鍵ペアと設定ファイルを生成します。

まずは,OpenVPNサーバが認識する固有のクライアント名を決めましょう。半角英数字のみ,記号は不可のようです。そして,そのクライアントに対応するパスフレーズも決めます。

[VPS上で]クライアントAに対応する鍵ペアの生成
$ export CLIENTNAME="ClientA"
$ docker run --rm -it -v $HOME/ovpn-data:/etc/openvpn \
    kylemanna/openvpn easyrsa build-client-full $CLIENTNAME

(中略)
Enter PEM pass phrase:(クライアントAのパスフレーズを入力)
Verifying - Enter PEM pass phrase:(クライアントAのパスフレーズを再入力)
(中略)
Enter pass phrase for /etc/openvpn/pki/private/ca.key:(CAのパスフレーズを入力)
(中略)
Write out database with 1 new entries
Data Base Updated

次に,ここで設定した ClientA というクライアントにインストールすべき設定ファイル(証明書を含む)を生成します。

[VPS上で]クライアントAのための設定ファイル生成
$ docker run --rm -v $HOME/ovpn-data:/etc/openvpn \
    kylemanna/openvpn ovpn_getclient $CLIENTNAME > ~/$CLIENTNAME.ovpn

すると,この例ならば ~/ClientA.ovpn というファイルが生成されているはずです。

以下,他に接続したいクライアント ClientB, ClientC, …… があれば,その分だけこの工程を繰り返します。

工程4:ファイアウォールの許可設定

ConoHa VPS 内のLinuxのファイアウォール設定において,1194/udp443/tcp を許可します。Ubuntu の ufw であれば次のようにして許可します。

[VPS上で]Ubuntuのファイアウォール許可設定
$ sudo ufw allow 1194/udp
$ sudo ufw allow 443/tcp

ConoHa の側でもファイアウォール設定がある場合は,そちらにも許可設定を加えることを忘れてはいけません。ConoHa VPS Ver.3.0 の「セキュリティグループ」であれば,次のようにカスタムしたセキュリティグループを適用します。

image.png

image.png

工程5:デュアルスタンバイのための Docker compose ファイルを用意

次のファイルをVPS上の ~/docker-compose.yml として用意します。

[VPS上の]~/docker-compose.yml
version: '3.8'

services:
  openvpn-udp:
    image: kylemanna/openvpn
    container_name: openvpn-udp
    ports:
      - "1194:1194/udp"
    volumes:
      - "./ovpn-data:/etc/openvpn"
    cap_add:
      - NET_ADMIN
    restart: unless-stopped

  openvpn-tcp:
    image: kylemanna/openvpn
    container_name: OpenVPN-TCP
    ports:
      - "443:1194/tcp"
    volumes:
      - "./ovpn-data:/etc/openvpn"
    cap_add:
      - NET_ADMIN
    command: ovpn_run --proto tcp
    restart: unless-stopped

これにより,openvpn-udpopenvpn-tcp という2つのコンテナが起動します。後者は,起動オプションとして --proto tcp が指定されています。これにより,UDPでの待ち受けを,強制的に(ポート番号は同じ1194のままで)TCPでの待ち受けに変更して起動します。そして,

  • VPS上の 1194/udp ↔ openvpn-udp コンテナ内の 1194/udp
  • VPS上の 443/tcp ↔ openvpn-tcp コンテナ内の 1194/tcp

へとマッピングすることにより,外部からVPSへの 1194/udp,443/tcp への接続のそれぞれが openvpn-udp, openvpn-tcp へとマッピングされる,というわけです。

では,Docker compose を使って,これら2つのコンテナを起動してみましょう。

[VPS上で]Dockerコンテナ2つを起動してデュアルスタンバイ
$ cd ~
$ docker compose up -d

docker ps -a で起動中のコンテナを確認し,openvpn-udp, openvpn-tcp という名称のコンテナが起動していることを確認しておきましょう。

工程6:クライアントに設定ファイルをダウンロードして編集

次に,VPS上の ~/ClientA.ovpn を,実際に使用するクライアントAへと安全にダウンロードします。クライアントAの ~/.ssh/config__conoha という名前のエイリアス設定が済んでいれば,次のように scp コマンドによって安全・簡単にダウンロードできます。

[クライアントAで]ClientA.ovpnをダウンロード
$ scp __conoha:ClientA.ovpn .

次に,UDP用とTCP用にファイルを分けます。

[クライアントAで]設定ファイルの複製
$ mv ClientA.ovpn ClientA-udp.ovpn
$ cp ClientA-udp.ovpn ClientA-tcp.ovpn

ClientA-tcp.ovpn のファイルを開き,

remote VPS.SERVERNAME.COM 1194 udp

の行を

remote VPS.SERVERNAME.COM 443 tcp

に変更しておきます。

工程7:クライアントに OpenVPN Connect をインストールして接続

VPN接続したいクライアント(Mac,Windows,iPhoneなど)に対し,クライアントソフトウェアである OpenVPN Connect をインストールしましょう。

そして,新規プロファイル設定画面において,ClientA-udp.ovpn を投入します。

これで接続できれば成功です!

同様に,ClientA-tcp.ovpn 用のプロファイルも作っておきましょう。

確認:接続先から見た自分のIPアドレスが ConoHa VPS のものに変更されているか?

WhatIsMyIPAddress.com のようなIPアドレス確認サイトを利用して,接続先サイト側から見た自分のIPアドレスが ConoHa VPS のものに変化したことを確認しましょう。

発展:どのようにして全通信をVPNトンネルに通しているか?

ClientA-udp.ovpn を開いてみると,次のような行があります。

redirect-gateway def1

この設定があることで,(LAN内向けの通信を除く)全通信がVPNトンネル経由で送られるようになります。しかし,OSのデフォルトゲートウェイ設定は変更されていません。OSのデフォルトゲートウェイ設定を変えることなく,LAN外向けの全通信をどのようにしてVPNトンネルへと差し向けているのでしょうか。

まずは ifconfig で設定を確認してみます。

[クライアントA上で]ifconfigを確認
$ ifconfig
(中略)
utun14: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
	inet 192.168.255.2 --> 192.168.255.1 netmask 0xffffff00

この場合,utun14 がVPN用の仮想ネットワークインターフェースです。そこで,utun14 が絡むルーティングテーブルを確認してみます。

[クライアントA上で]ルーティングテーブルを確認
$ netstat -rn | grep utun14
0/1                192.168.255.1      UGScg              utun14       
128.0/1            192.168.255.1      UGSc               utun14 
192.168.255        192.168.255.2      UGSc               utun14       
192.168.255.1      192.168.255.2      UHr                utun14     

ここに,0/1 および 128.0/1 というルート情報があることに注目します。ルーティングテーブルにはロンゲストマッチという原則があります。宛先IPアドレスにマッチするルート情報が複数ある場合,その中で最もプレフィックス長が長いルート情報が優先されます。

2進法で書くと,0/1128.0/1 はそれぞれ

00000000.00000000.00000000.00000000(先頭1bitがプレフィックス)
10000000.00000000.00000000.00000000(先頭1bitがプレフィックス)

となります。つまり,先頭1bitが0であるようなIPアドレスは前者に,1であるようなIPアドレスは後者にマッチします。

これらはプレフィックス長が1なので,ロンゲストマッチ原則における優先順位としては最下位となります。しかし,どのIPアドレスも,先頭1bitは01のどちらかなので,他のルート情報にマッチしなかったどのIPアドレスも,必ずこのどちらかのルート情報にマッチして拾われることになります。その結果,

  • 他のどのルート情報にもマッチしなかった宛先への通信は,全て 0/1, 128.0/1 いずれかのルート情報にマッチする結果,utun14 に回される
  • OSのデフォルトゲートウェイ設定まで落ちてくることはなく,デフォルトゲートウェイ設定が結果的に無効化されることになる

となるわけです。

仮想ネットワーク内でのクライアントのIPアドレスを固定するには

目的④(外部から自宅LAN内の端末と同一ネットワーク内として接続したい)のためには,仮想ネットワーク内でのクライアントのIPアドレスが固定されるようにした方が都合が良いです。
そこで,OpenVPNサーバの側に,クライアントIPアドレス固定設定を加えましょう。

[VPS上で]ClientAのIPアドレスを192.168.255.2へと固定する設定を行う
$ cd ~/ovpn-data/ccd
$ sudo echo ifconfig-push 192.168.255.2 255.255.255.0 > ClientA

この ClientA というのは,工程3で定めたクライアント名と一致させてください。192.168.255.1 はコンテナ内のサーバ側のIPアドレスとして使うので,クライアントのIPアドレスとしては 192.168.255.2192.168.255.254 のいずれかを指定してください。

その他のメンテナンス作業

稼働中のコンテナ内に入ってネットワークの状況を調査するには

[VPS上で]稼働中のコンテナ内に入ってネットワークの状況を調査する
$ docker exec -it openvpn-udp /bin/bash

こうしてコンテナ内に入れば,ifconfignetstat で仮想ネットワークの状況調査ができます。

クライアント証明書を失効させるには

使わなくなったクライアントがあれば,その証明書ではVPN接続できないようにしましょう。「登録を削除する」ということはできず,「証明書を CRL (Certificate Revocation List,証明書失効リスト) に登録する」という手を取ります。

[VPS上で]ClientAの証明書を失効させる
$ export CLIENTNAME="ClientA"
# revokeしてCRLに登録
$ docker run --rm -it -v $HOME/ovpn-data:/etc/openvpn \
    kylemanna/openvpn easyrsa revoke $CLIENTNAME
$ docker run --rm -it -v $HOME/ovpn-data:/etc/openvpn \
    kylemanna/openvpn easyrsa gen-crl
# その上でOpenVPNサーバを再起動 → CRLが有効化
$ docker compose down
$ docker compose up -d
4
5
1

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
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?