rsyslog の通信を暗号化する。
rsyslog のパケットは、平文で流れる。平文でネットワークを漂うってのは怖い。
現代では、家庭内にGoogleHome やAmazon Alex/FireTVがあり、TP-link製品もあふれている。下手をすると中華世界とつながっていてログ・パケットを覗き見サれているかもしれない。いい気はしない。家庭内ですら機器が溢れてるのだ。暗号化なしでログを流すってのは怖い。
そこで、すぐ使えるCertbotの証明書を使って、Rsyslogの通信を暗号化できないか調べて試してみた。
暗号化通信はできたけど、、RaspberryPiのストレージがアクセスし放題だった。溜まるログがストレージ暗号化されてないので意味がない・・ね。
そのうち、何処か別の場所に、移転する。
証明書の作成
証明書を作るのにオレオレCAとか自己署名を作ってもいいけど、管理が面倒なのでLEに任せたい。
certbot / lets encrypt でサクッと作成
export certbot=/usr/bin/certbot export domain=log.example.tld sudo $certbot certonly --dns-cloudflare\ --dns-cloudflare-credentials /etc/letsencrypt/cloudflareapi.cfg \ --cert-name $domain -d $domain
インストール
rsyslog がTLSを扱えるパッケージを入れる。
sudo apt-get install rsyslog-openssl
rsyslog-openssl に含まれるファイル
apt-file show rsyslog-openssl rsyslog-openssl: /usr/lib/aarch64-linux-gnu/rsyslog/lmnsd_ossl.so rsyslog-openssl: /usr/share/doc/rsyslog-openssl/NEWS.Debian.gz rsyslog-openssl: /usr/share/doc/rsyslog-openssl/changelog.Debian.gz rsyslog-openssl: /usr/share/doc/rsyslog-openssl/copyright
lmnsd_ossl.so
とあるので、ossl
という名前で参照できることがわかる。
サーバー側
必要なもの - 発行機関の証明書(RootCA) - 自分の証明書(fullchain) - 自分の秘密鍵(privkey.pem)
変数の設定
global( DefaultNetstreamDriver="ossl" DefaultNetstreamDriverCAFile="/etc/ssl/certs/ISRG_Root_X1.pem" DefaultNetstreamDriverCertFile="/etc/letsencrypt/live/log.example.tld/fullchain.pem" DefaultNetstreamDriverKeyFile="/etc/letsencrypt/live/log.example.tld/privkey.pem" )
TCPモジュールを書き換え
TCPをロードしている箇所を書き換える。
module(load="imtcp")
ロード時に、OpenSSLの設定入れる。
module( load="imtcp" ## 括弧の中に追記。読みにくいので改行 StreamDriver.Name="ossl" StreamDriver.Mode="1" StreamDriver.Authmode="x509/fingerprint" PermittedPeer=["SHA1:31:69:6D:7E:xxxxxx"] )
StreamDriver.Authmode="x509/fingerprint"
は送信元を識別する。
PermittedPeer=["SHA1:31:69:6D:7E:xxxxxx","SHA1:..."]
で送信元の鍵フィンガープリントを使って簡易認証する。
StreamDriver.Name="ossl"
は"ossl"
(たぶんopensslのこと)を指定した。
設定全体
サーバー側のビフォー・アフター
Server / Before
module(load="imtcp") input(type="imtcp" port="514" address="192.168.1.240" ruleset="remote")
Server / After
## 2022-08-29 global( DefaultNetstreamDriver="ossl" DefaultNetstreamDriverCAFile="/etc/ssl/certs/ISRG_Root_X1.pem" DefaultNetstreamDriverCertFile="/etc/letsencrypt/live/log.example.com/fullchain.pem" DefaultNetstreamDriverKeyFile="/etc/letsencrypt/live/log.example.com/privkey.pem" ) module( load="imtcp" StreamDriver.Name="ossl" StreamDriver.Mode="1" StreamDriver.Authmode="x509/fingerprint" PermittedPeer=["SHA1:31:69:6D:7E:xxxxxx"] ) input(type="imtcp" port="514" address="192.168.1.240" ruleset="remote") module(load="imudp") input(type="imudp" port="514" address="192.168.1.240" ruleset="remote")
フィンガープリントの確認
送信者の確認に、Fingerprintを使うそうだ。証明書のフィンガープリントをopenssl コマンドで抽出しておく。
PEM_File=/etc/letsencrypt/live/log.example.com/fullchain.pem openssl x509 -in $PEM_File -fingerprint | grep SHA
フィンガープリントがあればなりすましが可能とかそういうことではなく署名と証明書のチェックはもちろん行う。
だろうが、送信者の証明を「受け入れる」かどうかをフィンガープリントで決める。
認証局の公開鍵証明書。
もし、OSの証明書ストアにLet'sEncryptの証明書がない場合。手作業で持ってくる。
ls /etc/ssl/certs/ISRG_Root_X1.pem || ( curl -LJO https://letsencrypt.org/certs/isrgrootx1.pem mv isrgrootx1.pem /etc/ssl/certs/ISRG_Root_X1.pem )
認証局、発行元がわからないとき(そんなこと起きるはずがないと思うが)
PEM_File=/etc/letsencrypt/live/log.example.com/fullchain.pem sudo openssl x509 -in $PEM_File -text -noout | grep -i issuer Issuer: C = US, O = Let's Encrypt, CN = R3 CA Issuers - URI:http://r3.i.lencr.org/
クライアント
とりあえず、動けばいいので、Certbotの証明書と秘密鍵をサーバーからクライアントへコピーした。
tar cvzh cert.tgz /etc/letsencrypt/live/log.example.com/ rsync ~/cert.tgz cliet:~/cert.tgz ssh client "tar zxvf cert.tgz" ssh client "rsync -avz ./etc/ /etc/" ssh client "chown -R syslog /etc/letsencrypt/live/log.example.com/"
クライアント側でも同じように証明書と鍵の設定を入れていく。(使いまわし)
global( DefaultNetstreamDriver="ossl" DefaultNetstreamDriverCAFile="/etc/ssl/certs/ISRG_Root_X1.pem" DefaultNetstreamDriverCertFile="/etc/letsencrypt/live/log.example.com/fullchain.pem" DefaultNetstreamDriverKeyFile="/etc/letsencrypt/live/log.example.com/privkey.pem"
ログ送信先に設定を書く
*.* action( type="omfwd" (略) ## 送信先 target="192.168.1.240" Port="514" Protocol="tcp" ## 暗号化設定 StreamDriver="ossl" StreamDriverMode="1" StreamDriverAuthMode="x509/fingerprint" StreamDriverPermittedPeers="SHA1:31:69:6D:7E:E0:2xxxxxxxxxx" )
出来上がった設定がこちら。
global( DefaultNetstreamDriver="ossl" DefaultNetstreamDriverCAFile="/etc/ssl/certs/ISRG_Root_X1.pem" DefaultNetstreamDriverCertFile="/etc/letsencrypt/live/log.example.com/fullchain.pem" DefaultNetstreamDriverKeyFile="/etc/letsencrypt/live/log.example.com/privkey.pem" preserveFQDN="off" ) *.* action( type="omfwd" queue.type="LinkedList" queue.filename="raspi-ubuntu_fwd" action.resumeInterval="30" action.resumeRetryCount="-1" queue.spoolDirectory="/var/lib/rsyslog" queue.saveonshutdown="on" target="192.168.1.240" Port="514" Protocol="tcp" StreamDriver="ossl" StreamDriverMode="1" StreamDriverAuthMode="x509/fingerprint" StreamDriverPermittedPeers="SHA1:31:69:6D:7E:E0:2xxxxxxxxxx" ) }
再起動して確認
サーバークライアントの双方を再起動
$ssh server sudo systemctl restart rsyslog $ssh client sudo systemctl restart rsyslog
エラー
最初に苦労したエラーがこちら。
9月 05 18:34:38 acid rsyslogd[645709]: unexpected GnuTLS error -12 in nsd_gtls.c:2173: A TLS fatal alert has been received. [v8.2102.0 try https://www.rsyslog.com/e/2078 ]
ossl
ではなく、gtls
設定した場合に出るエラー。gtls でCertbot証明書を使おうとしても無理だった。
その他の設定
MaxSessions="10"
MaxSessionsでTCPのセッション数を絞ってもいいかもしれない
TCPKeepAliveなども設定できる。詳しくはマニュアルに https://rsyslog.readthedocs.io/en/latest/configuration/modules/imtcp.html
暗号化を tcpdump で見てみる
暗号化した経路と、被暗号化の経路を2つ作って、見比べてみた。
logger -p syslog.info -t TEST $(printf %05d $RANDOM)." Hello from sender "
暗号化した設定のクライアントのパケットは、パケットキャプチャで読めないことがわかる。
syslog転送は基本的に平文である。
ログに、パスワードなどがうっかり混じる可能性があるので、重要なサーバーのログを転送するときは暗号化をしないとやばいのである。
ストレージも暗号化
パケットを暗号化しても、Syslogサーバ(ログ転送先)のストレージが暗号化されてないと、サーバーを物理的に強奪された場合、ログが誰からでも読めてしまう。ログ転送先のストレージの暗号化や侵入対策にも気配りが必要。
ログがあると、自分が何をしていたのか動かぬ証拠になってしまう。というより、どのようなソフトのどのバージョンををどのように使って、どのようなエラーが出てるのかから、侵入者に思わぬ脆弱性を発見されてしまうという危険性につながる。
過去にも、G◯hubがログ転送に平文箇所があり従業員が読める状態でパスワードが保存されてしまっていた。みたいな例があり、ログ転送の設定ミスは後を絶たない。
あとは
nginx にプロキシさせるとかですかね。
certbot は特に何もせずに自動更新が入るが、サーバー・クライアントの双方でCertbotを使うのはちょっと手間かもしれない。ローカルに認証局を作成するのもありだったか。