こんにちはRed Hatでソリューションアーキテクトをしている石川です。 過去にもOpenShiftでCI/CDの機能を提供するOpenShift Pipelinesについて何度かご紹介してきましたが、今回はOpenShift Pipelinesのv1.7からTech Previewとして利用可能となったTekton Chainsというコンポーネントについて試してみたいと思います。
ATTENTION! Tech Preview機能の位置付けについてはこちらのページを参照して下さい。
そもそもOpenShift Pipelinesとは何か?について知りたい方は過去記事をご参照下さい。
rheb.hatenablog.com rheb.hatenablog.com
Tekton Chainsの概要
まずTekton Chainsが何者なのかを把握するために、GithubのTekton Chainsページを見てみましょう。 github.com
Tekton Chains is a Kubernetes Custom Resource Definition (CRD) controller that allows you to manage your supply chain security in Tekton. In its default mode of operation, Chains works by observing all TaskRuns executions in your cluster. When TaskRuns complete, Chains takes a snapshot of them. Chains then converts this snapshot to one or more standard payload formats, signs them and stores them somewhere.
最初の文を読むと、Tekton ChainsはKubernetesのCRDコントローラーであり、Tektonにおけるサプライチェーンセキュリティを管理する、とあります。
また以降の文では、TektonにおけるTaskRun
の実行結果のスナップショットを標準的なペイロードフォーマットに変換し、署名をした上で保管する、とあります。
上記だけでTekton Chainsの全容を掴むことは中々難しいですが、要約すると、Tektonで実行したパイプラインの成果物に署名を行って、適切に管理するためのツールであると言えるでしょう。
ここで書かれているパイプラインの成果物は、大きく二つに分類することができます。
一つ目はTaskRun
の実行結果、二つ目はパイプラインの中で作成したコンテナイメージです。
一つ目はともかく、二つ目のコンテナイメージについては、これまでもイメージの発行元を確認するために、署名や検証を実施するということはあったため、比較的理解し易いのではないでしょうか。
本記事ではまずTekton Chainsによるコンテナイメージの署名について考えてみたいと思います。
コンテナイメージに署名するメリット
CIパイプラインの中でビルドしたコンテナイメージに対し署名を行うと、どんなメリットが得られるでしょうか?
分かりやすいメリットとしてはセキュリティの向上です。 例として、コンテナレジストリに不正なアクセスを受け、悪意のあるイメージを外部からプッシュされるパターンを考えてみましょう。 コンテナイメージに対する署名や、その検証というプロセスが無い場合、外部からプッシュされたイメージをクラスタ上で稼働させるリスクがあります。
一方でCIパイプラインの中で署名し、その検証を行っていると、外部から勝手にプッシュされたイメージには署名が存在しないため、実際にクラスタ上でコンテナが稼働することを防ぐことが可能となります。
そもそもコンテナレジストリに簡単にアクセスさせない事は非常に重要ですが、上記のようなプロセスを確立しておくことで、万が一の場合もリスクを低減させることができます。
セキュリティ以外にも、例えばISVベンダーが自社のアプリケーションをコンテナとしてユーザーに提供する際にコンテナイメージに署名を行うことで、ユーザー側でイメージの提供元が正しいことを確認することができます。
Tekton Chainsの設定方法
ここからはOpenShift上でTekton Chainsを使用する方法について見ていきたいと思います。 基本的に以下のドキュメントに沿って設定を行っていくためこちらもご参照下さい。 docs.openshift.com
今回使用する環境はOpenShift 4.10で、OpenShift Pipelinesのv1.7をあらかじめインストールしています。 自身でも試してみたいという場合は、OpenShiftクラスタのバージョンとOpenShift Pipelinesのバージョンに注意しましょう。
まず以下のCustom Resourceオブジェクトを作成し、openshift-pipelines
ProjectでTekton Chainsを有効化します。
apiVersion: operator.tekton.dev/v1alpha1 kind: TektonChain metadata: name: chain spec: targetNamespace: openshift-pipelines
その上で設定ファイルに必要な値を入れます。
oc patch configmap chains-config -n openshift-pipelines -p='{"data":{"artifacts.taskrun.format": "in-toto", "artifacts.taskrun.storage": "tekton", "artifacts.taskrun.signer": "x509", "artifacts.oci.format": "simplesigning", "artifacts.oci.storage": "oci", "artifacts.oci.signer": "x509"}}'
設定値を見ると、artifacts.xxx
のxxx
部分でTaskRunを対象とする設定か、OCI(コンテナイメージ)に対する設定かを表しており、それ以下の箇所でフォーマットや、保管場所、署名時のキーとして何を使用するかを決めています。
コミュニティ版のTekton Chainsでは、署名時の鍵として各種クラウドサービスが提供するKMSを使用したり、実験的な機能として、自分自身で鍵管理を不要とするKeyless Signingを設定することも可能です。 設定可能な値についてより詳しく知りたい方は以下を参照してみて下さい。
署名用の鍵作成
ここからは署名に必要な鍵を作成していきます。 鍵の作成にはcosignというツールを使用します。 cosignはsigstoreという、ソフトウェア署名の利用の簡素化を目指すプロジェクトのコンポーネントの一つとなっています。 Tekton Chainsはsigstoreのその他のコンポーネントとも深く関連しているため興味がある方は是非調べてみて下さい。
まずはcosignを作業環境にインストールします。 Githubのリリースページから最新版を取得します。
curl -L https://github.com/sigstore/cosign/releases/download/v1.9.0/cosign-linux-amd64 > cosign-linux-amd64 chmod 755 cosign-linux-amd64 mv cosign-linux-amd64 /usr/local/bin/cosign
# バージョン確認 cosign version --- ______ ______ _______. __ _______ .__ __. / | / __ \ / || | / _____|| \ | | | ,----'| | | | | (----`| | | | __ | \| | | | | | | | \ \ | | | | |_ | | . ` | | `----.| `--' | .----) | | | | |__| | | |\ | \______| \______/ |_______/ |__| \______| |__| \__| cosign: A tool for Container Signing, Verification and Storage in an OCI registry. GitVersion: v1.9.0 GitCommit: a4cb262dc3d45a283a6a7513bb767a38a2d3f448 GitTreeState: clean BuildDate: 2022-06-03T13:47:07Z GoVersion: go1.17.11 Compiler: gc Platform: linux/amd64
cosignを使って鍵を作成します。作成時に暗号鍵に対するパスワードを求められるので適宜設定します。
cosign generate-key-pair k8s://openshift-pipelines/signing-secrets --- Enter password for private key: Enter password for private key again: Successfully created secret signing-secrets in namespace openshift-pipelines Public key written to cosign.pub
上記のコマンド実行結果により、ローカルにはcosign.pub、クラスタ上にはsigning-secretsというSecretが作成されました。 cosign.pubには公開鍵の情報、signing-secretsには公開鍵 + 暗号鍵 + パスワードの情報が含まれています。 間違えてパブリックGitレポジトリ等にこれらの情報をアップロードしたりしないよう注意しましょう。
コンテナビルド用のTaskの確認
CIパイプラインの中でコンテナビルドを行うためには、当然ビルドのためのTaskが必要となるのですが、Tekton Chainsによる署名を行う場合、ビルド用のTaskの実行結果としてIMAGE_DIGEST
とIMAGE_URL
という二つのパラメーターを返却する必要があります。
そのため、これらのパラメーターが返却されるよう使用するTaskに適宜変更を加えます。 以下はs2iのTaskにパラメーター設定を行った例です。
chains-s2i-nodejs.yaml (クリックで展開)
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: s2i-nodejs
labels:
app.kubernetes.io/version: '0.1'
operator.tekton.dev/provider-type: redhat
spec:
description: >-
s2i-nodejs task clones a Git repository and builds and pushes a container
image using S2I and a nodejs builder image.
params:
- default: 14-ubi8
description: The tag of nodejs imagestream for nodejs version
name: VERSION
type: string
- default: .
description: The location of the path to run s2i from.
name: PATH_CONTEXT
type: string
- default: 'true'
description: >-
Verify the TLS on the registry endpoint (for push/pull to a non-TLS
registry)
name: TLSVERIFY
type: string
- description: Location of the repo where image has to be pushed
name: IMAGE
type: string
- default: >-
registry.redhat.io/rhel8/buildah@sha256:e19cf23d5f1e0608f5a897f0a50448beb9f8387031cca49c7487ec71bd91c4d3
description: The location of the buildah builder image.
name: BUILDER_IMAGE
type: string
results:
- description: Digest of the image just built.
name: IMAGE_DIGEST
- description: ''
name: IMAGE_URL
steps:
- command:
- s2i
- build
- $(params.PATH_CONTEXT)
- >-
image-registry.openshift-image-registry.svc:5000/openshift/nodejs:$(params.VERSION)
- '--as-dockerfile'
- /gen-source/Dockerfile.gen
env:
- name: HOME
value: /tekton/home
image: >-
registry.redhat.io/ocp-tools-4-tech-preview/source-to-image-rhel8@sha256:e518e05a730ae066e371a4bd36a5af9cedc8686fd04bd59648d20ea0a486d7e5
name: generate
resources: {}
volumeMounts:
- mountPath: /gen-source
name: gen-source
workingDir: $(workspaces.source.path)
- command:
- buildah
- bud
- '--storage-driver=vfs'
- '--tls-verify=$(params.TLSVERIFY)'
- '--layers'
- '-f'
- /gen-source/Dockerfile.gen
- '-t'
- $(params.IMAGE)
- .
image: $(params.BUILDER_IMAGE)
name: build
resources: {}
volumeMounts:
- mountPath: /var/lib/containers
name: varlibcontainers
- mountPath: /gen-source
name: gen-source
workingDir: /gen-source
- command:
- buildah
- push
- '--storage-driver=vfs'
- '--tls-verify=$(params.TLSVERIFY)'
- '--digestfile=$(workspaces.source.path)/image-digest'
- $(params.IMAGE)
- 'docker://$(params.IMAGE)'
image: $(params.BUILDER_IMAGE)
name: push
resources: {}
volumeMounts:
- mountPath: /var/lib/containers
name: varlibcontainers
workingDir: $(workspaces.source.path)
- image: $(params.BUILDER_IMAGE)
name: digest-to-results
resources: {}
script: >-
cat $(workspaces.source.path)/image-digest | tee
/tekton/results/IMAGE_DIGEST
- image: $(params.BUILDER_IMAGE)
name: url-to-results
resources: {}
script: 'echo $(params.IMAGE) | tee /tekton/results/IMAGE_URL '
volumes:
- emptyDir: {}
name: varlibcontainers
- emptyDir: {}
name: gen-source
workspaces:
- mountPath: /workspace/source
name: source
Tekton HubにあるKanikoのTaskでは必要なパラメーターが設定されているのでそちらを使用してもよいでしょう。
パイプラインの実行と署名
それではいよいよパイプラインを実行してみたいと思います。 今回はソースコードをGitからクローンし、その後s2iによるコンテナビルドを行うシンプルなパイプラインを構成します。
chains-pipeline.yaml (クリックで展開)
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: chains-pipeline
spec:
workspaces:
- name: shared-workspace
description: Git Repo for Patient Health Records Apps
params:
- name: target-path
type: string
- name: git-url
type: string
- name: git-revision
type: string
- name: image-name
type: string
tasks:
- name: git-clone-health
taskRef:
name: git-clone
params:
- name: url
value: $(params.git-url)
- name: revision
value: $(params.git-revision)
workspaces:
- name: output
workspace: shared-workspace
- name: build-container
taskRef:
name: s2i-nodejs
kind: Task
runAfter:
- git-clone-health
workspaces:
- name: source
workspace: shared-workspace
params:
- name: PATH_CONTEXT
value: $(workspaces.source.path)/$(params.target-path)
- name: IMAGE
value: ${IMAGE_REGISTRY}/$(params.image-name):$(params.git-revision)
クラスタにPipelineをデプロイします。
export IMAGE_REGISTRY=$(oc get route -n openshift-image-registry default-route -o "jsonpath={.spec.host}") cat rhf-pipeline.yaml | envsubst |oc apply -f -
対応するPipelineRunを作成し、定義したPipelineを実行しましょう。
chains-pipelinerun.yaml (クリックで展開)
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
generateName: chains-pipeline-run-
spec:
pipelineRef:
name: chains-pipeline
params:
- name: target-path
value: 'site'
- name: git-url
value: 'https://gitlab.com/jpishikawa/rhf2021-cicd-app'
- name: git-revision
value: 'master'
- name: image-name
value: 'tekton-chains/sample-image'
workspaces:
- name: shared-workspace
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
oc create -f chains-pipelinerun.yaml
実行してしばらく待つとPipelineRunが成功します。
さて、無事パイプラインが完了しましたが、今回の目的であるコンテナイメージへの署名はどこから確認できるでしょうか。
実は署名は、ビルドしたコンテナイメージと同じくレジストリに格納されています。(今回の場合OpenShiftの内部レジストリ)
上図の、sample-image:master
がコンテナイメージで、sample-image:sha256-xxx.sig
が署名となっています。
一般的なOCIレジストリでは、コンテナのイメージレイヤー以外にもMedia typeを指定することでBlobを保存することが可能になっています。
コンテナイメージの場合、各レイヤーのMedia typeにapplication/vnd.oci.image.layer.v1.tar+gzip
が設定されていますが、
署名ではapplication/vnd.dev.cosign.simplesigning.v1+json
が設定されています。
(これらはcraneなどのツールを使うことで確認が可能です。)
格納された署名を検証してみましょう。 鍵を生成する際に使ったcosignで署名の検証についても実施することができます。
# 認証 cosign login -u cluster-admin -p $(oc whoami -t) $IMAGE_REGISTRY # ローカルに保存したcosign.pubにより検証を実施 cosign verify --key cosign.pub $IMAGE_REGISTRY/tekton-chains/sample-image:master --- Verification for default-route-openshift-image-registry.apps.mycluster2.t7cy.p1.openshiftapps.com/tekton-chains/sample-image:master -- The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key [{"critical":{"identity":{"docker-reference":"default-route-openshift-image-registry.apps.mycluster2.t7cy.p1.openshiftapps.com/tekton-chains/sample-image"},"image":{"docker-manifest-digest":"sha256:bc3daf4033d41be7434a72615ddbf3b68f54f65e394071a81b9c81bcbdca90c2"},"type":"cosign container image signature"},"optional":null}]
検証の結果から、署名に用いられた鍵が検証時の公開鍵と対になっていることが確認できました。
上記のようにcosignを使用し手動で検証を行う以外に、OPA GatekeeperやKyvernoといったAdmission Controllerを使うことで、コンテナをデプロイする際に署名の検証を自動化する方法が知られています。デプロイ時に署名検証を厳格化するにはこうしたツールを導入することが望ましいと言えるでしょう。
まとめ
今回はTekton Chainsについてご紹介しました。現在はTech Previewであるため、機能としての成熟はこれからというところですが、sigstoreなどの関連プロジェクトの進化も相まって、今後更なる発展が見込めるのではないでしょうか。 昨今ホットなテーマであるSupply Chain Security対策の一つとしてこれからも注目していきたいと思います。