こんにちわ、久しぶりの投稿です。
仕事でL2TP/IPsecなVPNを構築しているのですが、インストールや設定などの手間がそこそこ掛かるので、良い感じにお手軽なVPNは無いか調べていました。
すると、我らがSSH大先生でVPNまで張れちゃうとの情報を入手したので、早速試してみることにしました。
SSH万能すぎん?🤔
解説
SSHには SSH Tunneling という仕組があり、それを利用することでVPNを構築できます。
重要なのは「ベースはSSHだが独立した仕様」ではなく「SSH本体機能の一部」なので、普通に ssh
コマンドを叩いてトンネリング有効化のオプションを付与すれば、ポートも認証方式も暗号化も、そっくりそのままVPNを張れるのです。
通常のシェル接続で使用するコネクションやチャネルをそのままトンネリング接続へ転用しているため、技術的には全く同じものとなります。
接続までの流れ
実際に行っている処理は、だいたい下記のようになります。
- ローカル・リモート間でコネクションを確立
- コネクション内でチャネル0をオープン (シェル用)
- ローカルとリモートへそれぞれVNICを生成
- コネクション内でチャネル1をオープン (トンネリング用)
- チャネル1の両端をVNICで終端
- トンネル開通
[Terminal] [VNIC] [Shell]
|| || ||
{ -------- SSH Client -------- }
| || || |
| Connection |
| <Ch:0> <Ch:1> |
| || || |
{ -------- SSH Server -------- }
|| ||
[Shell] [VNIC]
このうち (2) までは、シェル接続とトンネリング接続で同じ挙動となります。
普通にSSH接続する場合はチャネル0を使用してリモートのシェルを操作するだけで事足りるためです。
そして (3) からが、トンネリング接続でのみ行われる処理となります。
と言っても難しい話ではなく、SSHクライアントがローカル (直接) とリモート (チャネル0経由) のシェルへ接続してコマンドを叩き、VNICを生成しているだけです。
VNIC生成の時点で新たにチャネル1が開かれ、チャネル1の両端がVNICで終端されることで、晴れてローカルとリモートを結ぶトンネルが完成となります。
プロトコル
VNIC (仮想ネットワークデバイス) として Tun / Tap を利用することから、トンネル内のプロトコルは PPP もしくは Ethernet のどちらを使用するか選択できます。
なおパケット構造については、ローカル・リモート間を往来するのはあくまでSSHパケットなため、データ本体が格納されるエリア (ペイロード) へEthernetやIPのフレームが丸ごと乗っかり、カプセル化されます。
そのため、L2TP/IPsecやOpenVPNなどの専用プロトコルと比較した場合、オーバーヘッドは大きい傾向にあります。
まぁ何より、環境を選ばず手軽に扱えるのがウリなので、効率を求めるなら専用プロトコルを使えば良いだけの話です。
-----------------------------------------------
| SSH Packet |
| HEADERS | Payload(Data) | PADDING |
-----------------------------------------------
| Ethernet Frame |
| HEADERS | Payload |
-----------------------
注意事項
非常に使い勝手の良さそうなSSH Tunnelingですが、1点だけ注意が必要です。
ローカルとリモートの双方でVNICを操作するため、ローカルとリモートのどちらも、ルート権限での実行が必須となります。
SSHサーバーの初期設定値を見てみると、ルートアカウントへのログインはパスワード認証以外で可能、つまり公開鍵認証や証明書認証などの非対称でセキュアな認証方法でのみ可能となっています。
その制限を緩めてパスワード認証でルートログインを許すことは無いと思いますが、公開鍵認証を使用する際も注意を払う必要があります。
例えば公開鍵認証の場合は、使用する暗号アルゴリズムを ED25519 / X25519 などの強固な物のみ使用すうよう明示的に設定したり、それらの暗号アルゴリズムが実際にどう振る舞うのかをよく調べてから使用するようにしましょう。
(この辺の暗号技術については元気があったら別途記事にしようと思います)
PermitRootLogin prohibit-password
実際に接続してみる
ざっくり解説も済んだところで、早速繋いでみたいと思います。
まずSSH Tunnelingを使用するにあたり、そこまで大掛かりではありませんが、いくつかの設定変更が必要となります。
なお、ポート開放やファイアウォール制御については、繰り返しになりますが、あくまで往来するのはSSHパケットそのものなので、既にSSHが接続できる環境であれば変更は不要です。
サーバー設定
サーバー側は sshd_config
の <PermitTunnel
> を変更するだけです。
<PermitTunnel
>
クライアントからのトンネリング接続要求を制御します。
-
yes
... クライアント側でPPPまたはEthernetを選択可能 -
point-to-point
... PPPのみ許可 -
ethernet
... Ethernetのみ許可 -
no
... トンネリングを拒否 (デフォルト値)
PermitTunnel yes
設定を変更したら、デーモンを再起動して準備完了です。
systemctl restart sshd
クライアント設定
クライアント側は ssh
コマンドを叩くときにオプションとして全ての設定を指定できます。
しかし、設定項目が多く毎回入力は面倒なので、トンネリング接続用のプロファイルを ~/.ssh/config
に作っておくと便利です。
Host (name)
HostName (host)
Port (port)
User (user)
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PreferredAuthentications publickey
Ciphers chacha20-poly1305@openssh.com
KexAlgorithms curve25519-sha256@libssh.org
MACs hmac-sha2-512-etm@openssh.com
HostKeyAlgorithms ssh-ed25519
StrictHostKeyChecking no
Compression yes
IdentitiesOnly yes
IdentityFile (~/.ssh/key)
Tunnel ethernet
TunnelDevice 0:0
PermitLocalCommand yes
LocalCommand ifconfig tap0 10.0.0.2 netmask 255.255.255.0
RemoteCommand ifconfig tap0 10.0.0.1 netmask 255.255.255.0
これは私の使い回し用テンプレです。
暗号アルゴリズム周りは、狙った物へ誘導するために1種類へ絞っています。
「誘導」と言ったのは、SSHで実際に使用される暗号アルゴリズムは、クライアント側とサーバー側がそれぞれ持っている複数の候補の中からネゴシエートされるためです。
クライアント側から候補を1個しか提示しなければ、サーバー側はそれを使わざるを得ません。
そして、トンネリング接続に関係する項目は下記となります。
<Tunnel
>
VNICとしてTunまたはTapのどちらを使用するか指定します。
-
ethernet
... Tapを使用 -
point-to-point
... Tunを使用
なお、サーバー側の <PermitTunnel
> をどちらかへ制限している場合、クライアント側の <Tunnel
> と合致しない場合、接続要求は拒否されます。
<TunnelDevice
>
VNICを生成する際のデバイス番号を指定します。
0:0
のように記述し、左値はクライアント側で右値はサーバー側のデバイス番号となります。
<PermitLocalCommand
>
接続完了後に、後述の <LocalCommand
> で記述したコマンドの実行を許可するかを選択します。
-
yes
... 実行を許可 -
no
... 実行を拒否
<LocalCommand
>
<RemoteCommand
>
接続完了後にローカルまたはリモートのシェルで実行するコマンドを記述します。
ここでは、接続時に生成されたVNICへIPアドレスを割り当てています。
他にも、IPマスカレードやルーティングなどもここへ記述できるため、活用の幅が広がります。
鍵生成
この辺は既出情報が豊富なため、本記事に必要か迷いましたが、使用する暗号アルゴリズムについて少し触れたので、ついでに載せておきます。
ssh-keygen -q -t ed25519 -N "" -f ./(key)
これも私の使いまわし用テンプレです。
2021年現在、公開鍵はとりあえずED25519/X25519を使用しておけば、まず間違いは無いと思います。
接続
さて、いよいよ接続です。
ssh
コマンドをルート権限で実行しますが、ここで注意したいのは、ルート権限で実行した場合にデフォルトで読み込むコンフィグファイルは ~/.ssh/config
ではなく /etc/ssh/ssh_config
となります。
そして sudo
コンテキスト内では ~
が指し示すディレクトリは置き換わるので、ホームディレクトリ配下にアクセスしたい場合はフルパス指定が必須となります。
そのため -F
オプションを付与し、コンフィグファイルの読み込み先をフルパスで指定する必要があります。
プロファイルの <IdentityFile
> がフルパスで指定されているのも同じ理由です。
sudo ssh -Nq -F /home/(user)/.ssh/config (profile)
コマンドを入力し、何も応答が返らなかったら接続成功です。
応答が返らない間は、常にトンネリング状態となります。
トンネリング状態を解除 (切断) したい場合は CTRL+C
で切断できます。
なお、VNICは切断時に自動で削除されるため、切断後の手動削除は不要です。
ifconfig
や nmcli connection show
などでネットワークインターフェースを確認すると、IPアドレス付与済のVNICが生成されていると思います。
もしインターフェースが見当たらなかったら、どこかで接続が失敗しています。
-vvv
オプションを付与し、ログを確認してみてください。
おわりに
今回はVNICを使ったVPNでしたが、思ったより簡単に動いて驚きました。
SSHは他にも ポートフォワード というVPN機能があるので、今度はそちらも試してみたいと思います。
ポートフォワードはVNICを使用せずルート権限が不要なため、敷居も下がるかと思います。
いやSSH万能すぎん???🤔🤔🤔
参考 - 公開鍵暗号の強度
参考として、各種公開鍵暗号の強度について載せておきます。
複数の暗号アルゴリズムを組み合わせた場合の最終的なセキュリティ強度は、その中で最低強度のアルゴリズムへ依存します。
例えばRSA-2048 (112bit) とAES-256 (256bit) を組み合わせた場合、総合的な強度は112bitとなります。
公開鍵 | 種類 | 強度 (bits) | 備考 |
---|---|---|---|
RSA-2048 | RSA | 112 | や め と け |
RSA-3072 | RSA | 128 | 演算が非効率的 (遅い) 古代遺産にも対応 ECCが使えないとき用 |
NIST-P256 | ECC | 128 | 演算効率重視のパラメータ NISTを信頼できるか否か |
NIST-P521 | ECC | 260 |
2^521-1 なので超高速(P256より速いらしい) AES-256の強度が損なわれない NISTを以下略 |
ED25519 X25519 |
ECC | 128 | 2021年現在のトレンド 脆弱性を回避した設計 鍵長が短い割に高強度 |
ED448 X448 (Goldilocks) |
ECC | 224 | 25519の上位互換 2021年現在まだ普及していない |
RSAは鍵長が長い割に強度が低いうえ、鍵長を増やすと演算負荷も極端に増えるため、最近ではあまり使われなくなってきています。
NIST系は演算効率に特化しているのが特徴ですが、過去NISTはバッグドア疑惑など色々とグレーな問題を起こしてきたようで、その点において信頼できるか否かは、皆さんの判断によると思います。
ED25519はツイストエドワーズ曲線、X25519はモンゴメリ曲線という異なる楕円曲線を利用していますが、この2曲線は、あるパラメータ (下記) の時に同値となる双有理同値という性質を持っています。