カテゴリー
FreeBSD

FreeBSDでLet’s Encryptな証明書でSoftEtherサーバを立てる

お出かけ先のWindows PCやAndroidスマホから、SSL-VPNを使いたいことが多々ありまして、VPNサーバを立ててます。

使い方として多いのは、カフェやファミレスのフリーWifiを生で使いたくないときや、腐ったWebプロキシ(だがSSLは空いている)を強制されてしまったときとか。

SoftEtherを選んでいるのは?

まず腐ったWifi環境を通過しないと行けないので、IPsecベースのものは使えないことが多いです。PPTP等もWebプロキシは越えられないので、SSLベースで構成されているVPNソフトウェアとなります。

あと、Windows PCに余計なソフトウェアを入れずに使えることも大事です。OpenVPNクライアントとか入れてたら、明らかにVPN使っているな感が出てしまって穏便な感じから遠ざかってしまいます。その点、SoftEtherでSSTPを喋らせておくと、Windowsの標準的なVPN設定だけで接続を構成出来ます。これ、すごい大事。

Androidスマホだと、SSTP VPN Clientってソフトを入れてます。色んなVPNソフトウェアがあるんですが、逆に通信を盗聴されるなんていうマルウェアもどきなものもあるのでこの辺は慎重に選ぶ必要がありますが。

FreeBSDベースを選んだわけは?

FreeBSDを使いたいから。いや、SoftEtherがpkgの中に入っているからインストールが楽なんですよ。
海外だとOpenVPNが主流なのもあって、なかなかパッケージマネージャの対象になっているのが少ないんですよね。

FreeBSDをインストールする

私は家に余ってたHyper-V上の仮想マシンに、ISOイメージからFreeBSDをインストールしました。

ここでのポイントは仮想NICを2個接続しておくことです。
VPNトンネル終端として受け付けるためのインターフェースと、カプセル化を解除したパケットを出すためのインターフェースは別である必要があります。
(画像はhr0とhr1の2つのNICが繋がっているの図)

OSインストールが終わったら、pkgコマンドでSoftEtherをインストールしておきます。

pkg install softether

インストールが終わったら、rc.confに自動起動の設定をしておきます。

vi /etc/rc.conf
softether_server_enable="yes"
softether_bridge_enable="yes"

SoftEtherを構成する

FreeBSDのシェル上で、vpncmdコマンドを使ってずっと構成しても委員ですが、、、、さすがに分かりにくい場合が多いので、適当なWindowsマシンにSoftEther管理ツールを入れてGUIで設定するのをおすすめします。
※初回設定したときは特に画面キャプチャも取らずに1年くらい前にやったのでもううろ覚えです。

SoftEther VPNサーバ管理マネージャの接続画面
新しい接続設定からVPNサーバのIPアドレスを指定して接続できる。
※初回接続時はパスワードの設定を求められる
SoftEther VPNサーバ管理マネージャのメイン画面
SSTPの設定は画面下部のOpenVPN/MS-SSTP設定から

ローカルブリッジは、外側がhr0で、内側がhr1だと下画面のような設定になる。(外に持ち出したPC等インターネット上からはhr0に着信する場合)

証明書周りは初期設定ウィザードで一度設定してもらっても良いですが、この後Let’s Encryptで発行したもので置き換えるのでどっちでも良いです。

Let’s Encryptの導入と証明書作成

Let’s Encryptで証明書を発行し、定期的に更新するためにcertbotを利用します。

pkg install py36-certbot

で、certbotを使って証明書を発行してもらいます。
※初回実行時はもっとLet’s Encryptアカウントの設定とかいろいろあった気がします。(が、このブログを書いている時はその当時の操作ログが見つからないので割愛)

# certbot-3.6 certonly

Saving debug log to /var/log/letsencrypt/letsencrypt.log
How would you like to authenticate with the ACME CA?
 1: Spin up a temporary webserver (standalone)
 2: Place files in webroot directory (webroot)
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 1
Plugins selected: Authenticator standalone, Installer None
Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c' to cancel):  vpn.example.com
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for vpn.example.com
Waiting for verification…
Cleaning up challenges
IMPORTANT NOTES:
 Congratulations! Your certificate and chain have been saved at:
 /usr/local/etc/letsencrypt/live/vpn.example.com/fullchain.pem
 Your key file has been saved at:
 /usr/local/etc/letsencrypt/live/vpn.example.com/privkey.pem
 Your cert will expire on 2020-05-09. To obtain a new or tweaked
 version of this certificate in the future, simply run certbot
 again. To non-interactively renew all of your certificates, run
 "certbot renew"
If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
Donating to EFF:                    https://eff.org/donate-le

# 

Let’s Encryptの証明書をSoftEtherに設定する

さて、証明書を発行してもそのままではSoftEtherは利用出来ません。Apache HTTPd Serverやnginxだとcertbotが証明書を配してサービスの再起動までセットで対応してくれるのだと思いますが、さすがにSoftEtherまでは対応していません。

SoftEther VPNサーバ管理マネージャのGUI上から手動で証明書を設定しても良いですが、それは更新忘れが必ず発生します。なので、自動化します。

