はじめに
記事を開いていただきありがとうございます。三菱電機の佐々木です。
本記事では、デーモンレスなコンテナエンジンであるPodmanを対象に、コンテナ向けセキュリティプロファイルの作成を支援するツールについて紹介します。具体的には、seccomp用のoci-seccomp-bpf-hook、SELinux用のudica、およびAppArmor用のaa-logprofを取り上げます。
セキュアなコンテナ環境を構築する際には、複数のセキュリティ対策を組み合わせて適用することが一般的です。代表的な対策を以下に示します。
-
イメージの安全性確保
- コンテナイメージの脆弱性スキャン
- コンテナイメージへの署名・検証
-
ランタイムの保護
- ルートレスモードでの運用
- seccompによるシステムコール制御
- SELinux/AppArmorによる強制アクセス制御
上記の対策の中でも、特に導入の難易度が高いのは、「seccompによるシステムコール制御」と「SELinux/AppArmorによる強制アクセス制御」であると考えられます。その理由として、コンテナ内で実行するプロセスごとに適切なセキュリティプロファイルを個別に作成する必要がある点が挙げられます。
セキュリティプロファイルの作成は、アプリケーションのテスト実行時に記録されたシステムログやAuditログを精査し、必要なルールを1つずつ抽出して定義しなければならず、煩雑な作業になりがちです。このような作業負荷の高さが問題となり、実環境への適用が敬遠されるケースも少なくありません。
こうした問題を解決するために、各Linuxディストリビューションでは、コンテナ向けセキュリティプロファイルの作成を支援するツールが提供されています。以降ではこれらのツールについて使用例を交えながら紹介します。
なお、各コンテナエンジンには予めデフォルトのセキュリティプロファイルが用意されています。しかし、これらは様々なアプリケーションに対して汎用的に適用できるよう、ルールが比較的緩やかに定義されています。そのため、更なるセキュリティ強化が必要な場合には、コンテナ内のプロセスの挙動に合わせて必要最低限のルールを定義したセキュリティプロファイルを個別に作成する必要があります。
セキュリティ機能
本題のコンテナ向けセキュリティプロファイル生成ツールを紹介する前に、生成したセキュリティプロファイルに基づいてコンテナ内のプロセスの挙動を制限する、Linuxカーネルに実装されたセキュリティ機能であるseccomp、SELinux、およびAppArmorについて簡単に説明します。
seccomp
seccompは、プロセスから発行されるシステムコールをフィルタリングする仕組みです。seccompプロファイルにプロセスの実行に必要なシステムコールのみを明示的に許可することで、それ以外の不要なシステムコールを拒否し、カーネルの攻撃対象領域を縮小できます。
SELinux
SELinuxは、プロセスからファイルやソケットなどのシステムリソースへのアクセスを制御する仕組みです。SELinuxプロファイルにプロセスの実行に必要なアクセスのみを明示的に許可することで、それ以外の不要なアクセスを拒否します。万が一プロセスが侵害されてルート権限を奪われた場合でも、SELinuxがアクセス制限を強制し、被害を最小限に抑止できます。
なお、SELinuxはRHEL系Linuxディストリビューションにおいて標準で採用されている強制アクセス制御です。
AppArmor
AppArmorは、SELinuxと同様に、プロセスごとにシステムリソースへのアクセスを制御する仕組みです。SELinuxは制御対象のプロセスにラベルを付与してSELinuxプロファイルを適用するのに対し、AppArmorは制御対象のプロセスの実行パスを指定してAppArmorプロファイルを適用できるため、より直感的にセキュリティプロファイルを適用できるという特徴があります。
なお、AppArmorはDebian系Linuxディストリビューションにおいて標準で採用されている強制アクセス制御です。
seccompとSELinux/AppArmorの併用は可能であり、これらを組み合わせることで多層防御を構成できます。この場合、RHEL系ディストリビューションではseccompとSELinuxの構成が一般的であり、Debian系LinuxディストリビューションではseccompとAppArmorの構成が一般的です。
セキュリティプロファイル生成ツール
ここからは、本記事の本題であるコンテナ向けセキュリティプロファイル生成ツールを紹介します。seccomp用のoci-seccomp-bpf-hook、SELinux用のudica、およびAppArmor用のaa-logprofを取り上げます。これらのツールは、各Linuxディストリビューションの標準リポジトリからDNFやAPTといったパッケージマネージャを用いて容易にインストールすることができます。
| ツール | 対象 | 生成方法 | 特徴 |
|---|---|---|---|
| oci-seccomp-bpf-hook | seccomp | eBPFトレース | 自動生成、継承可能 |
| udica | SELinux | スペック解析 | 設定ベース、ログ補完 |
| aa-logprof | AppArmor | ログ解析 | 対話式、段階的構築 |
oci-seccomp-bpf-hook
oci-seccomp-bpf-hookは、eBPF(extended Berkeley Packet Filter)を用いてコンテナ内のプロセスが発行するシステムコールをトレースし、その結果を基にseccompプロファイルを生成するツールです。
使用例
-
--annotationオプションでseccompプロファイルの出力先パスをof:形式で指定し、生成対象のプロセスを実行するコンテナを起動します。# 以下の例ではnginxコンテナを使用しています。 sudo podman run -d -p 8080:80 --name nginx --annotation io.containers.trace-syscall=of:/tmp/nginx_profile.json docker.io/library/nginx:1.29.3 -
コンテナ内のプロセスに対してテストを実行します。
# 以下の例ではベンチマークツールであるwrkを用いてnginxコンテナに対して負荷を与えています。 # 本来はseccompプロファイルに必要なルールを不足なく抽出するため、アプリケーションのすべての機能を網羅するようにテストを実施する必要があります。 sudo wrk -t2 -c100 -d30s http://localhost:8080/ -
seccompプロファイルを生成するために使用したコンテナを停止または削除します。
# 以下の例ではコンテナを削除しています。 sudo podman rm -f nginx -
出力先パスにseccompプロファイルが生成されていることを確認します。
# 生成されるseccompプロファイル例 sudo cat /tmp/nginx_profile.json {"defaultAction":"SCMP_ACT_ERRNO","architectures":["SCMP_ARCH_X86_64"],"syscalls":[{"names":["access","arch_prctl","bind","brk","capset","chown","clone","close","close_range","connect","dup2","epoll_create","epoll_ctl","epoll_wait","eventfd2","execve","exit_group","faccessat2","fadvise64","fchdir","fchown","fcntl","fgetxattr","fsetxattr","fstat","futex","getcwd","getdents64","getegid","geteuid","getgid","getpid","getppid","getrandom","gettid","getuid","io_setup","ioctl","listen","lseek","mkdir","mmap","mprotect","munmap","newfstatat","openat","pipe2","prctl","pread64","prlimit64","pselect6","pwrite64","read","recvmsg","rename","rseq","rt_sigaction","rt_sigprocmask","rt_sigreturn","rt_sigsuspend","sched_getaffinity","sendmsg","set_robust_list","set_tid_address","setgid","setgroups","setresgid","setresuid","setsockopt","setuid","sigaltstack","socket","socketpair","statfs","sysinfo","umask","uname","unlink","utimensat","vfork","wait4","write"],"action":"SCMP_ACT_ALLOW","args":[],"comment":"","includes":{},"excludes":{}}]}
なお、oci-seccomp-bpf-hookは、--annotationオプションにベースとなるseccompプロファイルをif:形式で指定することで、既存のルールを継承しつつ、不足していたルールを新たに追加して更新することができます。そのため、一度の操作で完全なseccompプロファイルを生成できなくても、テストと調整を繰り返しながら段階的に完成度を高めていく運用が可能です。
sudo podman run -d -p 8080:80 --name nginx --annotation io.containers.trace-syscall="if:/tmp/nginx_profile.json;of:/tmp/nginx_profile_v1.json" docker.io/library/nginx:1.29.3
適用例
--security-optオプションで対象のseccompプロファイルを指定することで、不要なシステムコールが制限されるコンテナを起動できます。
sudo podman run -d -p 8080:80 --name nginx --security-opt seccomp=/tmp/nginx_profile.json docker.io/library/nginx:1.29.3
udica
udicaは、コンテナエンジンによってコンテナにデフォルトで付与されるcontainer_tラベルと類似する基本ルールと、コンテナのスペック情報を解析することで検出されたLinuxケイパビリティ、ネットワークポート、およびマウントポイントに関するルールを組み合わせることで、SELinuxプロファイルを生成するツールです。
使用例
-
生成対象のプロセスを実行するコンテナを起動します。
# 以下の例ではnginxコンテナを使用しています。 sudo podman run -d -p 8080:80 --name nginx docker.io/library/nginx:1.29.3 -
起動したコンテナのスペック情報を抽出します。
sudo podman inspect nginx > nginx_container.json -
抽出したコンテナのスペック情報を基にSELinuxプロファイルを生成します。
sudo udica -j nginx_container.json nginx_profile -
SELinuxプロファイルが生成されていることを確認します。
# 生成されるSELinuxプロファイル例 sudo cat nginx_profile.cil (block nginx_profile (blockinherit container) (blockinherit restricted_net_container) (allow process process ( capability ( chown dac_override fowner fsetid kill net_bind_service setfcap setgid setpcap setuid sys_chroot ))) (allow process http_port_t ( tcp_socket ( name_bind )))
なお、udicaは、-aオプションで拒否イベントが記録されたログファイルを指定することで、スペック情報の解析では網羅しきれない実行時の動的な挙動に関するルールを追加することができます。そのため、一度の操作で完全なSELinuxプロファイルを生成できなくても、テストと調整を繰り返しながら段階的に完成度を高めていく運用が可能です。
sudo udica -j nginx_container.json -a /path/to/log nginx_profile_v1
適用例
-
生成されたSELinuxプロファイルをテンプレートとなる基本ルールとマージしてSELinuxに読み込みます。
sudo semodule -i nginx_profile.cil /usr/share/udica/templates/{base_container.cil,net_container.cil} -
念のため、SELinuxをenforcingモードに設定します。
sudo setenforce 1enforcingモードではSELinuxプロファイルに定義されたルールのみが許可され、それ以外はSELinuxによって拒否されます。
-
--security-optオプションで対象のSELinuxプロファイルを指定することで、不要なシステムリソースへのアクセスが制限されるコンテナを起動できます。sudo podman run -d -p 8080:80 --name nginx --security-opt label=type:nginx_profile.process docker.io/library/nginx:1.29.3
aa-logprof
aa-logprofは、システムログやAuditログに記録されたAppArmorの拒否イベントを解析し、既存のAppArmorプロファイルに不足しているアクセス権限を補完するルールを対話形式で提示するツールです。ユーザーは提示されたルールを確認し、許可や拒否などを選択することで、AppArmorプロファイルを効率的に更新できます。
なお、aa-logprofは、oci-seccomp-bpf-hookやudicaのようなコンテナ専用のツールではなく、一般的なホスト上のプロセス向けのAppArmorプロファイル生成にも活用できます。
使用例
-
具体的なルールを定義していない最小構成のAppArmorプロファイルを作成します。
sudo vim /etc/apparmor.d/nginx-profile include <tunables/global> profile nginx-profile flags=(attach_disconnected, mediate_deleted) { include <abstractions/base> } -
作成したAppArmorプロファイルをAppArmorに読み込みます。
sudo apparmor_parser -r /etc/apparmor.d/nginx-profile -
読み込みんだAppArmorプロファイルをcomplainモードに設定します。
sudo aa-complain /etc/apparmor.d/nginx-profilecomplainモードではenforceモードで本来拒否されるアクセスを許可しつつ、拒否イベントをログに記録します。プロセスの実行を中断させることなく拒否イベントを収集できるため、AppArmorプロファイルを更新する際に利用されます。
しかし、環境によっては、complainモードで記録された拒否イベントがaa-logprofで正しく解析できない場合があります。このような場合、対象のAppArmorプロファイルをenforceモードに設定し、実際にアクセスを拒否させることで、明確な拒否イベントを発生させる必要があります。これにより、aa-logprofが不足しているルールを確実に検出できるようになります。
-
--security-optオプションで対象のAppArmorプロファイルを指定し、生成対象のプロセスを実行するコンテナを起動します。sudo podman run -d -p 8080:80 --name nginx --security-opt apparmor=nginx-profile docker.io/library/nginx:1.29.3 -
コンテナ内のプロセスに対してテストを実行します。
# 以下の例ではベンチマークツールであるwrkを用いてnginxコンテナに対して負荷を与えています。 # 本来はAppArmorプロファイルに必要なルールを不足なく抽出するため、アプリケーションのすべての機能を網羅するようにテストを実施する必要があります。 sudo wrk -t2 -c100 -d30s http://localhost:8080/ -
aa-logprofを起動し、提示されたルールを選択してAppArmorプロファイルを更新します。
sudo aa-logprof # 以下のように既存のAppArmorプロファイルに不足しているルールが提示されます。 # 許可する場合はaを押下します。 Profile: nginx-profile Path: /docker-entrypoint.sh New Mode: owner r Severity: unknown [1 - owner /docker-entrypoint.sh r,] (A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / (O)wner permissions off / Abo(r)t / (F)inish Adding owner /docker-entrypoint.sh r, to profile. = Changed Local Profiles = # 変更内容をAppArmorプロファイルに保存するため、sを押下します。 The following local profiles were changed. Would you like to save them? [1 - nginx-profile] (S)ave Changes / Save Selec(t)ed Profile / [(V)iew Changes] / View Changes b/w (C)lean profiles / Abo(r)t Writing updated profile for nginx-profile.提示されたルールを選択する際は、最小権限の原則に基づき、プロセスの動作に必要なアクセスのみを許可するように判断します。
aa-logprofによって更新されたAppArmorプロファイルは、保存と同時にAppArmorへ自動的に再読み込みされます。
-
AppArmorプロファイルが更新されていることを確認します。
# 生成されるAppArmorプロファイル例 sudo cat /etc/apparmor.d/nginx-profile # Last Modified: Fri Jan 30 18:09:21 2026 include <tunables/global> profile nginx-profile flags=(attach_disconnected, mediate_deleted) { include <abstractions/base> capability chown, capability dac_override, capability net_bind_service, capability setgid, capability setuid, network inet stream, /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh mrix, /docker-entrypoint.d/20-envsubst-on-templates.sh mrix, /docker-entrypoint.d/30-tune-worker-processes.sh mrix, /usr/bin/basename mrix, /usr/bin/cut mrix, /usr/bin/dpkg-query mrix, /usr/bin/find mrix, /usr/bin/grep mrix, /usr/bin/mawk mrix, /usr/bin/md5sum mrix, /usr/bin/sort mrix, /usr/bin/touch mrix, /usr/sbin/nginx mrix, /usr/share/nginx/html/index.html r, owner / r, owner /docker-entrypoint.d/ r, owner /docker-entrypoint.d/15-local-resolvers.envsh r, owner /docker-entrypoint.sh r, owner /etc/group r, owner /etc/ld.so.cache r, owner /etc/nginx/conf.d/ r, owner /etc/nginx/conf.d/default.conf r, owner /etc/nginx/conf.d/default.conf w, owner /etc/nginx/mime.types r, owner /etc/nginx/nginx.conf r, owner /etc/nsswitch.conf r, owner /etc/passwd r, owner /etc/ssl/openssl.cnf r, owner /proc/*/fd/1 w, owner /proc/*/fd/2 w, owner /proc/filesystems r, owner /proc/sys/kernel/ngroups_max r, owner /run/nginx.pid rw, owner /sys/devices/system/cpu/online r, owner /usr/bin/dash r, owner /var/cache/nginx/client_temp/ w, owner /var/cache/nginx/fastcgi_temp/ w, owner /var/cache/nginx/proxy_temp/ w, owner /var/cache/nginx/scgi_temp/ w, owner /var/cache/nginx/uwsgi_temp/ w, owner /var/lib/dpkg/status r, owner /var/lib/dpkg/updates/ r, owner /var/log/nginx/access.log w, owner /var/log/nginx/error.log w, }
なお、aa-logprofは、手順5から6を繰り返すことで、AppArmorプロファイルを段階的に改善できます。そのため、一度の操作で完全なAppArmorプロファイルを生成できなくても、テストと調整を繰り返しながら完成度を高めていく運用が可能です。
適用例
-
生成されたAppArmorプロファイルをenforceモードに設定します。
sudo aa-enforce /etc/apparmor.d/nginx-profileenforceモードではAppArmorプロファイルに定義されたルールのみが許可され、それ以外はAppArmorによって拒否されます。
-
--security-optオプションで対象のAppArmorプロファイルを指定することで、不要なシステムリソースへのアクセスが制限されるコンテナを起動できます。sudo podman run -d -p 8080:80 --name nginx --security-opt apparmor=nginx-profile docker.io/library/nginx:1.29.3
さいごに
本記事では、コンテナ向けセキュリティプロファイル生成ツールとして、seccomp用のoci-seccomp-bpf-hook、SELinux用のudica、およびAppArmor用のaa-logprofを紹介しました。
これらのツールを活用することで、セキュリティプロファイルの作成は格段に容易になります。ただし、生成されたセキュリティプロファイルに適切なルールが定義されていることを最終的に確認する必要があります。そのため、各セキュリティプロファイルのシンタックスを理解しておくことは依然として重要です。
今回は、Podmanを利用するコンテナ環境を想定したツールを紹介しました。次回機会があれば、Kubernetesクラスタ環境でseccomp、SELinux、およびAppArmorの各セキュリティプロファイルを一元管理できるSecurity Profiles Operatorについて紹介する予定です。