Hari 19: Sign Docker Image Pakai Cosign, Verifikasi Integritas
6 min read

Hari 19: Sign Docker Image Pakai Cosign, Verifikasi Integritas

Tanda tangan Docker image dengan Cosign supaya kalau diubah orang, langsung ketahuan. Push ke GHCR, sign, verify — 3/3 check lulus.

devsecops
cosign
image-signing
ghcr
sigstore
60-days-challenge
Share

Kenapa Harus Sign Docker Image?

Bayangin kasus ini: seseorang berhasil masuk ke Docker registry kamu, lalu mengganti image securebank:v1 dengan versi yang sudah disisipin backdoor. Tim DevOps pull image itu, deploy ke production, dan boom — attacker punya akses ke sistem.

Tanpa image signing, tidak ada cara untuk membedakan image asli dari image yang sudah di-tamper.

Image signing itu seperti tanda tangan notaris di dokumen. Kalau isinya berubah, tanda tangannya gak cocok lagi. Verifikasi signature sebelum deploy = pastikan image belum diubah sejak di-sign.

Hari ini kita pakai Cosign dari Sigstore project untuk sign Docker image SecureBank API.

Install Cosign

Cosign dibuat oleh Sigstore (Linux Foundation project). Cara install-nya:

# Download binary langsung dari GitHub releases (lebih cepat dari Homebrew)
curl -sL -o /opt/homebrew/bin/cosign \
  "https://github.com/sigstore/cosign/releases/download/v3.1.1/cosign-darwin-arm64"
chmod +x /opt/homebrew/bin/cosign
cosign version

Versi yang terinstall: Cosign v3.1.1, Go 1.26.3.

Generate Key Pair

Cosign pakai konsep asymmetric cryptography — sama seperti SSH key atau GPG key:

  • Private key (cosign.key) — dipakai untuk sign. Hanya yang punya key ini yang bisa sign image.
  • Public key (cosign.pub) — dipakai untuk verify. Siapapun yang punya key ini bisa verifikasi signature.
$ COSIGN_PASSWORD="" cosign generate-key-pair
Private key written to cosign.key
Public key written to cosign.pub

COSIGN_PASSWORD="" supaya gak interaktif (gak minta input password). Untuk challenge ini, key tanpa password sudah cukup. Di production, private key harus pakai password dan disimpan di secret manager.

Penting: Private Key Tidak Boleh Masuk Repo

# .gitignore
cosign.key

cosign.key ditambahkan ke .gitignore. Private key itu kunci rumah — kalau hilang atau jatuh ke tangan orang, mereka bisa sign image palsu atas nama kamu.

Public key (cosign.pub) aman di-commit. Kayak nomor rekening — bisa dibagi, tapi gak bisa dipakai untuk tarik uang.

Push Image ke GHCR

Sebelum bisa sign, image harus ada di registry. Kita pakai GitHub Container Registry (GHCR):

# Tag image untuk GHCR
docker tag securebank:v1 ghcr.io/stayrelevantid/securebank:v1

# Login ke GHCR (butuh GitHub PAT scope write:packages)
echo $GITHUB_TOKEN | docker login ghcr.io -u stayrelevantid --password-stdin

# Push image
docker push ghcr.io/stayrelevantid/securebank:v1

Hasilnya: image ghcr.io/stayrelevantid/securebank:v1 sudah ada di registry, digest sha256:df0ecb33....

Sign Image

Sekarang sign image-nya:

$ COSIGN_PASSWORD="" cosign sign --key cosign.key ghcr.io/stayrelevantid/securebank:v1

Signing artifact...
Pushing signature to: ghcr.io/stayrelevantid/securebank

Apa yang terjadi di belakang layar?

  1. Cosign menghitung SHA256 hash dari image manifest
  2. Hash tersebut di-sign dengan private key menggunakan algoritma ECDSA
  3. Signature (bukan image-nya) di-push ke registry sebagai OCI artifact
  4. Signature juga dikirim ke Rekor — transparency log di Sigstore

Image aslinya tidak berubah. Signature tersimpan terpisah di registry.

Warning: Tag vs Digest

Cosign memberi warning:

WARNING: Image reference uses a tag, not a digest, to identify the image to sign.

Ini karena tag (:v1) bisa di-repoint ke image berbeda kapan saja. Best practice di production: pakai digest (@sha256:...) untuk signing. Tapi untuk challenge ini, tag v1 sudah cukup karena kita kontrol full lifecycle image.

Verify Signature

Sekarang verifikasi:

$ COSIGN_PASSWORD="" cosign verify --key cosign.pub ghcr.io/stayrelevantid/securebank:v1

