🦺

Google Cloud で実践するソフトウェアサプライチェーンのセキュリティ強化

2024/05/15に公開

はじめに

こんにちは。クラウドエースの間瀬です。
今回はソフトウェアサプライチェーンのセキュリティ強化の必要性について理解を深めつつ、Google Cloud を使って実践していきたいと思います。

ソフトウェアサプライチェーンとは、そこに潜むセキュリティリスクとは

ソフトウェアサプライチェーン (Software Supply Chain) を直訳すると、「ソフトウェア供給の連鎖」という意味になるのですが、これはソフトウェアを構成するコードの開発、ビルド、デプロイといった一連のプロセスを指します。

これらのプロセスにおけるセキュリティリスクは例えば以下のようなものが挙げられます。

1

上図のソフトウェアの開発から運用までの各プロセスにおいて様々なセキュリティリスクが挙げられていますが、これらのリスクが顕在化することで最悪の場合、提供サービスの停止、機密データの漏洩、破損といったインシデントへ繋がることで時間やコストの消費及び、お客様からの信頼を失うような影響が考えられます。

実際に海外の事例として、2020 年頃に SolarWinds 社にてサプライチェーンの侵害による大きなセキュリティインシデントが発生したことをきっかけに注目が高まっているようです。

ソフトウェアサプライチェーンのセキュリティガイドライン SLSA

コミュニティによって、SLSA というソフトウェアサプライチェーンセキュリティのためのガイドラインが整備されています。
当初、SLSA ではバージョン 0.1 としてソフトウェアサプライチェーン全般のセキュリティについて言及されていましたが、記事執筆時点での最新バージョン 1.0 では一連のプロセスの中でも、ビルドプラットフォーム及びアーティファクト(上図ではBuild, Package に該当)に注力して整理されています。

https://slsa.dev/spec/v1.0/about

ビルドプロセスにおけるセキュリティレベルの定義

SLSA ではトラックと呼ばれるプロセスの要素別にセキュリティレベルを定義しています。
トラックとレベルの定義はバージョン 1.0 時点ではビルドのみとなっており、今後バージョンアップに伴って追加されることが期待されます。
※ 尚、バージョン 0.1 ではトラックという定義がない状態でレベルのみ定義されていました。

ビルドトラックにおけるレベル毎に求められる要件は以下の通りです。
詳しくは 公式サイト を参照してください。

レベル 求められる要件
0 何も対策されていない状態
1 ビルド成果物がどのように作成されたかの来歴が存在すること。
2 ビルドプラットフォームによって署名された来歴が存在すること。
3 L2 に加えて、ビルドプロセス同士が影響を受けない分離された環境で実行されること。来歴の署名に利用される秘密情報がユーザステップからアクセスできないこと。

来歴(provenance)について

上記で登場した来歴(provenance)について説明したいと思います。
来歴はビルドによって作成された成果物が「どのソースを基に、どこで、どうやって」作成されたかを示す情報となります。
SLSA では成果物を使用する際に、正しいソースから正しいプロセスでビルドが行われているか来歴を検証せよとされています。

上記レベル 2 でも紹介した通り、来歴については改ざんへの対策として署名することが求められています。
そのため、レベル 2 以上においては、来歴の検証時には署名を検証することも必要となります。

来歴の検証方法

SLSA より来歴を検証するためのツールが提供されているのでこれを利用することで容易に検証が可能です。

現状、本ツールがサポートしているのは、Github Actions にて来歴を作成することができるツール SLSA ジェネレーターと Google Cloud のビルドサービスである Cloud Build になります。

ソフトウェアの部品を管理するための SBOM

SLSA からは少し離れますが、ソフトウェアの部品(例:依存する OSS ライブラリ)を管理することが海外をはじめ国内でも求められ始めています。

例えば、私が過去に関わっていたクレジットカードに関するセキュリティ水準である PCIDSS において、新たなバージョンである 4.0 の要件では SBOM の管理が必要になっています。
また、国内では 2023 年 7 月に経済産業省が SBOM の導入に関する手引きを公表しており、SBOM によるソフトウェアの適切な管理を推進していることが伺えます。

SBOM は食品の成分表のようなもの

