およそ1ヶ月前にリリースされたsystemd 235ではサービスごとにIPトラフィックを計算したり、アクセスコントロールができるようになったらしい。 元ネタはレナートのブログ記事 IP Accounting and Access Lists with systemd 。
本稿では、この記事の詳細を紹介する。
IPアカウンティング
v235以前でも、systemdはすでにユニットごとのリソース管理のフックを様々な種類のリソースに対し提供してきた: 消費されたCPU時間、ディスクI/O、メモリ使用量やタスクの数などである。 v235では別の種類のリソースをsystemdでユニットごとに管理できるようになった: ネットワークトラフィック(特にIP)である。
これに伴い、ユニットに追加された設定項目は次の3つ
IPAccounting=
- booleanを取る。これが有効なユニットでは関連するプロセスによるIPトラフィックの送受信がバイト数およびパケット数でカウントされる。
IPAddressDeny=
,IPAddressAllow=
3つのオプションはLinux 4.11で導入されたカーネルの機能であるcontrol group eBPF フックを薄くラップするものである。 実際の仕事はカーネルにより実行され、systemdはこの機能を設定する新しい設定の多くを提供するだけである。
とのこと。
ブログでは次のようなservice unitを作成し、デモをおこなっている。
[Service] ExecStart=/usr/bin/ping 8.8.8.8 IPAccounting=yes
デモを見る限り、たしかにパケットのin/outがカウントされている。
デモ用サービスを停止するときに
表示された最後の行は興味深く、アカウンティングデータを表示している。 これは実際は構造化されたログメッセージであり、サービスのメタデータのフィールドにはより包括的な生のデータがある。
その行は
Received 49.5K IP traffic, sent 49.5K IP traffic
というもの。
journalctl -u $SERVICE_NAME -n 1 -o verbose
としたら、上記のメッセージが格納されているのが確認できるよ、
MESSAGE_ID
で引っ張れば、このメッセージが取り出せるよ、と書いているが、何が興味深い
のか正直よくわからない。
デモでは停止後にメッセージを取りに行っているが、リアルタイムでも取れるんだろうか。
個人的には、次の部分の方が興味深かった。
systemd-run -p IPAccounting=yes --wait wget https://cfp.all-systems-go.io/en/ASG2017/public/schedule/2.pdf
とすると、一時的なサービスとしてIPアカウンティングを有効にしながらコマンドが実行できるようである。なにそれ面白い。
あと、ちゃっかり、自分が登壇する The user-space Linux technologies conference のPDFを読者にダウンロードさせてるのが面白い。
(ところで、もうチケットは予約したかな?もうそろそろ売り切れちゃうから、急いで!)
じゃねーよw
これの応用編として
systemd-run -p IPAccounting=1 -t /bin/sh
で新しいシェルを起動させて、コマンドを打つとかをやっている。なにそれ面白い。
なお、すでに起動済みのサービスに対し、IPアカウンティングを有効にする方法もあるようで、systemctl
より設定する。
systemctl set-property foobar.service IPAccounting=yes
/etc/systemd/system.conf
にDefaultIPAccounting=
を設定すると、すべてのサービスでIPアカウンティングが有効になる。
IPアクセスリスト
設定されたユニットのパケットの通過の可否は次の順で判断される。
1. IPAddressAllow=
に設定されたIPアドレスとの通信は許可
2. IPAddressDeny=
に設定されたIPアドレスとの通信は拒否
3. いずれにもマッチしない通信は許可
後述するように、TCP wrapperに代わる使い方を想定しており、これに合わせているんだろうなという感想。
こちらもデモをおこなっている。
それぞれのユニットに個別にIPアクセスリストを設定できるだけでも、すでにかなりいい。 とはいえ、典型的にみんながやりたいことは、これを包括的に設定すること、つまり、別個のユニットだけじゃなく、いくつかのユニットをまとめて一気に、あるいは、それだけじゃなく、システムまるごと設定することだろう。 systemdでは
.slice
ユニットを使うことでこれが可能になる。 (systemdをあまり知らない人のために書いておくと、sliceユニットはリソース管理のためにサービスを階層的な木にまとめるための概念のことだ) IPアクセスリストはユニットにとって事実上、ユニット自身に設定された個別のIPアクセスリストの組み合わせであったり、それの含まれているすべてのsliceユニットのそれらであったりするのである*1。
続くデモでは、デフォルトでシステムのサービスが割り当てられるsystem.slice
に対し、IPアクセスリストを設定している。
使用例
レナートはこのIPアクセスリストのユースケースとして、次のような例を挙げている
TCP Wrapperの置換
TCP Wrapperに対するメリットとして、あるサービスのIPソケットすべてに無条件で適用できる点、サービス側のコードに何も入れなくて良い点を挙げている。
逆にデメリットはTCP Wrapperが提供する機能のうち、カバーできてないものがある点を挙げている。特に、DNS名で設定する方法がないことを挙げているが、レナート本人は「でも、正直に言ってしまうと、ネットワーキングを制限するためにネットワーキングをおこなうってやっぱりかなり疑わしい機能だ。少なくとも自分には」と書いている。
IPファイヤーウォールの置換
NetFilter/iptablesに対するメリットとして、サービスという概念が理解できる点、そのため、コンテキストを理解している点を挙げている。
ただし、単純に比較もできないと書いている。 systemdのIPアクセスリストはサービスのことはわかるがそれ以外はわからないが、典型的なファイヤーウォールはピュアなIPに注目している分、用途が広いと述べている。
ディストリ/ベンダー提供のシステムサービスをセキュアにする
ネットワークにつなぐ必要のないサービスはIPAccessDeny=any
(とIPAddressAllow=localhost
)を設定することで、ネットワークに繋がせないようにできる。
セキュアにコマンドを実行
あまり信用できない実行ファイルをsystemd-run
に-p
でIPAccessDeny=
とIPAccessAllow=
とを渡せば、ネットワーク的に安全な状態でコマンドを実行できる。
ノート
socket activationとの組み合わせ
ソケットアクティベーションとの組み合わせが可能である。 ただし、ソケット側でIPアカウンティングを設定した場合、(当たり前だが)ソケット側でカウントされるため、ソケットがサービスに渡されても、カウントがそちらで続いていることに注意が必要。 IPアクセスリストもソケット側とサービス側と別個にカーネルで管理される。そのため、例えば、socketユニットでは比較的オープンなアクセスができるアクセスリストを設定し、serviceユニット側で制限のかかったアクセスリストを設定することで、socketユニットで設定されたソケットをサービスを起動・停止の唯一の方法とすることができる*2。
他のアドレスファミリーとの組み合わせ
IPアカウンティングとアクセスリストはIPソケットのみに適用され、他のアドレスファミリーには適用されない。
つまり、AF_PACKET
*3ソケットはカバーされない。IPアクセスリストとRestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
と組み合わせるとより強固になる。
systemd-runの他のアカウンティング
CPUAccounting=
, IOAccounting=
をつけると、これらのアカウンティングも見れる
オーバーヘッド
ゼロというわけではないが、最新のカーネルではeBPFの実行はすでに最適化されている。今後も最適化が進んで、コストは無視できるようになるはずと述べている。
IPアカウンティングの再帰
現時点では再帰的ではない。つまり、sliceユニットに設定して、その配下のユニットのIPアカウンティングをまとめるといったことはできない。
将来的には実装したいが、カーネル側での実装がまず進む必要がある。
PrivateNetwork との関係
PrivateNetwork=
とIPAccessDeny=any
とは似たような機能で、ユニットがネットワークを使用できないようにすることができる。ただ、前者はLinuxネットワーク名前空間を使って実装されているため包括的である。