# How to Sign a container image with Cosign ## 1. 甚麼是 Cosign? Cosign 是一個的工具,它可以在符合 OCI(Open Container Initiative,開放貨櫃計畫)標準的儲存庫中,對軟體產物進行簽署、驗證以及儲存。雖然 Cosign 最初是針對 containers (貨櫃)及與 container-related(貨櫃有關)的產物而開發,但它同樣適用於開源軟體套件和其他各種檔案類型。 例如,Cosign 可以用來對各種資料進行簽署,包括 Container image、blobs (binary large objects)、像 README 檔、SBOM(軟體物料清單)、 Kubernetes Helm Charts、Tekton bundles(an OCI artifact containing Tekton CI/CD resources like tasks)等等。 透過對軟體簽署,你可以證明「你」確實是你所聲明的身份,這也能建立一個信任基礎,使得開發者和使用你軟體的人能夠驗證該軟體產物確實是由你創建的。同時,他們也能確認該產物未被第三方竄改。對於在開發流程中需要使用軟體庫、貨櫃或其他產物的人來說,已簽署的產物能夠讓你更放心,因為你知道所採用的程式碼或貨櫃來自一個值得信賴的來源。 ## 2. 安裝 Cosign binary ```! $ sudo curl -o /usr/local/bin/cosign \ -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" $ sudo chmod +x /usr/local/bin/cosign # Check Version $ cosign version ______ ______ _______. __ _______ .__ __. / | / __ \ / || | / _____|| \ | | | ,----'| | | | | (----`| | | | __ | \| | | | | | | | \ \ | | | | |_ | | . ` | | `----.| `--' | .----) | | | | |__| | | |\ | \______| \______/ |_______/ |__| \______| |__| \__| cosign: A tool for Container Signing, Verification and Storage in an OCI registry. GitVersion: v2.4.1 GitCommit: 9a4cfe1aae777984c07ce373d97a65428bbff734 GitTreeState: clean BuildDate: 2024-10-03T17:01:50Z GoVersion: go1.22.7 Compiler: gc Platform: linux/amd64 ``` ## 3. 產生 Self-Managed Keys ```! $ mkdir sign; cd sign $ cosign generate-key-pair Enter password for private key: Enter password for private key again: Private key written to cosign.key Public key written to cosign.pub $ ls -l total 8 -rw-------. 1 rancher users 653 Feb 3 23:31 cosign.key -rw-r--r--. 1 rancher users 178 Feb 3 23:31 cosign.pub ``` ## 4. Sign a container image and store the signature in the registry ``` $ podman login docker.io --compat-auth-file ~/.docker/config.json Username: hahappyman Password: Login Succeeded! $ cosign sign --key cosign.key docker.io/hahappyman/alpine Enter password for private key: ... By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above. Are you sure you would like to continue? [y/N] y tlog entry created with index: 168432318 Pushing signature to: index.docker.io/hahappyman/alpine # 驗證 container image 有簽名 $ cosign verify --key cosign.pub docker.io/hahappyman/alpine | jq . Verification for index.docker.io/hahappyman/alpine:latest -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The signatures were verified against the specified public key [ { "critical": { "identity": { "docker-reference": "index.docker.io/hahappyman/alpine" }, "image": { "docker-manifest-digest": "sha256:def822f9851ca422481ec6fee59a9966f12b351c62ccb9aca841526ffaa9f748" }, "type": "cosign container image signature" }, "optional": { "Bundle": { "SignedEntryTimestamp": "MEUCIDbGUgBokwb4V+MIbOu8tCt05dUDiTD6YsYKACY9cWveAiEA0jWB6gkq1Vp1bZ0/A+wQNw9qKjiYxZC/um47n8ueJZc=", "Payload": { "body": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiIwNjEwNzBmZDAwOTk3MWQ3MzA1ZWU2MjE2NmIxZWJhMTVhMjI5OTdjMGEzZmZiMWQyNWY2NzMwYjY1OGNlM2M2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRzlrSjcwLzRIQll6d3FjOHBBeTRoNFc0d0toc0k4NzZlTStCNlJOS0IyOUFpRUExT0JuTDg1TEc0c0poSTkyaCtzcTNiS2JnSm9rcVFXclVMbGpqdlg0My9RPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCUVZVSk1TVU1nUzBWWkxTMHRMUzBLVFVacmQwVjNXVWhMYjFwSmVtb3dRMEZSV1VsTGIxcEplbW93UkVGUlkwUlJaMEZGWWsxNE1sWmFOR0o2VFRoMWVsRjNjbE1yUlZsa1RrUTNaaXMxTUFwR1pGa3hkM05NYUVkelEyOVRXRmw2VUdsaWJIZDRVRkJIVjBadE9HbEJaSGhUV1RVNE5tUmhUblY2Y2xFMmF6QldTRTl4UkVNNFRtMW5QVDBLTFMwdExTMUZUa1FnVUZWQ1RFbERJRXRGV1MwdExTMHRDZz09In19fX0=", "integratedTime": 1738636249, "logIndex": 168432318, "logID": "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d" } } } } ] ``` ## 5. 模擬竄改 contaimer image 並上傳至 registry ``` $ sudo podman build -t docker.io/hahappyman/alpine -f - . <<EOF FROM quay.io/cloudwalker/alpine CMD echo just a test EOF $ sudo podman push docker.io/hahappyman/alpine Getting image source signatures Copying blob 3e01818d79cd done | Copying config bdfe5ec11e done | Writing manifest to image destination ``` ## 6. 確認為未經驗證的 contaimer image ```! $ cosign verify --key cosign.pub docker.io/hahappyman/alpine | jq . Error: no signatures found main.go:69: error during command execution: no signatures found ``` ## 7. 使用 Trivy 掃描 contaimer image 以找出漏洞並產生證明 ```! $ curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo s h -s -- -b /usr/local/bin v0.41.0 $ trivy image --ignore-unfixed --format cosign-vuln --output vuln.json docker.io/hahappyman/alpine@sha256:def822f9851ca422481ec6fee59a9966f12b351c62ccb9aca841526ffaa9f748 # 產生證明 ``` ## 8. 設定只允許有簽署過的 image 可以被部屬進 Kubernetes ```! # 1. 安裝 Kyverno $ kubectl create -f https://github.com/kyverno/kyverno/releases/download/v1.13.0/install.yaml # 2. 確認 Kyverno pod 正常運作 $ kubectl -n kyverno get pods NAME READY STATUS RESTARTS AGE kyverno-admission-controller-545dd46f74-j8plr 1/1 Running 0 7m36s kyverno-background-controller-5c8cbbb888-tqlf2 1/1 Running 0 7m36s kyverno-cleanup-controller-7494bd4d8f-vrsxk 1/1 Running 0 7m36s kyverno-reports-controller-69989df659-nw28s 1/1 Running 0 7m36s # 3. Install a policy $ cat <<EOF | envsubst | kubectl apply -f - apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: check-vulnerabilities spec: validationFailureAction: Enforce background: false webhookTimeoutSeconds: 30 failurePolicy: Fail rules: - name: checking-vulnerability-scan-not-older-than-one-hour match: any: - resources: kinds: - Pod verifyImages: - imageReferences: - "*" attestations: - type: https://cosign.sigstore.dev/attestation/vuln/v1 conditions: - all: - key: "{{ time_since('','{{ metadata.scanFinishedOn }}', '') }}" operator: LessThanOrEquals value: "1h" attestors: - count: 1 entries: - keys: publicKeys: |- $(sed 's/^/ /' ~/sign/cosign.pub) EOF $ kubectl run app-unsigned --image=docker.io/anaisurlichs/cns-website:0.1.1 Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request: resource Pod/default/app-unsigned was blocked due to the following policies check-vulnerabilities: checking-vulnerability-scan-not-older-than-one-hour: | image attestations verification failed, verifiedCount: 0, requiredCount: 1, error: no matching attestations: error verifying bundle: comparing public key PEMs, expected -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbMx2VZ4bzM8uzQwrS+EYdND7f+50 FdY1wsLhGsCoSXYzPiblwxPPGWFm8iAdxSY586daNuzrQ6k0VHOqDC8Nmg== -----END PUBLIC KEY----- , got -----BEGIN CERTIFICATE----- MIIC1TCCAlugAwIBAgIUCaDDp3gxDc+aCC2jACWbBSleuCYwCgYIKoZIzj0EAwMw NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl cm1lZGlhdGUwHhcNMjQwMTA4MTQyNDEzWhcNMjQwMTA4MTQzNDEzWjAAMFkwEwYH KoZIzj0CAQYIKoZIzj0DAQcDQgAE0XTT4yiarpOHiQLZviJBN4WXb1ibml5JMHHl CRYBS3+WVkrEtDN1P/tnDlaKzM8AuKjOebvBO6ElY5O6ng4VpaOCAXowggF2MA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUfMeV lPkZSIEl2vp33nABx42ptzQwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y ZD8wJAYDVR0RAQH/BBowGIEWdXJsaWNoc2FuYWlzQGdtYWlsLmNvbTAsBgorBgEE AYO/MAEBBB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwLgYKKwYBBAGD vzABCAQgDB5odHRwczovL2dpdGh1Yi5jb20vbG9naW4vb2F1dGgwgYoGCisGAQQB 1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAA AYzpdbEbAAAEAwBHMEUCIQCXkciXtNvEvXk+G5XymRiprfSF+YEqYrBFbVIk7h4Z MwIgUIHzK5MczeS90QxhwX/lVNOay86CHMGknmFpLgUb8/4wCgYIKoZIzj0EAwMD aAAwZQIwQtSOZauh5ubyjj3MR+Sn6E3BPBMt2TuC2aaoLSFUHm/FP6RYFTpFgOPW Ow/wWkyRAjEApKPUppzjYmWzPwDhKNFOvzLmjng00lrCmIXYajGhv1FW8l0jZTgb KfN07EfupRrM -----END CERTIFICATE----- $ ```