🛡️

ソフトウェア・サプライチェーン・インテグリティ保証ツール in-toto の紹介

2021/08/19に公開

はじめに

ソフトウェア・サプライチェーン・インテグリティ保証とは

ソフトウェア・サプライチェーン・インテグリティ保証とは、ソフトウェアのコーディング、ビルド、パッケージング、利用の一連のソフトウェア・ライフサイクルの中で、第三者によるコードの改ざんやパッケージのすり替えなどの攻撃がないことの保証のことです。

例えば、Google は、ソフトウェア・サプライチェーン・インテグリティ保証を次のような図でモデル化して説明しています。

image.png

出典: https://security.googleblog.com/2021/06/introducing-slsa-end-to-end-framework.html

ソフトウェアが開発者(Developer)から利用者(Use)に届くまでのプロセスの中で、A ~ Hまでの攻撃が考えられます。

  • A: 不正なコードの送信
  • B: ソース管理プラットフォームの汚染
  • C: コードの改ざん
  • D: ビルドプラットフォームの汚染
  • E: 不正な依存関係
  • F: CI/CDをバイパス
  • G: パッケージリポジトリの汚染
  • H: 不正なパッケージの使用

これらの攻撃に対し、パッケージマネージャやリポジトリといった従来のツールは脆弱で、これらの攻撃を防御することができませんでした。

ソフトウェア・サプライチェーン・インテグリティ・フレームワークとは

従来のツールの脆弱性を補うために登場したツールが、ソフトウェア・サプライチェーン・インテグリティ・フレームワークです。

このツールは、ソフトウェア・ライフサイクルの中で使われるSCMツール、パッケージマネージャなどの従来のツールと連携しつつ、ソフトウェア・サプライチェーン・インテグリティ保証のための機能を提供します。

ソフトウェア・サプライチェーン・インテグリティ・フレームワークとしては、次の2つのツールが知られています。

  1. SALSA
  2. in-toto

SLSA

SLSA (Supply-chain Levels for Software Artifacts) は、 Google が使用し、提案するソフトウェア・サプライチェーン・インテグリティ・フレームワークの仕様です。

https://slsa.dev/

https://japan.zdnet.com/article/35172644/

https://security.googleblog.com/2021/06/introducing-slsa-end-to-end-framework.html

SLSAは、次のようなインテグリティ保証のレベルやその要件を定義します。

レベル 説明
1 ビルドプロセスのドキュメント 未署名の起源(provenance)
2 ビルドサービスの耐タンパー性 ホストされたソース/ビルド、署名済み起源
3 特定の脅威に対する余分な抵抗を防ぎます ホストのセキュリティ管理、改ざん不可能な起源
4 最高レベルの信頼と信頼 二者間レビュー + 堅牢ビルド(hermetic build)

一方で、SLSA 自身にはインテグリティ保証のためのツールや参照実装を含んでいません。

その代わり、SLSA のコミュニティは、後述する in-toto を使ったデモ を例示しています。

in-toto とは

in-toto とは、インテグリティ保証を実現するためのツールです。

ちなみに、in-toto はラテン語で「全体」を意味します。

in-toto は、ソフトウェア製品の製造開始からエンドユーザーへのインストールまでの整合性を確保するように設計されています。整合性は、ソフトウェア・ライフサイクル上のどのステップが、誰によって、どの順序で、実行されたかの記録を署名付きで保存することで実現します。

ちなみに、in-toto は米国国立科学財団(NSF)、国防高等研究計画局(DARPA)、空軍研究所(AFRL)および Linux Foundation によって支援されています。

デモ

デモ概要

in-totoで紹介されているデモのプログラムを紹介します。

https://github.com/in-toto/demo

このソフトウェア・サプライチェーン・レイアウトには、次の登場人物がいます。

  • Alice: プロジェクトの管理者、所有者。
  • Bob: プロジェクトの開発者。
  • Carl: ソフトウェアをパッケージ化(tar ball)する者。
  • Client: 最終製品(final product)の利用者

準備

まず、demoプロジェクトをクローンして、poetryプロジェクトを作成します。

git clone https://github.com/in-toto/demo
cd ../demo
poetry init

つぎに、in-totoをインストールします。

poetry add in-toto

ソフトウェア・サプライチェーン・レイアウトの定義(Alice)

まず、ソフトウェア・サプライチェーン・レイアウトを定義します。

cd owner_alice
poetry run python create_layout.py

create_layout.py は、次のような署名付き レイアウト・メタデータ・ファイル を生成します。