SBOM を管理することによってソフトウェアを構成する部品を管理することが可能となるため、特定の部品に脆弱性が見つかった場合においても該当の部品が混入しているかの判断を早期に行うことができます。

Google Cloudにおけるソフトウェアサプライチェーンのセキュリティ強化

Google Cloud ではソフトウェアサプライチェーンのセキュリティソリューションとして、Software Delivery Shield (以下、SDS) が提供されています。
SDS という一つのプロダクトが提供されているわけではなく、以下のようにいくつかのプロダクトを組み合わせて実現するものとなっています。

2

引用元: https://cloud.google.com/software-supply-chain-security/docs/sds/overview?hl=ja

これら各プロダクトの役割については公式サイトにて解説されているので参照してください。

Software Delivery Shiled の一部を実践

記事執筆時点では、上図中の Cloud Code の source protect が利用できない状態であること、Assured Open Source Software が Java と Python の一部のライブラリにしか対応していないことから、これまでに記事内にて紹介した SLSA と SBOM と関連する Cloud Build 以降のプロセスについて実践していきたいと思います。

今回は以下のような構成で実践していきます。
サンプルアプリは掲載を割愛しますが、HTTP リクエストに対して 200 を返すだけの簡単なものを用意しています。

3

上記の構成によって、記事冒頭で挙げたソフトウェアサプライチェーンの一部のセキュリティリスクについて、下図の通り対策が可能です。

但し、下図の左に位置しているコードの脆弱性や内部の開発者が意図的に悪意のあるコードを混入させるようなリスクには対策できていません。また、依存ライブラリにおいても悪意のあるコードが含まれているリスクについても対策はできていません。

10

ビルドフェーズ

Go で実装したサンプルアプリケーションについて、Cloud Build にてコンテナイメージをビルドし、Artifact Registry へ push していきます。

7

Cloud Build における処理内容

深く解説しませんが、Github リポジトリ上の main ブランチへの push をトリガーに起動するようにします。

今回の実践では、以下の処理を行なっています。

  1. golangci-lint による Go のソースコードの静的解析
  2. Open Policy Agent(以下、OPA) による Cloud Run のデプロイに使用するマニフェストのチェック
  3. コンテナイメージのビルド
  4. kritis によるコンテナイメージの脆弱性スキャン&署名
  5. コンテナイメージの SBOM 作成
  6. ビルドの署名付き来歴の作成

上記の 1.2. についてはあまり重要ではありません。OPA では Binary Authorization の使用を強制させるようにチェックしています。
※ Binary Authorization は組織ポリシーを使って組織またはプロジェクト単位に強制させることも可能です。

kritis の利用も必須ではありませんが、参考までに追加しています。今回は Binary Authorization で kritis による署名を必須としたいと思います。利用方法の詳細についてはこちらを参照ください。
※ kritis のコンテナイメージは事前に自身でビルドする必要があることにご注意ください。

Cloud Build のステップ内容は以下の通りです。
※ 今回なるべく自前のコンテナイメージを用意したくなかったので、非効率なところありますが容赦ください。。

  • cloudbuild_build.yaml
steps:
  - name: 'golangci/golangci-lint:v1.57.2'
    args: ['golangci-lint', 'run','.']
    id: ci-lint-code

  - name: 'ubuntu:24.04'
    entrypoint: bash
    args:
    - -c
    - |
      apt-get update && apt-get install -y wget
      wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq
      yq -o json cloudrun/sample-app.yaml > /workspace/ci-target-sample-app.json
    id: ci-conv-yaml

  - name: 'openpolicyagent/opa:latest'
    args: ['eval','--fail-defined','-f','pretty','-d','./opa/opa-policy.rego','-i','/workspace/ci-target-sample-app.json','data.cloudrun.sample_app.errors[_]']
    id: ci-policy-cloud-run-manifest

  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$COMMIT_SHA', '.']
    id: build

  - name: gcr.io/cloud-builders/docker
    entrypoint: /bin/bash
    args:
    - -c
    - |
      docker push asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$COMMIT_SHA &&
      docker image inspect asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$COMMIT_SHA --format '{{index .RepoDigests 0}}' > image-digest.txt &&
      cat image-digest.txt
    id: push-image

  - name: asia-northeast1-docker.pkg.dev/$PROJECT_ID/kritis/kritis-signer
    entrypoint: /bin/bash
    args:
    - -c
    - |
      /kritis/signer \
      -v=10 \
      -alsologtostderr \
      -image=$(/bin/cat image-digest.txt) \
      -policy=kritis-policy/kritis-policy-strict.yaml \
      -kms_key_name=projects/$PROJECT_ID/locations/global/keyRings/kritis/cryptoKeys/kritis-signer/cryptoKeyVersions/1 \
      -kms_digest_alg=SHA256 \
      -note_name=projects/$PROJECT_ID/notes/my-signer-note
    id: vulnsign

  - name: gcr.io/cloud-builders/gcloud
    args: [ 'artifacts','sbom','export','--uri=asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$COMMIT_SHA' ]
    id: export-sbom