Verification for ghcr.io/stayrelevantid/securebank:v1 --
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":"ghcr.io/stayrelevantid/securebank:v1"},
"image":{"docker-manifest-digest":"sha256:df0ecb33..."},
"type":"https://sigstore.dev/cosign/sign/v1"},"optional":{}}]

3 checks lulus:

  1. Cosign claims validated — metadata signature valid (image reference, digest, type)
  2. Transparency log verified — signature tercatat di Rekor (tidak bisa diyangkal)
  3. Public key match — signature cocok dengan public key yang kita punya

Kalau image di-tamper di registry, verifikasi akan gagal karena hash image tidak akan cocok dengan yang ada di signature.

Alurnya: Build → Push → Sign → Verify → Deploy

Developer          CI/CD              Registry           Deployment
   |                  |                  |                   |
   |--- build ------->|                  |                   |
   |--- push -------->|--- push -------->|                   |
   |--- sign -------->|--- sign -------->|                   |
   |                  |--- transparency->| (Rekor log)       |
   |                  |                  |                   |
   |                  |                  |<--- pull -------|
   |                  |                  |<--- verify -----|
   |                  |                  |              [deploy if valid]

Di deployment (Kubernetes, Fase 3), kita bisa pakai Cosign verifier untuk auto-reject image yang signature-nya tidak valid. Ini namanya policy enforcement — image yang tidak di-sign tidak boleh deploy.

Private Key vs Public Key

Private Key (cosign.key) Public Key (cosign.pub)
Fungsi Sign image Verify signature
Simpan di mana GitHub Secret / Vault / KMS Di-commit ke repo
Bisa di-share? TIDAK. Rahasia. Ya, aman untuk publik
Kalau bocor? Attacker bisa sign image palsu Tidak masalah — public key cuma untuk verify
Di .gitignore? Ya Tidak

Konsep ini sama seperti SSH key pair: private key (~/.ssh/id_ed25519) tetap rahasia, public key (~/.ssh/id_ed25519.pub) di-share ke GitHub/server.

Transparency Log (Rekor)

Salah satu fitur Cosign yang menarik: transparency log via Rekor.

Setiap signature yang dibuat Cosign otomatis dikirim ke Rekor — log publik yang tidak bisa diubah. Ini berfungsi sebagai:

  1. Audit trail — siapa sign apa, kapan, dengan key mana
  2. Non-repudiation — signer tidak bisa bilang "saya gak pernah sign image itu"
  3. Detect compromise — kalau key bocor, semua signature yang dibuat dengan key itu bisa di-track

Saat verifikasi, Cosign cek apakah signature ada di transparency log. Kalau tidak ada, verifikasi gagal — kemungkinan signature palsu.

Lesson Learned

1. Image signing itu tentang integritas, bukan enkripsi. Cosign tidak mengubah image. Dia bikin signature terpisah yang bisa diverifikasi. Kalau image diubah, hash-nya berubah, signature tidak cocok.

2. Private key tidak pernah meninggalkan environment trusted. cosign.key di .gitignore, tidak di-commit. Di CI, private key di-store sebagai GitHub Secret. Public key aman di-commit — siapapun bisa verify, tapi cuma yang punya private key yang bisa sign.

3. Transparency log = non-repudiation. Cosign mengirim signature ke Rekor (transparency log). Verifikasi mencek signature ada di log. Signer tidak bisa menyangkal bahwa mereka sign image itu.

4. Tag vs digest itu tradeoff. Tag lebih mudah dibaca (v1, v2), tapi bisa di-repoint. Digest (@sha256:...) immutable, tapi panjang dan susah dibaca. Untuk production, pakai digest. Untuk dev, tag sudah cukup.

5. GHCR gratis untuk public repo. GitHub Container Registry tidak butuh subscription untuk public images. Pakai GitHub PAT dengan scope write:packages untuk push. Gratis dan terintegrasi dengan akun GitHub yang sudah ada.

Kesimpulan

Hari ini kita sign Docker image SecureBank API pakai Cosign. Image yang ada di GHCR sekarang punya tanda tangan kriptografis yang bisa diverifikasi kapan saja.

Prosesnya:

  1. Generate key pair (private + public)
  2. Push image ke registry
  3. Sign image dengan private key
  4. Verify signature dengan public key

Kalau seseorang mengganti image di registry, verifikasi akan gagal. Signature tidak cocok karena hash image sudah berubah.

Ini adalah fondasi untuk supply chain security — memastikan bahwa image yang di-deploy ke production benar-benar image yang kita build, bukan image yang sudah di-tamper.

Besok: Hari 20 — Terraform Setup + IaC Scan (Checkov) — mulai bikin infrastructure as code dengan Terraform dan scan misconfiguration-nya.

Repo: github.com/stayrelevantid/chalange-devsecops

Enjoyed this article? Share it!

Share

Diskusi & Komentar

Artikel Terkait