エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

k8sハニーポットとログ出力の話

本記事はエムスリー Advent Calendar 2019 - Qiitaの24日目の記事です。

こんにちは。エムスリーエンジニアリンググループの木村です。 業務ではBIRというチームでアンケートシステムの開発をやっています。

今回は個人的に構築しているk8sハニーポットシステムとそこで生じたログ出力の問題について話をしようと思います。

ハニーポット

まずハニーポットとは何かですが、一言でいうとインターネットからの攻撃を敢えて受けるサーバです。

通常通用されるサーバでは、ファイアーウォールやアンチマルウェアソフトウェアを利用して攻撃を防御し、サービスを安定して運用することが求められますが、ハニーポットはその逆です。

敢えて攻撃を受け、それを記録することによりネットワーク上でどのような攻撃が流行しているのか、その手口などを観測・分析することが目的になります。

ネットワーク構成

現在の私の自宅のネットワーク構成はざっくりと次の構成図のようになっています。

f:id:itto_ki:20191223104458p:plain
自宅ネットワーク構成図

ノードの構成ですがk8sクラスタはRasberry Pi 4 * 4台の物理サーバで構成しており、1台がmasterノード、3台がworkerノードになっています。

一方、ネットワークの構成ですがルータ1がインターネットに繋がっており、 このルータ1が構成するLAN(図の青枠部)が自宅のメインネットワークとなっています。 またルータ1のDMZ(DeMilitarized Zone、図の赤枠部)にルータ2を接続しています。 つまり自宅内から確立されたコネクションを除く、インターネットからの通信を全てルータ2で受けています。 更にこのルータ2は設定により指定したポート番号への通信を全てk8sクラスタにフォワーディングしています。

まとめるとインターネットからの特定のポート番号への通信(攻撃)は全てk8sクラスタが受けています。 このk8sクラスタに対して様々なハニーポットソフトウェアをコンテナ化してデプロイすることで自宅ネットワークへの攻撃を記録しています。

ついでに言うと、k8sクラスタにはルータ2に接続されているインタフェースとは別に管理用のインタフェースもあり、k8sへのデプロイはこの管理用インタフェース経由でメイン端末から行っています。

ルータを2つに分けている理由ですが、家の壁から生えている同軸ケーブル対応のルータがプロバイダから借りているルータ1しかない、 ただルータ1は機能が貧弱なのでポートフォワーディング等の設定を細かく行うにあたっては機能不足で別のルータが必要等といった理由です。 ルータ2はEdgeRouter Xという安価かつ高性能なルータを利用しています。

ログ出力とその問題

ハニーポットの運用においてログ出力は重要です。 攻撃者のIPアドレスや攻撃に掛かった時間、使用されたソフトウェアなどの様々な情報がログに残されていきます。

私が利用したかったハニーポットソフトウェアの1つにSSH、telnetハニーポットのcowrieがあるのですが、 これも当然ログ出力機能が備わっていました。

しかし、cowrieでは設定ファイルへ出力先ファイル名を指定することでログの出力先ファイルを指定できるのですが、 この出力先ファイルを標準出力stdoutと指定するとエラーとなりました。 どうやらスペシャルファイルはログの出力先に指定できないようです。

一方、The Twelve-Factor App (日本語訳)によればログ出力はイベントストリームとして扱われるべきです。 コンテナアプリケーションにおいては標準出力stdoutに出力すべきということになります。 k8sクラスタにおいてはログ出力をコンテナの標準出力stdoutとすることでkubectl logsなどのコマンドでも簡単にログが確認できるようになります。

色々と解決策を考え、cowrieをフォークして標準出力stdoutへのログ出力ができるようにする、fluentd等のログ転送アプリケーションSidecar方式で利用する等の方法も考えましたが、今回は出力先を変えたいだけなのでもう少し手軽に解決したいと思いました。

supervisord

そこで利用したのがsupervisordです。

supervisordはプロセスのデーモン化を行うツールですが、 設定ファイルに記述することでデーモン化したプロセスのログ出力先を変更することも可能です。

以下のように記述することでcowrieが通常ファイルに書き出したログを標準出力stdoutにリアルタイムに出力できます

[supervisord]
nodaemon=true

[program:cowrie]
command=/cowrie/cowrie/bin/cowrie start -n

[program:tail]
command=tail -F /cowrie/cowrie/var/log/cowrie/cowrie.json
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

やっていることは単純でまずデーモン化するプロセスにcowrieを指定します。 その後、tailコマンドでcowrieがログを書き出したcowrie.jsonファイルを読み、 その出力先をstdout_logfileで標準出力stdoutに指定しています。

Dockerfileではこの設定ファイルをコンテナ内にコピーした後、 ENTRYPOINTとしてsupervisordを指定しています(以下、一部を抜粋)。

COPY supervisord.conf /etc/supervisord.conf

VOLUME ["/cowrie/cowrie/var", "/cowrie/cowrie/etc/"]
ENTRYPOINT ["supervisord"]
CMD ["-c", "/etc/supervisord.conf", "-e", "critical"]

上の設定ファイルと合わせると、 コンテナイメージを起動するとまずsupervisordが起動し、 supervisordがcowrieをデーモンとして起動することになります。 その後cowrieに対する通信のログがファイルに書き込まれていきますが、これをsupervisordがtailコマンドで標準出力stdoutにも出力している訳です。

これでcowrieのログが標準出力stdoutに出力できました。 kubectl logsなどのコマンドによるログ出力の確認も容易です。

まとめ

自宅で運用しているk8sハニーポットクラスタとコンテナのログ出力の話をしました。

ログ集約やログ転送ではfluentdが利用されることが多いですが、 今回のログの出力先を変えたいだけな状況で簡単にやりたい、Raspberry Pi等のリソースが限られたクラスタで軽量に動かしたい時にはfluentdのSidecar方式で実現するよりも適しているのではないでしょうか。

とりあえずコンテナログとして出力しておけば、 後から外部のストレージ等にログ転送をしたくなった場合でもアプリケーションのマニフェストファイルをいじることなく、 fluentd公式のDaemonSetマニフェストファイルをほとんどそのまま使うことも可能だと思います。

We are hiring!

エムスリーではk8sやコンテナなど様々な技術スタックを持つエンジニアが活躍しています。 社員とカジュアルにお話することもできますので、興味を持たれた方は下記よりお問い合わせください。

open.talentio.com

jobs.m3.com