images:
  - asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$COMMIT_SHA

options:
  requestedVerifyOption: VERIFIED

現状、Cloud Build では SLSA 0.1 及び、1.0 (プレビュー) をサポートしており、requestedVerifyOption: VERIFIED を yaml に含めることで、署名付きの来歴を作成することができるようになります。

上記内容によってビルド結果のセキュリティ分析情報より SLSA のビルドレベル、来歴、脆弱性スキャンの結果や SBOM を確認することができます。

5
↑小さくて分かりづらいですが、コンソールの履歴から参照できます。
4
↑SLSA ビルドレベル
6
↑ビルドの来歴の一部

SBOM は jsonファイルとして GCS へ格納されており、ファイルのダウンロード、閲覧が可能です。

{
  "spdxVersion": "SPDX-2.3",
  "dataLicense": "CC0-1.0",
  "SPDXID": "SPDXRef-DOCUMENT",
  "name": "sample-app@sha256:f979e28f2205233a4a7a8bcb76ee467d582a035468db0cb2f3e807f3cf336b75",
  "documentNamespace": "https://asia-northeast1-docker.pkg.dev/xxxxxxx/samples/sample-app@sha256:f979e28f2205233a4a7a8bcb76ee467d582a035468db0cb2f3e807f3cf336b75_e03c8a4c-0b4c-4409-ae26-ab97b9b935c4",
  "documentDescribes": [
    "SPDXRef-sample-app-sha256-f979e28f2205233a4a7a8bcb76ee467d582a035468db0cb2f3e807f3cf336b75"
  ],
  "creationInfo": {
    "creators": [
      "Tool: artifactanalysis-0.1",
      "Organization: Google LLC"
    ],
    "created": "2024-05-14T00:17:02Z"
  },
  "packages": [
    {
      "name": "Package-main",
      "SPDXID": "SPDXRef-sample-app-sha256-f979e28f2205233a4a7a8bcb76ee467d582a035468db0cb2f3e807f3cf336b75",
      "versionInfo": "f979e28f2205233a4a7a8bcb76ee467d582a035468db0cb2f3e807f3cf336b75",
      "originator": "Person: Self",
      "downloadLocation": "NOASSERTION",
      "filesAnalyzed": false
    },
    {
      "name": "alpine-baselayout",
      "SPDXID": "SPDXRef-Package-alpine-baselayout-a21e242c-5170-4a24-a295-240c65772719",
      "versionInfo": "3.4.3-r2",
      "supplier": "NOASSERTION",
      "originator": "Person: Natanael Copa <ncopa@alpinelinux.org>",
      "downloadLocation": "NOASSERTION",
      "filesAnalyzed": false,
      "licenseConcluded": "GPL-2.0-only",
      "licenseDeclared": "NOASSERTION",
      "externalRefs": [
        {
          "referenceCategory": "PACKAGE-MANAGER",
          "referenceType": "purl",
          "referenceLocator": "pkg:apk/alpine/alpine-baselayout@3.4.3-r2?arch=x86_64&distro=alpine-3.19&upstream=alpine-baselayout%403.4.3-r2"
        }
      ]
    },
    (以下、かなり省略)
  ]
}