owner_alice/root.layout
{
  "signatures": [
    {
      "keyid": "...",
      "sig": "..."
    }
  ],
  "signed": {
    "inspect": [
      {
        "_type": "inspection",
        "name": "untar",
        "run": [
          "tar",
          "xzf",
          "demo-project.tar.gz"
        ]
      }
    ],
    "keys": {
      // ...
    },
    "readme": "",
    "steps": [
      {
        "name": "clone",
        "expected_command": [
          "git",
          "clone",
          "https://github.com/in-toto/demo-project.git"
        ]
      },
      {
        "name": "update-version",
        "expected_command": []
      },
      {
        "name": "package",
        "expected_command": [
          "tar",
          "--exclude",
          ".git",
          "-zcvf",
          "demo-project.tar.gz",
          "demo-project"
        ]
      }
    ]
  }
}

このファイルのポイントは、次の点です。

  1. signatures にはAliceの秘密鍵による署名が付されます。これにより、悪意ある改ざんが困難になります。
  2. inspectには、パッケージを検証するためのコマンドや検証条件が定義されます。このデモではパッケージtar balluntarコマンドで解凍し、その中のファイルの改ざん有無を検証します。
  3. stepsには、正規のビルドステップが定義されます。後述のリンク・メタデータ・ファイルがステップと紐付き、正規のビルドステップが正規のユーザにより実行されたことが保証されます。

プロジェクトのソースコードのクローンを作成する(Bob)

Bob は、git clone の代わりに、in-toto-run clone を使用して、GitHubからプロジェクトリポジトリのクローンを作成し、メタデータを記録します。

cd ../functionary_bob
poetry run in-toto-run --step-name clone --products demo-project/foo.py --key bob -- git clone https://github.com/in-toto/demo-project.git

このとき作成される署名付きメタデータは次の通りです。

functionary_bob/clone.776a00e2.link.json
{
  "signatures": [
    {
      "keyid": "...",
      "sig": "..."
    }
  ],
  "signed": {
    "_type": "link",
    "byproducts": {
      "return-value": 128,
      "stderr": "",
      "stdout": ""
    },
    "command": [
      "git",
      "clone",
      "https://github.com/in-toto/demo-project.git"
    ],
    "environment": {},
    "materials": {},
    "name": "clone",
    "products": {
      "demo-project/foo.py": {
        "sha256": "..."
      }
    }
  }
}

バージョン番号の更新(Bob)

開発者である Bob は、リリース担当の Carl がソースコードをパッケージ化する前に、in-toto-record を使用して、リンク・メタデータ・ファイルを生成します。

リンク・メタデータ・ファイルは、ファイルの状態を記録します。

poetry run in-toto-record start --step-name update-version --key bob --materials demo-project/foo.py

このとき作成される署名付きメタデータは次の通りです。

functionary_bob/.update-version.776a00e2.link-unfinished
{
  "signatures": [
    {
      "keyid": "...",
      "sig": "..."
    }
  ],
  "signed": {
    "_type": "link",
    "byproducts": {},
    "command": [],
    "environment": {},
    "materials": {
      "demo-project/foo.py": {
        "sha256": "..."
      }
    },
    "name": "update-version",
    "products": {}
  }
}

このファイルのポイントは、次の点です。

  1. signatures にはAliceの秘密鍵による署名が付されます。これにより、悪意ある改ざんが困難になります。
  2. materials には、ファイルごとのハッシュ値が記録されます。

次に、Bob はdemo-project/foo.py を更新します。

cat <<EOF > demo-project/foo.py
VERSION = "foo-v1"
EOF

最後に、 Bob は変更後のファイルの状態を記録し、 リンク・メタデータ・ファイル を生成します。

poetry run in-toto-record stop --step-name update-version --key bob --products demo-project/foo.py

このとき作成される署名付きメタデータは次の通りです。

functionary_bob/update-version.776a00e2.link
{
  "signatures": [
    {
      "keyid": "...",
      "sig": "..."
    }
  ],
  "signed": {
    "_type": "link",
    "byproducts": {},
    "command": [],
    "environment": {},
    "materials": {
      "demo-project/foo.py": {
        "sha256": "..."
      }
    },
    "name": "update-version",
    "products": {
      "demo-project/foo.py": {
        "sha256": "..."
      }
    }
  }
}

このファイルのポイントは、次の点です。

  1. signatures にはAliceの秘密鍵による署名が付されます。これにより、悪意ある改ざんが困難になります。
  2. materials には、ファイルごとのハッシュ値が記録されます。

これで、 Bob の作業は完了です。

リリース担当の Carl にソースコードを送ります。

cp -r demo-project ../functionary_carl/

パッケージ(Carl)

Carl は、 in-toto-run package コマンドを実行して、ソフトウェア・プロジェクトのパッケージを作成します。

cd ../functionary_carl
poetry run in-toto-run --step-name package --materials demo-project/foo.py --products demo-project.tar.gz --key carl -- tar --exclude ".git" -zcvf demo-project.tar.gz demo-project

このとき作成される署名付きメタデータは次の通りです。

