Try   HackMD

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-----

$