参考までに Cloud Run のマニフェストファイルのチェックに使用した .rego ファイルは以下の通りです。
エラーがある場合、メッセージが返却されます。メッセージが返却されると Cloud Build でエラーとして認識するようにビルドステップでは --fail-defined を使用しています。

  • OPA(opa-policy.rego)
package cloudrun.sample_app

import rego.v1

errors contains msg if {
    expected := "default"
    not input.metadata.annotations["run.googleapis.com/binary-authorization"] == expected
    msg := sprintf("Allowed binary-authorization policy is '%s' only.",[expected])
}

(その他のポリシーは割愛)

Artifact Registry

Cloud Build によって作成されたコンテナイメージの格納先のサービスになります。
SBOM を作成する機能自体は本サービスが提供しており、Cloud Build の処理の中でこの機能を使って SBOM を管理できるようにしています。

デプロイフェーズ

最後にデプロイフェーズでは、ビルドにて作成された、来歴及び、コンテナイメージを検証した上で Cloud Run へデプロイしていきたいと思います。

8

デプロイ自体を行うのは Cloud Deploy ですが、来歴を検証する機能は提供されていないため、Cloud Deploy からデプロイを行う前にここでも Cloud Build を利用して来歴を検証したいと思います。
コンテナイメージの検証については Binary Authorization を利用していきます。

Binary Authorization の事前準備

Binary Authorization を利用して署名されたイメージのみデプロイするために、認証者(公開鍵)を用意して署名を検証できるようにする必要があります。

今回は、以下の条件を満たす場合のみ Cloud Run へのデプロイを許可するものとします。

  • Cloud Build にてビルドされたコンテナイメージであること
  • kritis による脆弱性スキャンをパスしたコンテナイメージであること

それぞれの署名の方法と認証者の設定方法は以下です。

  • Cloud Build
    署名方法については非常に簡単で、上記にて紹介した requestedVerifyOption: VERIFIED を Cloud Build の yaml ファイルへ含めることで署名が行われます。
    認証者の設定については公式サイト に手順が記されているので参考にしてください。

  • kritis
    署名方法については、ビルドフェーズにて実践している通り、スキャン及び署名するためのコンテナイメージを用意し、実行する必要があります。利用方法の詳細については公式サイトを参照ください。
    認証者の設定については少し複雑ですが、こちらの認証者を作成するという手順に従い、kritis のセットアップ時に生成する Cloud KMS の鍵から公開鍵を生成した上で、こちらの認証者を作成するという手順に従って公開鍵を追加する必要があります。

認証者の設定が完了したら、公式サイトを参考に Binary Authorization のポリシーとして認証者による検証が必須となるようにしてください。

Cloud Deploy の事前準備

Cloud Deploy は事前にパイプラインを作成しておいて、デプロイフェーズの中でそのパイプラインを利用してリリースを行う方法を取るため、事前にパイプラインを作成します。
今回は以下のようなファイルを使ってパイプラインを事前に作成しています。

  • sample-pipeline.yaml
apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
  name: sw-delivery-shield-app
description: "deploy pipeline for sample-app"
serialPipeline:
  stages:
    - targetId: prd
      profiles: [prd]
---
apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
  name: prd
description: Cloud Run deploy to prd environment
requireApproval: True
run:
  location: projects/$PROJECT_ID/locations/asia-northeast1

Cloud Build における処理内容

デプロイフェーズの Cloud Build では以下の処理を行います。

  1. 来歴の取得及び、検証
  2. デプロイ対象イメージへのタグ付け
  3. Cloud Deploy のリリース作成

来歴を検証するためには、json 形式となる来歴ファイル及び、対象コンテナイメージの sha256 ハッシュ値が必要になるので、それらを取得した上で、検証ツールである slsa-verifier をインストールして実行しています。

その後、Cloud Deploy から Cloud Run へデプロイするために yaml へのイメージタグの挿入及び、事前準備で作成した Cloud Deploy のパイプラインに対してリリースを作成してデプロイ処理をキックしています。

Cloud Build の yaml ファイルは以下の通りです。

  • cloudbuild_deploy.yaml
