- systemd は強力なサービス管理機能を提供するが、デフォルト設定はセキュリティよりも使いやすさに最適化されているため、別途ハードニングオプションの適用が必要
systemd-analyze security コマンドで、サービス全体または特定のサービスのセキュリティ露出指標を分析し、優先順位を決められる
- サービス単位で適用できるさまざまなセキュリティオプションがあり、これは
/etc/systemd/system/ServiceName.service.d/override.conf などを通じて個別に修正できる
- 主なオプションには
ProtectSystem, PrivateTmp, NoNewPrivileges, SystemCallFilter, MemoryDenyWriteExecute など、プロセス権限やリソースアクセスを制限する項目が含まれる
- 完全なセキュリティを目指すのではなく、外部公開サービスを優先的にハードニングしてリスクを減らし、self-hosting 環境でも大きな効果が期待できる
systemd 概要
- systemd はサービス管理において非常に完成度が高く堅牢な方式を提供する
- ただし、セキュリティよりも即時の使いやすさを優先しているため、デフォルト設定は緩めになっている
- 本文書は、systemd サービスユニットと podman quadlet に適用できる複数のセキュリティ強化オプションを紹介し、侵害の可能性を減らし、侵害発生時の被害範囲を最小化することを目的とする
- すべてのサービスへ一括適用するためのガイドではなく、それぞれのサービスの特性や必要機能に合わせた個別の実験、ログ確認、調整が必要
- インフラのセキュリティ責任は全面的に運用者にあり、本書は参考ツールである
systemd セキュリティ分析
systemd-analyze security コマンドでサービス全体のセキュリティ状態を確認したり、特定サービス(例: sshd.service)の詳細設定を分析したりできる
- 出力にはチェック有無、機能名、説明、Exposure スコアが含まれ、Exposure が高いほど危険度が高い
- セキュリティオプションは
[Service] セクション(systemd)または [Container] セクション(podman quadlet)に追加設定できる
systemctl edit ServiceName.service によって override ファイルを作る方法が推奨され、失敗した場合は必要な権限を確認して調整する必要がある
サービスのセキュリティオプション
- systemd は多様なセキュリティオプションのキーワードを提供しており、
man systemd.exec 5, man capabilities 7 などで確認できる
- 代表的なセキュリティ関連オプション
ProtectSystem → ファイルシステムを読み取り専用に制限するオプション
ProtectHome → /home, /root, /run/user へのアクセスを遮断するオプション
PrivateDevices → 物理デバイスへのアクセスを遮断し、/dev/null などの仮想デバイスのみ許可するオプション
ProtectKernelTunables, ProtectKernelModules, ProtectKernelLogs → カーネルリソースへのアクセスを遮断するオプション
NoNewPrivileges → setuid/setgid などによる新規権限取得を防ぐオプション
MemoryDenyWriteExecute → 書き込み可能かつ実行可能なメモリの同時使用を防ぎ、一部の JIT 言語では問題が起きる可能性がある
SystemCallFilter → 許可するシステムコールを制限するオプションで、違反時にプロセス終了または EPERM を返せる
各オプションの説明
- ProtectSystem:
strict 設定時はファイルシステム全体を読み取り専用でマウントし、/dev, /proc, /sys には別途保護オプションが必要
- ReadWritePaths: 一部のパスだけを書き込み可能に戻す設定
- ProtectHome:
/home, /root, /run/user へのアクセスを遮断
- PrivateDevices: 物理デバイスへのアクセスを無効化し、
/dev/null などの Pseudo デバイスのみ許可
- ProtectKernelTunables:
/proc, /sys を読み取り専用にする
- ProtectControlGroups: cgroup への読み取り専用アクセスのみ許可
- ProtectKernelModules: カーネルモジュールの明示的なロードを禁止
- ProtectKernelLogs: カーネルログバッファへのアクセスを制限
- ProtectProc:
invisible 設定時、他ユーザー所有のプロセスを /proc/ から隠す
- ProcSubset: 特定の PID 関連項目以外の内容を
/proc から遮断
- NoNewPrivileges: setuid、setgid、ファイルシステム capability による新たな権限昇格を防ぐ
- ProtectClock: システム/ハードウェアクロックへの書き込みを遮断
- SystemCallArchitectures:
native 設定時は x86-64 などネイティブ syscall のみ許可
- RestrictNamespaces: コンテナ向けの名前空間を制限
- RestrictSUIDSGID: ファイルの setuid, setgid ビット設定を遮断
- LockPersonality: 実行ドメインの変更を防ぐ(古いアプリケーションなどでのみ必要)
- RestrictRealtime: リアルタイムスケジューリングを制限(一部の特殊用途サービスのみ必要)
- RestrictAddressFamilies: 許可するソケットアドレスファミリを制限(例: AF_INET, AF_INET6, AF_UNIX などを指定)
- MemoryDenyWriteExecute: 書き込み+実行可能なメモリ領域の追加生成を防ぐ(JIT を使うサービスは注意)
- ProtectHostname:
sethostname, setdomainname syscall の使用を禁止
- SystemCallFilter: サービスごとに syscall の許可/遮断を設定でき、細かくフィルタリング可能
- グループ、個別 syscall、許可/遮断方式などを調整できる
- 違反時に終了の代わりとして EPERM などのエラーコードを返す設定もサポート
- 一覧は
systemd-analyze syscall-filter または man systemd.exec(5) で確認できる
~ 接頭辞でリスト全体を否定できる(例: CapabilityBoundingSet=~CAP_SETUID など)
SystemCallFilter 制限の調整プロセス
auditd を使えば、サービス失敗時にどの syscall がブロックされたかをログで確認できる
sudo ausearch -i -m SECCOMP -ts recent を実行後、syscall 値を確認
- 該当 syscall または関連グループを
SystemCallFilter に追加し、順次問題を解決できる
セキュリティ強化の適用優先順位と運用のヒント
- すべてのサービスに全部適用する必要はない
- 脅威モデルとリスク管理が核心であり、特に外部公開サービス(httpd, nginx, ssh など)は必ず検討したい
- カスタムコマンド、timer ユニット(旧来の cron 代替)などにも先行適用が効果的
- 複雑でないサービスほど細かな調整を行いやすい
チェックリスト: 推奨セキュリティオプションの組み合わせ(初期適用の優先順位)
ProtectSystem=strict
PrivateTmp=yes
ProtectHome=yes または ProtectHome=tmpfs
ProtectClock=yes, ProtectKernelLogs=yes, ProtectKernelModules=yes
RestrictSUIDGUID=yes
UMask=0077
LockPersonality=yes
RestrictRealtime=yes
MemoryDenyWriteExecute=yes
DynamicUser=yes または User を root 以外の特定ユーザーに指定
- 上記項目は一般にサービスへの影響がほとんどなく使える組み合わせ
- さらに syscall フィルタリング(
SystemCallFilter)まで適用するには詳細なテストが必要
Traefik の設定例
- コンテナベースの Traefik サービスを systemd quadlet で実行し、多数のセキュリティオプションを適用した事例
ProtectSystem=full, ProtectHome=yes, SystemCallFilter=@system-service @mount @privileged などを適用
CapabilityBoundingSet=~CAP_SETUID CAP_SETPCAP で特定権限を削除
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK など、ネットワークアクセス制限を適用
結論
- systemd のセキュリティ強化オプションは、Unix 系システム管理者ならツールボックスに一つ入れておく価値のある実用的な手段
- 完璧なセキュリティ対策ではなく、リスクを減らすためのツールとして活用すべきであり、すべてのサービスに無差別にセキュリティ設定を適用する必要はない
- 特に self-hosting 環境の管理者が活用する場合、セキュリティ水準の向上に大きく役立つ
- 「完璧さより実用性」を優先し、業務や環境に合う範囲で部分的にでも適用することを推奨
1件のコメント
Hacker News の意見
strace のプロファイリングによって自動化された systemd サービスのハードニングを実現できる点が興味深いと思う
https://github.com/desbma/shh
私が見つけた良い方法がある。例では ProtectSystem= は使っていなかったが、
TemporaryFileSystem=/:ro, BindReadOnly=/usr/bin/binary /lib /lib64 /usr/lib usr/lib64 のようにすると、
必要なバイナリと読み取りたいパスだけを含められる
ProtectSystem= は現時点ではこの動作と互換性がない
詳しくは こちら を参照
この方式は、エラー発生時にメールを送るなど追加動作が必要なサービスには問題になるかもしれないと思う
昨日投稿された systemd ハードニング関連の記事よりはるかに現実的で、すぐ適用できる良いヒントが多い
昨日の記事のコメントで、もっと実践的な例を出そうと頑張っていたのだが、今日の記事は実用的な内容を見事に整理していて、systemd で隔離とセキュリティを素早く簡単に強化する方法を教えてくれる
素晴らしい記事だと思う
参考までに昨日の記事も残しておく
https://us.jlcarveth.dev/post/hardening-systemd.md
https://news.ycombinator.com/item?id=44928504
一部のブラウザでは証明書エラーのためアクセス自体ができない
共有してくれてありがとう
systemd-analyze を --user フラグ付きで使えば、systemd ユーザーユニットのセキュリティを確認できる("systemd-analyze --user security")
コンテナを Podman に移行しながら systemd をより多く使うようになったが、このツールは systemd ユニット/コンテナサービスのセキュリティ向上に大いに役立つはずだ
昔の init スクリプトはどれもバラバラだったので、こうした一貫したハードニング作業は不可能だった
私は Linux に比較的遅く入門したので、systemd のないシステムは想像しにくく、systemd のないシステムは扱いづらすぎた
最近は "unshare" というツールを見つけて、/nix 全体を RW で再マウントするといった実験を他のプロセスに影響を与えずにできた
systemd は使い勝手がやや無骨ではあるが、代替は正直自分にとっては Windows しかないと思う
なぜ Linux ディストリビューションではこうしたセキュリティスイッチをデフォルトでより多く有効にしないのか気になる
保守的にハードニングすることに何か欠点があるのだろうかと思う
多くのユーザーにとっては設定が多すぎて複雑すぎるのかもしれない
あまりに攻めた設定変更をすると、意図せず既存設定が壊れることがある
たとえば NetworkManager をハードニングしたなら、IPv4 と IPv6 の両方で接続できるか、dns=systemd-resolved と dns=default モードが正常に動くか、ModemManager とセルラー連携、openvpn や cisco anyconnect プラグイン、NetworkManager-dispatcher hook など、さまざまな部分を一つひとつ検証しなければならない
さらに、どれだけのディストリビューション管理者が、自分の管理するパッケージのスイッチをどの程度まで変えてもユーザー環境の 0.01% 以上が壊れないと確信できるかという問題もある
こうしたフラグをディストリビューションで管理すると、アップストリームのリリースのたびに互換性問題が付いて回るし、逆にアップストリーム側が設定する場合は下位互換性のためにさらに慎重にしか使えない
この質問は「なぜディストリビューションではデフォルトで MAC(SELinux など)を強く使わないのか?」に近い
sshd のようなものも、もっと制限した方がよいのは確かだが
こうした理由からメジャーディストリビューションでは負担が大きい
SELinux や AppArmor も同様で、メンテナーが投資対効果を低いと見ることが多い
中核システムサービスの正常動作を各パラメータごとに一つひとつ統合テストするだけの能力やリソースがないことも大きな理由だ
関連する議論
https://news.ycombinator.com/item?id=29995566
systemd-analyze security の結果はディストリビューションごとに異なる
desbma/shh は strace で収集した内容から SyscallFilter など単位ごとのルールを自動生成してくれ、SELinux の audit2allow に似ている
ただし、strace を本番環境にインストールすることには議論の余地がある
https://github.com/desbma/shh
私もよく分からないが、一部の設定は新しく追加されたものなので、多くのユーザーはよく知らないのかもしれない
systemd の達人ばかりではないし、設定を有効にすると以前のバージョンの systemd では正常に動かない危険もある
SELinux や AppArmor などさまざまな機能はあるが、多くのディストリビューションや開発者、ユーザーがそこまで必要だと感じていないため、自動適用が難しい部分だ
セキュリティ強化のためのオプションが多すぎるので、一般的なサービスごとのハードニング例をまとめたリポジトリがあるとよいと思う
ユーザーが共通して適用するハードニングスクリプトを活用することが多いが、意外にも権限をもっと広く設定しないと例外的な状況が起きないことに気づく
メインストリームのディストリビューションでどうパッケージングし、どうハードニングしているかを参考にするのが最も有用だ
そうしたハードニング手法はたいてい十分にテストされていることが多いので、postgresql などの強化例が気になるなら Debian、Ubuntu、RHEL のパッケージから始めるのがよい
systemd が提供する優れたセキュリティ機能の一つがクレデンシャル管理だ
これにより、環境変数やファイルシステムに保存するより安全にアプリケーションへ認証情報を渡せる
Vault などがない環境、たとえば個人プロジェクトでは常にこの方法を好んで使っている
この機能と連携する Go パッケージも自作した
systemd での credentials
credential-go パッケージ
nodejs や npm のように、2 行のコードでもパッケージ化する文化みたいだと思う
実際には
left-pad よりも複雑ではない
Go コミュニティではもともと依存を減らし、不要な抽象化(関数呼び出しなど)を避けるのが美徳だったと思っているが
こういう簡単な動作はみんなその場で自分で書いていたものだ
この credential の受け渡し方式が、fork された子プロセスにまで認証情報が継承されるのをどう防ぐのか気になる
本当に役立つ記事だ
systemd の各オプション一覧や、「man を参照して幸運を祈る」といった助言も気に入った
systemd は本当に素晴らしいので、自分のサーバーに積極的に展開したい
ちょっとした小ネタだが、タイトル表記での systemd の正しい表記は systemd だ
SystemD、system D、system d ではなく、systemd が正しい
理由は system daemon だからで、Unix/Linux の伝統どおり小文字の d で終わる名前にしたのだ
私は普段 systemD と呼ぶのをよく見かけるが、なぜそんなに広く使われるようになったのか気になる
systemd で syscall の問題をデバッグするコツが本当に有用だ