偽装されたコンテナイメージがシステムを蝕む前に:CosignとNotaryによるサプライチェーンの絶対防衛戦略
世界中のセキュリティコミュニティが、いま最も注力している領域はどこか? それは間違いなく「サプライチェーンセキュリティ」だ。ソフトウェア開発のライフサイクルは複雑化し、多様なOSSコンポーネント、サードパーティライブラリ、クラウドサービスが絡み合う。この複雑性が、攻撃者にとっての格好の獲物となっている。特にコンテナ技術がデファクトスタンダードとなった現代において、偽装されたコンテナイメージがシステム内部に侵入することは、もはや壊滅的な影響をもたらす「核爆弾」に等しい。
従来の境界防御や脆弱性スキャンだけでは、もはや防ぎきれない巧妙な攻撃が横行している。ビルドパイプラインの深部に埋め込まれたバックドア、信頼されたレジストリへの不正アクセスによるイメージ改ざん、あるいはCI/CDツール自体の乗っ取り。これらは、単なるCVEのパッチ適用では解決できない、根源的な信頼の問題に起因する。
このブログでは、コンテナイメージの真正性を保証し、サプライチェーンの「信頼の鎖」を強固に繋ぎ止めるための最前線技術、CosignとNotaryに深く切り込む。単なるツールの使い方にとどまらず、サイバー攻撃者が狙う盲点、低レイヤでの脅威、そして将来の耐量子暗号まで見据えた、ホワイトハッカーとしての深い洞察を提供する。
なぜコンテナイメージの署名がこれほどまでに重要なのか?
「ビルドされたばかりのイメージだから安全だろう」という安易な信頼は、もはや通用しない。攻撃者は、開発プロセスの各フェーズに潜む「信頼の隙間」を執拗に狙う。
1. ビルド環境の汚染: 開発者のワークステーションやCI/CDエージェントがマルウェアに感染し、正規のソースコードから悪意のあるバイナリが生成される。この場合、ソースコードレビューだけでは発見が困難だ。
2. レジストリの改ざん: プライベートなコンテナレジストリやパブリックレジストリが侵害され、既存のイメージが差し替えられたり、悪意のあるタグが追加されたりする。
3. 転送中の改ざん: イメージのプッシュ/プル中に通信経路が傍受され、中間者攻撃によってイメージが改ざんされる。TLSが有効であっても、サーバー側の証明書が不正なものである可能性は排除できない。
4. 依存関係の悪用: イメージのベースとなるOSレイヤや、ビルド時に組み込まれるライブラリ、パッケージに既知または未知の脆弱性やマルウェアが潜んでいる。
これらの攻撃は、単にイメージのハッシュ値を比較するだけでは防げない。ハッシュ値は「データが改ざんされていないこと」を示すが、「誰がそのデータを生成したか」や「その生成プロセスが信頼できるか」までは保証しない。ここで必要となるのが、発行元の身元とビルド時の状態を保証する「デジタル署名」なのだ。
署名されたイメージは、そのハッシュ値だけでなく、特定のエンティティによって「この時点で、この状態であった」ことが証明される。これにより、我々は「誰が、いつ、何を」信頼できるプロセスで生成したのかという、サプライチェーンにおける最も根源的な問いに答えることができるようになる。
Cosignによる軽量かつ柔軟な署名ワークフロー
Cosignは、Sigstoreプロジェクトの一環として開発された、コンテナイメージおよびその他のソフトウェア成果物の署名を容易にするためのツールだ。その最大の特徴は、OCIレジストリを署名ストレージとして利用し、シンプルながら強力な信頼の基盤を提供する点にある。
Sigstoreが変える署名体験
従来の公開鍵インフラ(PKI)は、鍵の生成、配布、失効、そして証明書管理の複雑さから、開発者が日常的に利用するには障壁が高かった。Sigstoreは、この複雑さを解消し、開発者が手間なく署名を行えるように設計されている。
- Fulcio: 短命の証明書発行局(CA)。OpenID Connect (OIDC) プロバイダ(GitHub, Googleなど)と連携し、開発者のIDを基に一時的な署名証明書を発行する。これにより、長期的な鍵管理の負担が軽減される。
- Rekor: 改ざん不可能な透明性ログ。すべての署名イベントとその証明書を記録し、公開する。これにより、署名イベントの存在証明と、悪意ある署名活動の検出を可能にする。
実践的な署名・検証手順
まずは、Cosignを使った基本的なイメージの署名と検証を見ていこう。
# 事前準備: Cosignのインストール (例: Homebrew)
# brew install cosign
# 1. 署名するコンテナイメージをビルドし、レジストリにプッシュ
# docker build -t ghcr.io/my-org/my-app:latest .
# docker push ghcr.io/my-org/my-app:latest
# 2. イメージにキーレス署名を行う (FulcioとRekorを利用)
# 署名時にOIDCプロバイダへの認証が求められる (例: GitHubアカウント)
cosign sign ghcr.io/my-org/my-app:latest
# 出力例:
# Pushing signature to: ghcr.io/my-org/my-app
# ... (ブラウザでの認証プロンプト) ...
# Successfully signed ghcr.io/my-org/my-app:latest
# 3. 署名されたイメージを検証する
# デフォルトではRekorの公開インスタンスを使用して検証される
cosign verify ghcr.io/my-org/my-app:latest
# 出力例:
# Verification for ghcr.io/my-org/my-app:latest --
# The following checks were performed on each of these signatures:
# - The cosign claims were validated
# - The transparency log was checked.
# - The identities were validated against the Fulcio roots.
#
# ✔ Verified OK
Kubernetesでの適用:Admission Controllerによる強制
署名されたイメージをビルドするだけでは不十分だ。そのイメージが実際にKubernetesクラスタ内でデプロイされる際に、署名の検証を強制する必要がある。この役割を果たすのが、Admission Controllerとポリシーエンジン(KyvernoやOPA Gatekeeper)だ。
以下は、Kyvernoを使って、特定のレジストリからプルされたイメージがCosignによって署名されていることを検証するポリシーの例だ。署名検証に失敗したPodはデプロイが拒否される。
# filename: verify-image-signatures.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
annotations:
policies.kyverno.io/category: Supply Chain Security
policies.kyverno.io/description: >-
This policy verifies that container images pulled from 'ghcr.io/my-org/*'
are signed by Cosign using a keyless signature.
It leverages the Fulcio root CA and Rekor transparency log.
spec:
# validationFailureAction: Enforce に設定することで、署名検証に失敗したPodのデプロイを拒否
validationFailureAction: Enforce
background: false # 既存リソースへの適用は行わない (新しくデプロイされるPodのみ対象)
rules:
- name: check-image-signature-ghcr-myorg
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system # システムネームスペースは検証対象外とするなど、柔軟な設定が可能
- kyverno
validate:
image:
# イメージパターンにマッチするコンテナイメージを検証対象とする
- images:
- "ghcr.io/my-org/*"
verifyImages:
- image: "ghcr.io/my-org/*"
# キーレス署名を検証する場合の設定
keyless:
# Fulcio CAのURL。Sigstore Public Instanceを利用する場合はデフォルトで良い
# 本番環境では、自前のFulcioインスタンスや、信頼できるインスタンスを指定することも検討
fulcioRef:
rekor:
url: https://rekor.sigstore.dev
# 署名者のIdentityを検証する例 (FulcioでのOIDC連携)
# GitHub Actions Workflowからの署名であれば、以下のフィールドで検証可能
# subject: "https://github.com/my-org/my-app/.github/workflows/build.yaml@refs/heads/main"
# issuer: "https://token.actions.githubusercontent.com"
# 署名時に付与されたアノテーションの検証
annotations:
# このアノテーションが付与されていることを検証
"build-source": "https://github.com/my-org/my-app"
# 署名検証が成功した場合にのみコンテナが実行される
このポリシーをKubernetesクラスタに適用することで、開発者が意図しない、あるいは攻撃者によって改ざんされたイメージがクラスタ内で実行されるリスクを劇的に低減できる。
Notary v2(TUF)による堅牢な信頼の基盤
Cosignが軽量性と開発者体験に優れる一方で、より厳格なセキュリティ要件が求められるエンタープライズ環境や、長期的な信頼の管理が必要なケースでは、The Update Framework (TUF) の原則に基づいたNotary v2がその真価を発揮する。
TUFの原理とセキュリティモデルの強固さ
TUFは、ソフトウェアアップデートシステムのセキュリティを向上させるためのフレームワークであり、その設計思想は、攻撃者がシステムのあらゆる側面を侵害する可能性を前提としている。これは、ホワイトハッカーが常に最悪のシナリオを想定する思考と深く共鳴する。
TUFは以下のセキュリティ目標を達成するために設計されている。
- キーの漏洩からの回復: ルートキーが漏洩しても、システム全体が即座に破綻しないように、キーの役割と有効期限を厳密に分離する。
- ロールバック攻撃の防止: 攻撃者が古い、既知の脆弱性を持つバージョンを再配布することを防ぐ。
- フレッシュネス攻撃の防止: 攻撃者が最新のアップデートへのアクセスをブロックし、ユーザーが古いバージョンを使用し続けることを強制する攻撃を防ぐ。
- 複合攻撃からの耐性: 複数の種類の攻撃が同時に発生しても、システムが耐えられるように設計されている。
Notary v2は、OCI Distribution Specificationと連携し、TUFのこれらの強固なセキュリティモデルをコンテナイメージの配布と検証に応用する。
Notary v2のアーキテクチャと信頼の階層
Notary v2では、信頼の階層が明確に定義され、それぞれの役割に異なる鍵が割り当てられる。
1. Root Key: 最も重要な鍵。コールドストレージに保管され、ほとんど使用されない。システムの信頼の基点となる。
2. Timestamp Key: アップデートの「新鮮さ」を保証する鍵。定期的に更新される。
3. Snapshot Key: アップデートの整合性を保証する鍵。特定の時点で利用可能なすべてのターゲット(イメージ)のリストの整合性を保証する。
4. Target Key: 個々のターゲット(イメージ)を署名する鍵。この鍵の漏洩による影響を局所化する。
これらの鍵は、それぞれ異なる役割を持ち、異なる頻度で更新・ローテーションされることで、単一の鍵が侵害されてもシステム全体が破綻しないような「最小権限の原則」を鍵管理に応用している。
# Notary v2での署名・検証は概念的には以下のようになるが、
# 実際のコマンドはNotary v2クライアントのセットアップとリポジトリ初期化が前提となる。
# (Notary v2は現在も活発に開発が進められているため、具体的なコマンドは公式ドキュメントを参照)
# Notary v2リポジトリの初期化 (Root Key, Timestamp Key, Snapshot Keyなどを生成)
# notary init docker.io/my-org/my-app
# ターゲット(イメージ)の署名
# notary sign docker.io/my-org/my-app:latest --key-role targets --file my-app.json
# ターゲット(イメージ)の検証
# notary verify docker.io/my-org/my-app:latest
Notary v2は、特に大規模な組織で、複数のチームやサプライヤーが関与する複雑なサプライチェーンにおいて、その堅牢な設計思想が真価を発揮する。Cosignが手軽な開発者体験を提供するのに対し、Notary v2は「壊れにくい」システムを構築するための厳格なフレームワークを提供する。両者は排他的なものではなく、要件に応じて使い分けたり、将来的に連携させたりすることも視野に入れるべきだろう。
サプライチェーン攻撃の深層と防衛の盲点
ここからは、最高峰のホワイトハッカーとして、通常のセキュリティガイドラインでは語られない、より深層の攻撃メカニズムと防衛の盲点について言及する。
1. 低レイヤのメモリ挙動とビルド環境の信頼性
イメージの署名は、あくまで「特定のハッシュ値を持つ成果物が、特定の鍵で署名された」という事実を証明する。しかし、そのハッシュ値に至るまでの「ビルドプロセス」が本当に信頼できるか、という問いには直接答えない。
例えば、ビルドサーバーのコンパイラが改ざんされていた場合、正規のソースコードからコンパイルされたバイナリには、意図しないバックドアや脆弱性が埋め込まれる可能性がある。この時、生成されるイメージのハッシュ値は、改ざんされたコンパイラによって生成されたものとして「正規」であり、それに署名しても「改ざんされたプロセスによって生成された正規のイメージ」が生まれてしまう。
ここで重要になるのが、SLSA (Supply-chain Levels for Software Artifacts) のようなフレームワークだ。SLSAは、ビルドプロセスの透明性、再現性、分離性を高めることで、ビルド環境自体の信頼性を担保しようとする。署名プロセスは、SLSAのProvenance(来歴)情報を付与することで、ビルド環境の信頼性まで含めた「信頼の鎖」を構築する上で不可欠な要素となる。
2. 通信プロトコル仕様の欠陥とレジストリ通信の堅牢性
OCI Distribution SpecificationやHTTP/2といった、コンテナイメージの配布に利用されるプロトコルにも、実装上の欠陥や仕様の曖昧さが潜む可能性がある。例えば、HTTP/2の特定のフレーム処理における脆弱性や、HTTPレスポンスヘッダの巧妙な操作によって、クライアントが誤ったイメージ情報を信じ込んでしまうような攻撃シナリオもゼロではない。
我々セキュリティエンジニアは、単にTLS/mTLSを導入したからといって安心すべきではない。レジストリとの通信経路においては、パケットの深層解析(Deep Packet Inspection)を行い、プロトコル仕様に則った正常な挙動をしているかを常に監視する必要がある。異常なシーケンス、ペイロードの不整合、予期しないヘッダ情報の存在は、中間者攻撃やレジストリ自体の侵害を示す兆候かもしれない。
3. パケット構造の解析とシグネチャペイロードの異常検知
Cosignの署名は、OCIレジストリにapplication/vnd.cncf.cosign.signature.v1+jsonのようなメディアタイプで格納される。この署名ペイロードの構造や、それがレジストリとの間でやり取りされる際のパケット構造を詳細に解析することは、異常検知の重要な手がかりとなる。
例えば、署名ペイロードが通常とは異なるサイズであったり、既知の署名スキーマから逸脱していたりする場合、それは署名プロセスが何らかの形で改ざんされた可能性を示唆する。高度なIDS/IPSシステムは、これらの署名関連のトラフィックパターンを学習し、異常をリアルタイムで検知する能力を持つべきだ。
4. 耐量子暗号への移行:未来を見据えた署名アルゴリズム
現在の公開鍵暗号システム(RSA, ECCなど)は、将来登場するであろう大規模な量子コンピュータによって容易に破られる可能性がある。これは、CosignやNotaryが利用するデジタル署名アルゴリズムにも影響を及ぼす、極めて重要な問題だ。
ホワイトハッカーとしては、この「量子脅威」を常に意識し、耐量子暗号(Post-Quantum Cryptography, PQC)への移行戦略を今から検討する必要がある。NISTが標準化を進めているPQCアルゴリズム(例: CRYSTALS-Dilithium, Falconなど)が、将来的にCosignやNotaryのようなツールにどのように統合されていくか、その動向を注視し、早期のPoCや移行計画を立てることが求められる。現在の署名システムが量子コンピュータによって破られた場合、過去に署名されたすべてのイメージの信頼性が失われ、サプライチェーン全体が崩壊する可能性があるからだ。
5. 生成AIとサプライチェーンセキュリティ:プロンプトインジェクションへの防御層
開発パイプラインに生成AIアシスタント(GitHub Copilot, Bardなど)が導入されることで、新たな攻撃ベクトルが生まれている。攻撃者は、開発者が利用するAIアシスタントに対して悪意のあるプロンプトインジェクションを行い、意図しない脆弱なコードスニペットや、巧妙なバックドアを自動生成させようとするだろう。
AIによって生成されたDockerfile、Kubernetesマニフェスト、あるいはアプリケーションコードが、そのままビルドされ、署名されてしまうリスクがある。この場合、署名は「AIが生成した脆弱なイメージ」を保証することになる。
これに対する防御策としては、以下のレイヤーが必要だ。
- AI生成コードの厳格なレビュー: AIが生成したコードも、人間の目による厳格なセキュリティレビューを通過させる。
- AIアシシタントのガードレール: AIアシスタント自体に、セキュリティポリシー違反のコード生成を抑制するガードレール(例: 特定のAPI呼び出しの禁止、既知の脆弱性パターンの拒否)を組み込む。
- SAST/DASTの強化: AI生成コードに対応した静的・動的解析ツールを導入し、潜在的な脆弱性を早期に特定する。
- SBOM (Software Bill of Materials) の自動生成と解析: AIがどのような依存関係を組み込んだかを自動的に追跡し、脆弱性データベースと照合する。
コンテナイメージの署名は、このAI生成時代においても、生成されたコードが「どのようなプロセスを経て、最終的にこの成果物になったか」という信頼の来歴を確立するための最終防衛線となる。AIが生成したものであろうとなかろうと、最終成果物に対する人間の責任と承認の証として、署名は不可欠だ。
監査と継続的監視の重要性
イメージ署名システムは、一度導入すれば終わりではない。それは生きたシステムであり、継続的な監査と監視が不可欠だ。
- 署名鍵のライフサイクル管理: 署名鍵の生成、配布、ローテーション、そして失効プロセスは厳格に管理されなければならない。特にルートキーのような機密性の高い鍵は、オフラインストレージ(HSMなど)に厳重に保管し、利用時のみ限定的にアクセスさせる。
- Rekor透明性ログの監視: Rekorのような透明性ログは、全ての署名イベントを記録する。このログを継続的に監視し、不審な署名活動(例: 未知のIDによる署名、許可されていないイメージへの署名)がないかをチェックする。
- CI/CDパイプラインの監査: 署名が行われるCI/CDパイプラインへのアクセス制御、変更履歴、実行ログは厳重に監査されなければならない。パイプライン自体の改ざんを防ぐことが、署名の信頼性を保つ上で極めて重要だ。
- ポリシーの定期的なレビュー:
KyvernoなどのAdmission Controllerで適用される署名検証ポリシーは、ビジネス要件や脅威の変化に合わせて定期的にレビューし、更新する必要がある。
これらの継続的な活動を通じて、我々は攻撃者の一歩先を行き、サプライチェーン全体のセキュリティレベルを向上させ続けることができる。
まとめ:信頼は築き上げ、そして守り抜くもの
コンテナイメージの署名と検証は、もはや「あれば良い」レベルのセキュリティ対策ではない。それは、現代のクラウドネイティブ環境におけるサプライチェーンセキュリティの「土台」であり、システム全体の信頼性を左右する生命線だ。
Cosignは、その手軽さで開発者の署名習慣を根付かせ、Notary v2はTUFの堅牢な設計思想で、最も厳しいセキュリティ要件を満たす。これらを適切に活用し、ビルドプロセスの深層、通信プロトコルの微細な挙動、そして未来の量子脅威やAIの進化まで見据えた多層防御戦略を構築することが、我々ホワイトハッカーに課せられた使命だ。
机上の空論ではない。今日からあなたのチームのサプライチェーンに、この「信頼の楔」を打ち込み、偽装されたイメージがシステムを蝕むのを阻止するのだ。サイバーセキュリティに終わりはない。常に最悪を想定し、最善を尽くす。それこそが、我々の生き様だ。
コメント