vi renew_cert_softether.sh

#!/bin/sh
certbot-3.6 renew
vpncmd localhost:5555 /server /password:P@ssw0rd /CMD ServerCertSet /LOADCERT:/usr/local/etc/letsencrypt/live/vpn.example.com/fullchain.pem /LOADKEY:/usr/local/etc/letsencrypt/live/vpn.example.com/privkey.pem

まぁ、見たままですが、

  1. certbotで証明書を更新する (renew)
  2. vpncmdで、SoftEtherのサービスにSSL証明書を読み込ませる (LOADCERT&LOADKEY)

といった流れで実行させています。

おまけ: Windows 10でのSSTP VPNセットアップ

VPNの種類で、”Secure Socket トンネリング プロトコル (SSTP)”を選んで、ホスト名、ユーザ名、パスワードを入れるだけ。お手軽。

カテゴリー
Azure

Azure Monitor for VMsで仮想マシンの中のプロセス一覧を取得する

Azure Monitor for VMsの中で、Dependency Agentというエージェントを入れると、仮想マシンの内部でTCP/UDPのコネクション情報と、OS上のプロセス情報を定期的に収集し、Log Analytics workspaceに転送してくれる。

その情報はAzureポータル画面のMapに表示されるが、どんなプロセスが今動いてるのか見るのにわざわざブラウザを開くのは面倒くさいはず。

特に、プロセス監視のような用途だと、せっかくのLog Analytics workspaceなのでAzure MonitorからKustoクエリを使って定期的に監視したくなるはず。
ということでやってみた。

まずは、Dependency Agentが転送してきてるログを見てみる。

Dependency Agentが集めた情報は、InsightsMetricsテーブルに時系列で保管される。また、マスター表として、コンピュータの一覧はVMComputerに、プロセスの一覧はVMProcessにそれぞれ保管される)
なお、過去のバージョンを使っている場合は、ServiceMapComputer_CLやServiceMapProcess_CLと言われる互換用のテーブルに保管されている場合があるが、今後は上記のInsightsMetrics/VMComputer/VMProcessテーブルのセットを使うことが推奨されている。
https://docs.microsoft.com/ja-jp/azure/azure-monitor/insights/vminsights-ga-release-faq

次に、InsightsMetricsテーブルの中から、プロセス情報を持っている行だけを絞り込んでみてみる。これには、Name列がHeartbeatで、Originがvm.azm.ms/mapなものにフィルタすると良い。

Tags列のJSONデータの中に、vm.azm.ms/processIdsとしてVMProcessテーブルのIDが羅列されて並んでいる。多分に、データ収集の都度Process情報全部(PID、ユーザ名、プロセス名、コマンドライン文字列、等々をもたせると重複が多くなりすぎるので別表に参照として飛ばしているのだろう。

で、VMProcessテーブルを眺めてみる。Dependency Agentは定期的(1分おき?)にOS上のプロセス一覧を取得しており、新規に開始されたプロセスがあれば、そのときだけLog Analytics workspaceのVMProcessテーブルに情報を上げてくるようだ。

ということで、InsightsMetricsテーブルと、VMProcessテーブルをうまく結合すれば良さそうである。

まずはInsightsMetricsテーブルから、コンピュータ名を指定して最新時点のプロセスID一覧を表示させてみる。(ちなみに変数宣言→問い合わせクエリのような複数の処理を実施するときは実行したいクエリ全体を選択した上で、Runボタンを押さないとエラーになる)

//検索対象のコンピュータ名
let ComputerName = “LAINDC01”;

InsightsMetrics
| where Name == “Heartbeat” and Origin == “vm.azm.ms/map”
| where Computer startswith ComputerName
// 最新世代を取るために、TimeGeneratedの最大値を取る行を抜き出している
| summarize arg_max(TimeGenerated, *)
// Tags列のJSONの中から、vm.azm.ms/processIds句を取り出して、Process列を生成している
| extend Process = parse_json(extractjson(“$.[‘vm.azm.ms/processIds’]”, Tags))

ここまでだと、Process列は横に配列として並んだままになってしまっている。

mvexpand演算子を使うと、この配列を縦に行として展開することが出来る。

あとは、VMProcessテーブルとProcess列をもとに結合するだけ。

ここまでくれば、CommandLine列を正規表現で検索すれば、プロセス監視もどきとかも実行できる。

最終的なクエリ式はこんな感じになる。

let ComputerName = “LAINDC01”;
let ProcessName = “.*svchost.exe -k netsvcs -p -s Schedule”;

InsightsMetrics
| where Name == “Heartbeat” and Origin == “vm.azm.ms/map”
| where Computer startswith ComputerName
| summarize arg_max(TimeGenerated, *)
| extend Process = parse_json(extractjson(“$.[‘vm.azm.ms/processIds’]”, Tags))
| mvexpand Process to typeof(string)
| join kind= leftouter (VMProcess | where Computer startswith ComputerName | summarize arg_max(TimeGenerated, *) by Process) on Process
| where CommandLine matches regex ProcessName