steps:
  - name: gcr.io/cloud-builders/gcloud
    entrypoint: /bin/bash
    args:
      - -c
      - |
        gcloud auth configure-docker asia-northeast1-docker.pkg.dev
        gcloud artifacts docker images describe asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$COMMIT_SHA --format json --show-provenance > /workspace/provenance.json
    id: get-provenance

  - name: 'ubuntu:24.04'
    entrypoint: bash
    args:
      - -c
      - |
        apt-get update && apt-get install -y jq
        cat /workspace/provenance.json | jq -r '.provenance_summary.provenance[] | select(.build.inTotoSlsaProvenanceV1.predicateType == "https://slsa.dev/provenance/v1") | .build.inTotoSlsaProvenanceV1.subject[0].digest.sha256' > /workspace/image-digest.txt
    id: get-image-digest

  - name: golang:1.22-bullseye
    entrypoint: /bin/bash
    args:
      - -c
      - |
        go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@v2.5.1
        slsa-verifier verify-image asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app@sha256:$(cat /workspace/image-digest.txt) --provenance-path /workspace/provenance.json --source-uri github.com/$REPO_FULL_NAME --builder-id=https://cloudbuild.googleapis.com/GoogleHostedWorker
    id: verify-provenance

  - name: gcr.io/cloud-builders/gcloud
    entrypoint: /bin/bash
    args:
      - -c
      - |
        gcloud artifacts docker tags add asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app@sha256:$(cat /workspace/image-digest.txt) asia-northeast1-docker.pkg.dev/$PROJECT_ID/samples/sample-app:$TAG_NAME
    id: add-tag

  - name: gcr.io/cloud-builders/gcloud
    entrypoint: /bin/bash
    args:
      - -c
      - |
        sed -i -e s/IMAGE_TAG/$TAG_NAME/ ./cloudrun/sample-app-dev.yaml && sed -i -e s/PROJECT_ID/$PROJECT_ID/ ./cloudrun/sample-app.yaml
        gcloud deploy releases create sample-app-$SHORT_SHA --delivery-pipeline=sw-delivery-shield-app --region=asia-northeast1 --skaffold-file=./clouddeploy/skaffold.yaml
    id: deploy-app

options:
  requestedVerifyOption: VERIFIED

Cloud Deploy

今回は一つのアプリケーションのみデプロイしているため、ほとんど中身がありませんがリリースに使用している skaffold のファイルと Cloud Run のマニフェストファイルは以下の通りです。

  • skaffold.yaml
apiVersion: skaffold/v3
kind: Config
metadata:
  name: sample-app-config
profiles:
  - name: prd
    manifests:
      rawYaml:
        - ../cloudrun/sample-app.yaml
deploy:
  cloudrun: {}
  • Cloud Run のマニフェストファイル(sample-app.yaml)
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: sample-app
  labels:
    cloud.googleapis.com/location: asia-northeast1
  annotations:
    run.googleapis.com/binary-authorization: default  # Binary Authorization の有効化
spec:
  template:
    metadata:
      annotations:
        run.googleapis.com/execution-environment: gen2
        autoscaling.knative.dev/maxScale: '10'
    spec:
      containerConcurrency: 80
      serviceAccountName: cloud-run-service@PROJECT_ID.iam.gserviceaccount.com
      containers:
        - image: asia-northeast1-docker.pkg.dev/PROJECT_ID/samples/sample-app:IMAGE_TAG
        (一部割愛)
          ports:
            - name: http1
              containerPort: 8080
  traffic:
    - percent: 100
      latestRevision: true

Cloud Run を上記のような yaml ファイルを利用してデプロイ運用を行う場合、 Binary Authorization も yaml 上で有効化する必要があります。

まとめ

今回はソフトウェアサプライチェーンのセキュリティガイドラインとなる、SLSA バージョン 1.0 及び、Google Cloud の SDS に触れつつ、その一部を実際に実践しました。
冒頭でも触れたとおり、ソフトウェアサプライチェーンのセキュリティに対する注目は国内外で高まっているため、今後のアップデートに注視しつつ少しずつ実践できるようにしていくといいでしょう。
今回紹介した内容に関連してアップデートがあればまた記事を執筆したいと思います。ありがとうございました。

参考

SLSA サプライチェーンの脅威
Google Cloud ソフトウェアサプライチェーンの脅威

Discussion