お疲れ。最近、KubernetesクラスターやDocker環境の構築で「とりあえずコンテナ動いたからヨシ!」なんてやっていないだろうな?
インフラの現場で数々の修羅場をくぐってきた私から言わせてもらうと、コンテナのセキュリティを語る上で「カーネルの保護」を忘れているシステムは、外側だけ鉄壁にして玄関の鍵を開けっぱなしにしているようなものだ。
Dockerやコンテナ型仮想化の根本的な前提を思い出してほしい。コンテナはホストOSのLinuxカーネルを共有している。つまり、もしアプリケーション層に致命的な脆弱性があり、攻撃者にリモートコード実行(RCE)を許した場合、彼らはそこから直接ホストカーネルへアタックする足がかりを手に入れることになる。
今回は、そんな最悪のシナリオを根底から断つための技術、「Seccomp(Secure Computing Mode)プロファイルの適用」について、実戦の泥臭い知見を交えて徹底的に解説しよう。
—
1. 攻撃者が狙う盲点:なぜ「カーネルエクスプロイト」は恐ろしいのか?
Webアプリケーションの脆弱性(SQLインジェクションやRCEなど)から侵入に成功した攻撃者は、次に何を狙うか? 答えは「権限昇格(Privilege Escalation)」だ。
通常、コンテナ内ではroot権限を持っていたとしても、いくつかの機能(capabilities)は削られている。しかし、もしLinuxカーネル自体に未修正の脆弱性(CVE)が存在していたり、不必要に危険なシステムコールが許可されていたりするとどうなるか。
攻撃者は ptrace や kexec_load、あるいは不適切な unshare などのシステムコールを悪用し、コンテナの境界をいとも簡単にブチ破ってホストOSのroot権限を奪う。これが「コンテナエスケープ」のメカニズムだ。
デフォルトのDockerが抱えるリスク
Dockerのデフォルト設定でも、実は基本的なSeccompプロファイルが適用されており、約300個のシステムコールのうち、およそ40〜50個の危険なコール(システムに致命的な影響を与えるもの)がブラックリスト方式でブロックされている。
だが、考えてみてほしい。「ブラックリスト方式」は、セキュリティの世界では常に後手の対策だ。新しく追加されたシステムコールや、開発者が気づいていないニッチなコールに脆弱性があれば、そこを突かれる。
真にセキュアな環境を目指すなら、必要なシステムコール以外はすべて遮断する「ホワイトリスト方式(Custom Seccomp Profile)」を導入するべきなのだ。
—
2. 実践:カスタムSeccompプロファイルの設計と実装
では、実際にコンテナが実行可能なシステムコールを極限まで絞り込むJSONプロファイルを作成しよう。
今回は、一般的なWebアプリケーション(例:Python/FlaskやNode.js、PHPなど)が動作するために「最低限必要なシステムコール」だけを許可し、それ以外を容赦なくKillする厳格なプロファイルを定義する。
以下のJSONファイルを strict-seccomp.json としてプロジェクトのルートに配置してほしい。
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_ARM64"
],
"syscalls": [
{
"names": [
"accept4",
"epoll_wait",
"epoll_ctl",
"epoll_create1",
"fcntl",
"read",
"write",
"close",
"futex",
"clone",
"set_robust_list",
"rt_sigaction",
"rt_sigprocmask",
"prlimit64",
"ioctl",
"getsockname",
"getpeername",
"setsockopt",
"getsockopt",
"connect",
"bind",
"listen",
"nanosleep",
"clock_gettime",
"exit_group"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
プロファイルの解説
defaultAction:SCMP_ACT_ERRNO
ホワイトリストに明示されていないすべてのシステムコールをブロックし、呼び出し元にエラー(EPERM または ENOSYS)を返す。最強の拒絶設定だ。
architectures
ターゲットとなるCPUアーキテクチャを指定。クロスアーキテクチャ攻撃を防ぐためにも明示的に絞る。
syscalls(ALLOW)
ネットワーク通信、メモリ管理、スレッド制御など、Webサーバーが最低限動くために必要なシステムコールだけに厳選している。例えば、コンテナ内から任意のプロセス実行やモジュールロードを行うような危険なシステムコールは一切含まれていない。
—
3. アプリケーション層での検証とコンテナへの適用
作成したプロファイルを、実際のDockerコンテナ、あるいはKubernetes環境に適用する方法を見ていこう。
Docker CLIでの適用例
Dockerで起動する際は、--security-opt オプションを指定するだけでいい。
docker run --rm \
--security-opt seccomp=./strict-seccomp.json \
-p 8080:8080 \
my-secure-web-app:latest
もし、このプロファイルで許可されていないシステムコール(例えば、デバッグ用のツールや、予期せぬシェルコマンドの実行など)がコンテナ内で実行されると、カーネルは即座にそのシステムコールを拒否し、アプリケーションはクラッシュするかエラーを吐く。これにより、攻撃者の足場を完全に奪うことができる。
Kubernetes(K8s)での適用例
本番のクラウドネイティブ環境では、KubernetesのPodセキュリティ標準や、SeccompのPodセキュリティポリシー(またはSeccompProfile)として定義する。
apiVersion: v1
kind: Pod
metadata:
name: secured-api-pod
namespace: production
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/strict-seccomp.json
containers:
- name: api-server
image: my-secure-web-app:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
Kubernetesの securityContext で allowPrivilegeEscalation: false を併用し、ルートファイルシステムを読み取り専用(readOnlyRootFilesystem: true)にすることで、Seccompとの相乗効果で鉄壁の要塞が完成する。
—
4. 現場のセキュリティチーフからの教訓
Seccompプロファイルの導入において、多くのエンジニアが陥る罠が「どのシステムコールを許可すればいいか分からない」という問題だ。開発環境では動いていたのに、本番環境で突然アプリが落ちる原因の多くは、ログ出力ライブラリやフレームワークが裏で予期せぬシステムコール(例: getpid や uname など)を叩いているためだ。
現場での鉄則:
1. 監査モード(Audit Mode)を活用せよ
いきなり SCMP_ACT_ERRNO でブロックするのではなく、まずは SCMP_ACT_LOG(または SCMP_ACT_TRACE)を使って、アプリがどのようなシステムコールを実際に消費しているかをカーネルログ(/var/log/kern.log や dmesg)から洗い出すこと。
2. フレームワークのアップデートに追従せよ
Node.jsやPythonのバージョンが上がると、内部で使用するシステムコールが変わることがある。CI/CDパイプラインの中で、セキュリティスキャンと合わせてSeccompの動作テストを組み込むのがプロのやり方だ。
「面倒くさい」を理由にセキュリティを妥協した瞬間、そこがあなたのシステムの最大の弱点になる。コンテナの要塞化は、単なる設定作業ではなく、インフラエンジニアとしての誇りだ。
さあ、今すぐ手元の docker-run コマンドやK8sマニフェストを見直し、カーネルレベルの防御を実装してくれ。健闘を祈る。
コメント