functionary_carl/package.2f89b927.link
{
  "signatures": [
    {
      "keyid": "...",
      "sig": "..."
    }
  ],
  "signed": {
    "_type": "link",
    "byproducts": {
      "return-value": 0,
      "stderr": "",
      "stdout": ""
    },
    "command": [
      "tar",
      "--exclude",
      ".git",
      "-zcvf",
      "demo-project.tar.gz",
      "demo-project"
    ],
    "environment": {},
    "materials": {
      "demo-project/foo.py": {
        "sha256": "..."
      }
    },
    "name": "package",
    "products": {
      "demo-project.tar.gz": {
        "sha256": "..."
      }
    }
  }
}

このファイルのポイントは、次の点です。

  1. signatures には、Aliceの秘密鍵による署名が付されます。これにより、悪意ある改ざんが困難になります。
  2. command には、パッケージを生成する際に使用したコマンドが記録されます。
  3. materials には、ファイルごとのハッシュ値が記録されます。
  4. products には、パッケージごとのハッシュ値が記録されます。

最終製品の確認(Client)

Client に、最終製品をリリースします。

final_product/ ディレクトリ に、リリースするパッケージとメタデータを格納します。

cd ..
cp owner_alice/root.layout functionary_bob/clone.776a00e2.link functionary_bob/update-version.776a00e2.link functionary_carl/package.2f89b927.link functionary_carl/demo-project.tar.gz final_product/

この結果、final_product/ ディレクトリは次のような構成になります。

final_product/
final_product/
├── clone.776a00e2.link
├── demo-project.tar.gz
├── package.2f89b927.link
├── root.layout
└── update-version.776a00e2.link

Client は、信頼できるソースからAliceの公開鍵を取得して、 in-toto-verify コマンドを実行して、最終製品を検証します。

cd final_product
cp ../owner_alice/alice.pub .
poetry run in-toto-verify --layout root.layout --layout-key alice.pub
echo $?

0が表示されれば、検証がうまく機能したことを意味します。

このとき作成されるインスペクションの結果を示すリンク・メタデータ・ファイルは次の通りです。

final_product/untar.link
{
  "signatures": [],
  "signed": {
    "_type": "link",
    "byproducts": {
      "return-value": 0,
      "stderr": "",
      "stdout": ""
    },
    "command": [
      "tar",
      "xzf",
      "demo-project.tar.gz"
    ],
    "environment": {},
    "materials": {
      ".keep": {
        "sha256": "..."
      },
      "alice.pub": {
        "sha256": "..."
      },
      "demo-project.tar.gz": {
        "sha256": "..."
      },
      "root.layout": {
        "sha256": "..."
      }
    },
    "name": "untar",
    "products": {
      ".keep": {
        "sha256": "..."
      },
      "alice.pub": {
        "sha256": "..."
      },
      "demo-project.tar.gz": {
        "sha256": "..."
      },
      "demo-project/foo.py": {
        "sha256": "..."
      },
      "root.layout": {
        "sha256": "..."
      }
    }
  }
}

in-toto-verify コマンドは、次のことを検証します。

  1. レイアウト・メタデータ・ファイルの署名が有効期限内であること
  2. レイアウトの定義に従っており、Alice の秘密鍵で署名されたこと
  3. ビルドの各ステップが、許可された者によって実行され、署名されたこと
  4. 記録されたマテリアルとプロダクトはアーティファクトのルールに準拠していること
  5. インスペクションの結果が期待通りであること

ソフトウェアサプライチェーンの改ざん

悪意ある人が、ソフトウェア・サプライチェーンを改ざんすることを模擬します。

foo.py に悪意あるコード something evil を追加します。

cd ../functionary_carl
echo something evil >> demo-project/foo.py

Carl は、改ざんに気づかずにパッケージングします。

poetry run in-toto-run --step-name package --materials demo-project/foo.py --products demo-project.tar.gz --key carl -- tar --exclude ".git" -zcvf demo-project.tar.gz demo-project

Client は、改ざんに気づかずに生成された最終製品を受領します。

cd ..
cp owner_alice/root.layout functionary_bob/clone.776a00e2.link functionary_bob/update-version.776a00e2.link functionary_carl/package.2f89b927.link functionary_carl/demo-project.tar.gz final_product/

悪意のある製品の検証

Client は、悪意ある製品を検証します。

$ cd final_product

$ poetry run in-toto-verify --layout root.layout --layout-key alice.pub
(in-toto-verify) RuleVerificationError: 'DISALLOW *' matched the following artifacts: ['demo-project/foo.py']
Full trace for 'expected_materials' of item 'package':
Available materials (used for queue):
['demo-project/foo.py']
Available products:
['demo-project.tar.gz']
Queue after 'MATCH demo-project/* WITH PRODUCTS FROM update-version':
['demo-project/foo.py']

$ echo $?
1

戻り値が 1 ならば、改ざんを検出できたことを意味します。

まとめ

ソフトウェア・サプライチェーン・インテグリティ保証の考え方とそのツール in-toto を紹介しました。

Discussion