container-audit について
container-auditは、コンテナとKubernetes環境を対象とするセキュリティ監査スキルです。イメージ、ビルドファイル、オーケストレーション設定を調べ、攻撃経路と証拠を確認し、確定した指摘と検証が必要なリスクを区別します。
主な機能
- 01イメージ、Dockerfile、Kubernetes設定の監査
- 02権限、シークレット、ランタイムの確認
- 03検証手順に基づく指摘の分類
- 04証拠の記録と重大度の調整
活用シーン
- 01Dockerfileとベースイメージの安全性確認
- 02Kubernetesマニフェストの権限とRBACの確認
- 03リポジトリ内のシークレット露出やイメージリスクの評価
- 04クラスターの証拠に基づく設定問題の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ briiirussell/ cybersecurity-skills --skill container-auditインストール後、Claude Code で/container-auditを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/findings-format.mdfindings.schema.jsonseverity-calibration.mdto-sarif.mjsvalidate-findings.mjsverification.md詳細
コンテナ監査 — Docker と Kubernetes のセキュリティレビュー
コンテナイメージ、Dockerfile、Helm チャート、Kustomize オーバーレイ、Kubernetes マニフェストを監査し、設定ミス、過剰な権限、露出したシークレット、ランタイムセキュリティの不備を確認します。cloud-audit(クラウドプロバイダーの IAM とマネージドサービス)や dependency-audit(アプリケーション内のパッケージの CVE)とは異なります。このスキルは、その間に位置するコンテナ/オーケストレーション層を対象とします。
承認
ユーザーが所有する、またはレビューを許可されているイメージ、レジストリ、クラスターのみを監査してください。trivy と kube-bench は読み取り専用であり、クラスターに変更を加えません。監査中にワークロードで kubectl exec を実行したり、マニフェストを適用したり、Pod を削除したりしないでください。また、共有クラスターや本番クラスターでコンテナエスケープを試みてはいけません。手順を説明し、所有者が隔離された環境で検証できるようにしてください。
監査範囲の特定
- 対象を一覧化します — Dockerfile、ベースイメージ、レジストリ、Helm チャート、K8s マニフェスト、Kustomize オーバーレイ、イメージを生成する CI ビルドパイプライン
- ランタイムを特定します — vanilla K8s、EKS、GKE、AKS、OpenShift、ECS Fargate、Cloud Run、Fly.io
- ネットワークモデルを特定します — サービスメッシュ、イングレスコントローラー、デフォルト拒否とデフォルト許可のどちらか
- シークレットモデルを特定します — K8s Secrets(Base64 のみ)、External Secrets Operator、sealed-secrets、Vault、Doppler
監査チェックリスト — Dockerfile
ベースイメージとサプライチェーン
- タグではなくダイジェストで固定されている —
FROM node:20@sha256:abc...を使用し、変更される可能性のあるFROM node:20は使用しない - 可能な場合は Distroless/最小構成を使用する —
gcr.io/distroless/nodejs20、alpine(musl の特性に注意)、chainguard/* :latestを使用しない — ビルドを再現できません- マルチステージビルドでビルド時のツールを除外する —
FROM build AS builder→ 最終ステージのFROM runtime - 次を grep で検索します:
FROM .*:latest、FROM .*:[0-9]+$(ダイジェストなしのタグ)
ビルド時の露出
--build-argで渡したシークレットはイメージレイヤーに含まれ、そのイメージを pull できる人なら誰でも見られます — 代わりに BuildKit シークレット(--mount=type=secret)またはランタイム環境変数を使用してくださいCOPY . .はビルドコンテキスト内のすべてを取り込みます —.dockerignoreで.git、.env、node_modules、*.pem、.aws/、.ssh/を除外してくださいADD <url>はリダイレクトに従い、チェックサム検証を無効にします — 代わりにRUN curl ... && sha256sum -cを使用してください- 次を grep で検索します:
ARG .*KEY、ARG .*TOKEN、ARG .*SECRET、ENV .*=.*[A-Za-z0-9]{32,}、ADD http
ランタイムのセキュリティ態勢
- 非 root ユーザー —
CMDの前にUSER 1001(またはゼロ以外の任意の UID)を指定する - 最終イメージに
chmod 4755で設定された SUID バイナリがない - 最終ステージに不要なシェルやパッケージマネージャーがない — Distroless/FROM scratch を強く推奨します
- オーケストレーターがコンテナの異常を検知できるよう、
HEALTHCHECKを定義する - ランタイムのルートファイルシステムを読み取り専用にする(K8s で設定し、イメージ内のファイルが
/tmpまたは宣言済みボリューム以外に書き込まないことを確認する) - 次を grep で検索します:
USER root(またはUSERディレクティブがないこと)、chmod 4755、apt-get install.*sudo
監査チェックリスト — Kubernetes マニフェスト
Pod のセキュリティ
securityContext.runAsNonRoot: trueを設定し、runAsUserにゼロ以外の UID を指定するsecurityContext.allowPrivilegeEscalation: falsesecurityContext.readOnlyRootFilesystem: trueを設定し、アプリが書き込みを必要とする場合は明示的なemptyDirマウントを使用するsecurityContext.capabilities.drop: ["ALL"]を設定したうえで、必要な機能だけを追加する- アプリのワークロードでは
securityContext.privilegedを決してtrueにしない(Falco、kube-proxy、一部の CSI ドライバーは、正当な例外としてまれにあります) hostNetwork、hostPID、hostIPCはすべてfalseにする — これらを有効にすると「コンテナがノードを参照/通信できる」状態になりますhostPathボリューム — どれもノードエスケープのリスクがあるため、個別に確認する- 次を grep で検索します:
privileged: true、runAsUser: 0、hostNetwork: true、hostPath:
Pod Security Standards(PSS)/アドミッション
- クラスターで、PSS アドミッションを通じて
restrictedプロファイルを適用するか、OPA Gatekeeper/Kyverno で同等の制御を適用します - Pod Security Policies(1.21 以降非推奨、1.25 で削除)は、これを適用する仕組みではありません。現在のアドミッションコントローラーを確認してください
kube-system名前空間でアプリケーションコードを実行するワークロードがないこと
ネットワーク
- アプリケーションのワークロードを実行するすべての名前空間に
NetworkPolicyが存在すること — まずイングレスとエグレスをデフォルトで拒否し、その後、特定の Pod を許可します NetworkPolicyがない場合、すべての Pod はすべてのポートで、kube-apiserver やメタデータサービスも含め、他のすべての Pod と通信できます- 機密性の高い名前空間では、サービスメッシュ(Istio、Linkerd)の mTLS を STRICT モードにし、PERMISSIVE にしないこと
- イングレスコントローラーで TLS を適切に終端し、バックエンドの
tls.crt/tls.keyを K8s Secrets でローテーションすること
シークレット
- K8s Secrets は Base64 エンコードされているだけで、暗号化されていません — デフォルトでは etcd に平文のバイト列として保存されます
- etcd の保存データ暗号化を有効にすること — kube-apiserver の
--encryption-provider-config - ワークロードでは、環境変数ではなく投影ボリュームを通じてシークレットを使用します(環境変数は
/proc/<pid>/environ、エラーレポート、クラッシュダンプを通じて漏えいします) - External Secrets Operator/Vault/sealed-secrets を連携させ、Git リポジトリに平文が含まれないようにします
- 次を検索します。Git 内の
data:フィールドを含むkind: Secret(Base64 エンコードされた値がコミットされています)
RBAC
cluster-adminを除き、*リソースに対する*動詞を持つClusterRoleがないこと(誰に割り当てられているかを監査します)- ワークロードごとに
ServiceAccountを用意し、共有の「default」SA を使わないこと - API アクセスを必要としないワークロードでは
automountServiceAccountToken: falseを設定します system:authenticatedグループへのバインドは、正規のワークロードすべてから見えるため、ほとんどの場合、誤りです- 次を検索します。
verbs: ["*"]、resources: ["*"]、apiGroups: ["*"]、system:authenticated
リソース制限
- すべてのコンテナで
resources.requestsとresources.limitsを設定します — 制限がないと、ノイジーネイバーの問題や DoS の攻撃対象になり(1 つの Pod がノードのリソースを枯渇させる可能性があります) - 各名前空間に、最後の砦として
LimitRangeを設定します - 各名前空間に
ResourceQuotaを設定し、テナント間のリソース枯渇を防ぎます
イメージポリシー
:latestにはimagePullPolicy: Alwaysを設定します(:latestは使うべきではありませんが、使う場合) — そうしないと、ノードに古いイメージがキャッシュされます- すべてのイメージが承認済みのレジストリ(自社のものと、少数の許可リスト)から取得されるよう、クラスター全体のポリシーを設定し、Gatekeeper/Kyverno/image-policy-webhook で適用します
- イメージ署名を検証します — cosign と sigstore policy controller、または Notary v2
監査チェックリスト — ランタイム
- CI でイメージスキャンを実施します —
trivy image、grype、docker scout cves。ビルドごとに実行する必要があります。アドバイザリでビルドをブロックする必要はありませんが、検出結果を提示する必要があります - ランタイム検知 — Falco/Tracee/Tetragon で、「シェルを起動したことのない Pod でシェルが起動された」パターンを検出します
- 監査ログを有効にします — kube-apiserver の監査ログで
exec、attach、port-forwardイベントを記録し、インシデント対応に備えます kubectl execのアクセスを追跡します — cluster-admin が誰にも気づかれずに本番環境へシェルで入れる状態にしないでください
便利なワンライナー
# All Dockerfiles in the repo + their first FROM line
git ls-files | grep -E '(^|/)Dockerfile(\.|$)' | xargs -I{} sh -c 'echo "==> {}"; grep ^FROM "{}"'
# Manifests missing securityContext
grep -rL "securityContext" --include="*.yaml" --include="*.yml" .
# Manifests with privileged containers
grep -rln "privileged: *true" --include="*.yaml" --include="*.yml" .
# Manifests with hostPath volumes
grep -rln "hostPath:" --include="*.yaml" --include="*.yml" .
# Secrets in Git (base64-encoded but readable)
grep -rln "kind: *Secret" --include="*.yaml" --include="*.yml" . | xargs grep -l "^data:"
# Image scan (Trivy)
trivy image --severity HIGH,CRITICAL --exit-code 0 <image>
# Manifest scan (Trivy)
trivy config --severity HIGH,CRITICAL .
# Cluster posture (kube-bench, run inside the cluster)
kube-bench run --targets master,node,policies
確認してから記録する
チェックリストの候補すべてに、references/findings-format.md の共通形式を使って、1 つの判定を付けます。何かを confirmed とする前に、references/verification.md の反証確認を実施します。引用したマニフェストまたは Dockerfile の行から確認を始め、問題を阻止する最も強力な制御(アドミッションポリシー、名前空間の PSS ラベル、デフォルトを上書きする Helm 値、レイヤーを破棄する後続のビルドステージ)を探します。
設定ミスを検出事項とするのは、誰が何を得るのかを明示できる場合に限ります。references/severity-calibration.md の判定基準を書き留めます。信頼度の低い主体(リポジトリを読む人、イメージをプルできる人、Pod を作成できる名前空間ユーザー、インターネットに公開された Pod でコードを実行する攻撃者)、越える境界(コンテナからノード、名前空間からクラスター、リポジトリから本番シークレット)、そしてその結果を記載します。
confirmed — 設定がリポジトリ内、または保存済みのクラスター出力にあり、結果に至る経路を追跡できます。例:
kind: Secretがk8s/database.yamlにあり、本番 DB のパスワードにデコードされる base64 のdata:値が含まれています。リポジトリの読み取り権限を持つ人は誰でもデータベースにアクセスできます。重大度ファイルのキー表に従い、権限の範囲に基づいて評価します。system:authenticatedを、クラスター全体のsecretsに対するget権限を持つロールにバインドするClusterRoleBinding:あらゆる Pod のサービスアカウントトークンで、すべてのシークレットを読み取れます。values.yamlのデフォルトがprivileged: trueで、hostPath: /のマウントを含む、同梱された Helm チャート。system_configuration条件(「securityContext.privilegedを上書きしないデプロイメント」)と、信頼度mediumで確認済みです。
最初にコンテナ内でコード実行を必要とするエスケープ経路(privileged、hostPath、hostPID、CAP_SYS_ADMIN)には、その旨を示す environmental_dependency 条件を付けます。これにより可能性は下がりますが、ノードに到達した場合の影響は変わりません。
needs_validation — リポジトリからリスクは確認できますが、判断を左右する事実はクラスターまたはレジストリにあります。例:
securityContextのない Deployment があり、その名前空間の PSSenforceラベルは、このリポジトリの範囲外でプラットフォームチームが設定しています。阻害要因:名前空間のラベル。計画:kubectl get ns payments -o jsonpath='{.metadata.labels}'とkubectl apply --dry-run=server -f k8s/api.yamlを実行します(アドミッションは実行されますが、何も永続化されません)。- 最終ステージで使用されている
ARG NPM_TOKEN。阻害要因:誰がregistry.example.com/apiをプルできるか。計画:レジストリのリポジトリポリシーを確認し、プッシュ済みイメージに対してdocker history --no-truncを実行します。
rejected — 名前と場所が特定された制御によって、すべての経路で阻止されます。例:
charts/agent/templates/daemonset.yamlのhostPathが、対象の名前空間を含むマッチ条件を持つ KyvernoClusterPolicy(policies/disallow-host-path.yaml)のEnforceモードによって禁止されています。ARG NPM_TOKENはbuilderステージでのみ使用されています。ruled_out_by.locationは最終ステージのFROM行です。そこではクリーンなベースイメージから開始し、/app/distのみをコピーします。- 値が
REPLACE_MEのプレースホルダーであるkind: Secret、または暗号文のみのSealedSecret。
**強化に関するメモ(記録ではありません):**追跡されたものが何も依存していない場合に欠けているレイヤーが 1 つある状態です。例:ダイジェストではなくタグを使用している、readOnlyRootFilesystem がない、シングルテナントクラスターでリソース制限がない、ホストマウントのない、ほかに特権のない Pod で root として実行している、HEALTHCHECK がない。NetworkPolicy の欠如は、それによって公開される到達可能な対象(認証不要の内部サービス、ノードの認証情報を持つメタデータエンドポイントなど)を明示した場合にのみ、検出事項になります。
trivy image による イメージの CVE には、dependency-audit のルールを適用します。脆弱なパッケージは、コンテナが実行するものから到達可能であることを示した場合にのみ検出事項となります。それ以外の場合は needs_validation または強化に関するメモとします。
**クラスターの証拠。**ライブクラスター、レジストリ、またはイメージに関する検出事項(リポジトリ内のファイルに関するものではない場合)は、保存済みの出力を参照します。読み取り専用コマンドの出力を編集して <audit dir>/evidence/<name>.json または .txt に保存し(例:kubectl get clusterrolebindings -o json、kube-bench run --json、trivy image -f json)、location と evidence[] からそのファイルと行を参照します。Secret の値は決して保存しないでください。.data と .stringData を削除し(kubectl get secret -o json | jq 'del(.items[].data, .items[].stringData)')、環境変数内のトークンを編集します。説明されているだけで実行していないチェックは needs_validation です。
**フィンガープリント:**行番号、イメージタグ、ダイジェストを含めず、container-audit/<class>/<file or resource>/<name> の形式にします:
container-audit/privileged-container/charts/agent/values.yaml/securityContext.privilegedcontainer-audit/secret-in-git/k8s/database.yaml/db-credentialscontainer-audit/rbac-wildcard/cluster/prod-eks/ClusterRoleBinding/ci-deployercontainer-audit/CVE-2024-3094/image/registry.example.com/api/xz-utilsイメージ参照はタグなしで記述してください(api:1.4ではなくregistry.example.com/api)。バリデーターは:<digits>のセグメントを拒否します。タグとダイジェストは根拠の説明に記載してください。
ext キー(任意。すべてのレコードで使用できます):
| キー | 値 | 例 |
|---|---|---|
resource | Kubernetes の種類、名前空間と名前、またはイメージリポジトリ | "Deployment/payments/api"、"registry.example.com/api" |
cis | CIS Docker または Kubernetes Benchmark のコントロール | "CIS Kubernetes 5.1.1"、"CIS Docker 4.1" |
pss | 設定が違反している Pod Security Standards のレベル | "baseline"、"restricted" |
次に、フィンガープリント順に並べた findings.json を作成して検証し、必要に応じて SARIF にエクスポートします。
node references/validate-findings.mjs findings.json
node references/to-sarif.mjs findings.json -o findings.sarif
実行時に修正を検証する
runAsNonRoot: true— ポッドが正常に再起動し、Running 状態を維持することを確認します。イメージのENTRYPOINTがchownを呼び出す場合、クラッシュループが発生します。- NetworkPolicy のデフォルト拒否 — 正当なトラフィックが引き続き機能することを確認します(クラスター内で
kubectl run -it --rm debug ... curlを実行します)。デフォルト拒否の導入後は、気づきにくい部分的な障害がよく発生します。 readOnlyRootFilesystem: true— アプリが宣言済みのemptyDirマウント以外に書き込まないことを確認します。ログ、PID ファイル、一時ファイルへの書き込みが、よく問題を引き起こします。- イメージポリシーの強制 — 署名されていない、または許可リストにないイメージのデプロイを試し、アドミッションで拒否されることを確認します。
出力形式
まず findings.json を作成して検証します。そこから、ハードニングのメモ一覧と合わせてレポートを生成します。説明文によって、JSON に記載されていない判定、重大度、ブロッカーが変わることはありません。
# Container Security Audit
## Target: [cluster name / image registry / repo path] ## Date: [date]
## Scope: [full | partial — what was left out, e.g. "manifests only, no cluster access"]
## findings.json: [path] — validated: [yes]
### Summary
- Confirmed: [N] (Critical [n] | High [n] | Medium [n] | Low [n]) Needs validation: [N] Rejected: [N] Hardening notes: [N]
- Audited: [N] Dockerfiles, [N] manifests / charts, [N] images; cluster checks: [pass / fail counts, or "not run"]
### Confirmed findings
#### [SEVERITY] [Title] `fingerprint`
**Location:** `path/to/manifest.yaml:42` (or `audit/evidence/<name>.json:<line>`) **Confidence:** [medium | high (reproduced)]
**Category:** Dockerfile | Pod security | RBAC | NetworkPolicy | Secrets | Resource limits | Image policy | Runtime
**Principal → boundary → result:** [who, what they cross, what happens]
**Conditions:** [e.g. environmental_dependency: needs code execution in the pod]
**Vulnerable config:**
```yaml
[snippet]
```
**Remediation:**
```yaml
[fixed snippet]
```
**Verify the fix:** [observed behaviour that proves it holds, see Verify Fixes at Runtime]
### Needs validation
| Finding | Blockers | Check for the owner to run |
|---|---|---|
### Hardening notes (not findings)
- [location] — [gap] — [why it isn't a finding] — [effort]
### Rejected (do not re-raise)
| Fingerprint | Claim | Ruled out by | Location |
|---|---|---|---|
### Cluster posture
| Control | Status | Evidence |
|---|---|---|
| PSS / admission (Gatekeeper, Kyverno) | | |
| NetworkPolicy default-deny | | |
| etcd encryption at rest | | |
| API server audit logging | | |
| Image signature / registry policy | | |
| Runtime detection (Falco, Tetragon) | | |
### Prioritized remediation plan
1. [Critical / High confirmed findings]
2. [Medium / Low confirmed findings]
3. [Owner-run checks for needs-validation items]
4. [Hardening notes worth the effort]
確認済みの指摘に対する処置(通常は後で finding-triage が設定します):修正済み/延期/リスク受容済み/緩和済み(プラットフォーム/設定)/大規模な移行が必要。誤検知は処置ではなく、rejected レコードとして記録します。
境界
- ユーザーが提供した、またはユーザーに権限があるクラスターとレジストリのみを監査します。
- 監査中に
kubectl deleteを実行したり、クラスターの状態を変更したりしないでください。読み取り専用の操作(get、describe、auth can-i)のみを行います。 - 実行時の証拠には、実行中のワークロードを変更するのではなく、影響の少ない確認(
kubectl run -it --rmによる一時的なデバッグポッドなど)を優先します。 - クラスター乗っ取りにつながるシナリオは拒否してください。見つかった弱点を足掛かりに全面的な侵害へと進むのは、監査ではなくエクスプロイトです。
- リポジトリや保存済みの出力から確定できない指摘は、確認済みとしてではなく、ブロッカーと確認項目を添えて
needs_validationと記録します。
参考資料
- CIS Docker Benchmark
- CIS Kubernetes Benchmark
- NSA/CISA Kubernetes Hardening Guide
- Pod Security Standards(PSS)— restricted、baseline、privileged
- OWASP Docker Security Cheat Sheet
- OWASP Kubernetes Security Cheat Sheet
- MITRE ATT&CK for Containers
- 共通の参考資料(ビルド時に
references/にコピー):findings-format.md、severity-calibration.md、verification.md、validate-findings.mjs、to-sarif.mjs、findings.schema.json