Cosign & Sigstore: Tandatangani Container Image di 2026

Panduan lengkap Cosign dan Sigstore untuk menandatangani container image tanpa mengelola kunci, plus SBOM attestation dan enforcement Kubernetes di 2026.

Diperbarui: 20 Agustus 2026

Cosign adalah alat command-line dari proyek Sigstore yang menandatangani dan memverifikasi container image (OCI) tanpa perlu mengelola kunci pribadi jangka panjang, cukup identitas OIDC dari GitHub Actions, GitLab CI, atau akun Google/Microsoft. Dalam praktik yang saya jalankan di beberapa pipeline produksi sepanjang 2026, Cosign v2 sudah jadi standar de facto untuk supply chain security container: menandatangani image saat build, menyematkan attestation SBOM, mencatat di transparency log Rekor, lalu memaksa verifikasi di admission controller Kubernetes. Panduan ini menunjukkan alur end-to-end lengkap dengan snippet CI yang siap tempel.

  • Cosign v2.4 (rilis 2026) memakai keyless signing default via OIDC token, jadi tidak ada file cosign.key yang bisa bocor.
  • Setiap tanda tangan dan attestation dicatat di transparency log Rekor sehingga bisa diaudit publik selamanya.
  • Verifikasi memerlukan pencocokan tiga hal: identitas signer (email/URL workflow), issuer OIDC, dan digest image (bukan tag mutable).
  • SBOM attestation format in-toto memungkinkan menyematkan SPDX/CycloneDX ke image lalu diverifikasi oleh admission policy.
  • Policy Controller atau Kyverno di Kubernetes dapat menolak pod yang menjalankan image tak bertanda tangan sebelum menyentuh node.
  • Integrasi GitHub Actions membutuhkan id-token: write permission; tanpa itu Fulcio menolak menerbitkan sertifikat.

Apa itu Cosign dan Sigstore?

Sigstore adalah proyek open-source di bawah Linux Foundation (OpenSSF) yang menyediakan infrastruktur bersama untuk menandatangani artefak perangkat lunak (container image, biner, blob, dan bundle SLSA) tanpa memaksa setiap organisasi mengelola PKI sendiri. Tiga komponen intinya adalah Cosign (CLI signer/verifier), Fulcio (Certificate Authority berumur pendek yang menerbitkan sertifikat X.509 berdasarkan OIDC token), dan Rekor (transparency log append-only berbasis Merkle tree).

Alur konseptualnya sederhana. Saat Anda menjalankan cosign sign, CLI membuat sepasang kunci ephemeral di memori, meminta OIDC token dari identity provider (GitHub Actions, Google, Microsoft, atau instance Dex Anda sendiri), lalu mengirim token itu ke Fulcio yang menerbitkan sertifikat dengan masa hidup 10 menit. Setelah itu Cosign menandatangani digest image, mengunggah sertifikat plus tanda tangan ke Rekor, dan menyimpan referensi tanda tangan di registry OCI sebagai tag terpisah (sha256-abc....sig). Kunci pribadi ephemeral dibuang. Tidak ada rahasia jangka panjang untuk dibocorkan.

Model ini disebut keyless signing, dan sejak Cosign v2.0 ia jadi default. Menurut dokumentasi resmi Sigstore, model kunci statis (dengan file cosign.key terenkripsi) masih didukung tetapi dianggap legacy untuk kasus air-gapped saja.

# Konsep alur singkat (bukan runnable, sekadar ilustrasi)
# 1. cosign sign  →  ephemeral keypair
# 2. request OIDC token dari GitHub Actions / Google / Microsoft
# 3. Fulcio issue cert (valid 10 menit) berisi email/workflow signer
# 4. sign digest image, upload signature + cert ke Rekor
# 5. push signature ke OCI registry sebagai artifact sibling

Mengapa penandatanganan container penting di 2026?

Tiga hal berubah drastis pada 2025-2026 yang menjadikan penandatanganan image bukan lagi sekadar nice to have. Pertama, EU Cyber Resilience Act (CRA) mulai berlaku dan mewajibkan produsen perangkat lunak menyediakan bukti integritas serta SBOM untuk produk digital yang dijual di pasar Uni Eropa. Kedua, SLSA v1.1 menetapkan Level 3 (build platform terisolasi plus signed provenance) sebagai target minimum banyak konsumen enterprise. Ketiga, insiden seperti kompromi build server Ultralytics YOLO 2024 dan XZ backdoor menunjukkan kalau serangan supply chain kini menyasar tahap build, bukan hanya kode sumber.

Untuk container, konsekuensinya konkret. Kalau image Anda didorong ke registry publik atau private tanpa tanda tangan, tidak ada cara kriptografis untuk membedakan build resmi dari build yang di-replay oleh penyerang yang menembus akun CI. Cosign menutup celah ini dengan biaya operasional mendekati nol karena tidak ada kunci yang harus dirotasi. Bagi tim yang sudah menjalankan pemindaian kerentanan, penandatanganan adalah lapisan komplementer, mirip cara Trivy memindai kerentanan container Docker memberitahu apa yang bermasalah, sementara Cosign memberitahu siapa yang membangun dan kapan.

Instalasi Cosign v2 di Linux

Cosign v2.4.x (rilis Q2 2026) adalah versi stabil terbaru saat artikel ini ditulis. Ada tiga cara umum memasangnya di Linux: biner statis dari GitHub Releases, paket Homebrew (yang juga jalan di Linux), atau image container. Untuk server produksi, saya pribadi memilih biner statis karena mudah diverifikasi checksum-nya sebelum eksekusi.

# Ubuntu 24.04 / Debian 12 — biner statis
COSIGN_VERSION="2.4.1"
curl -sSLo cosign https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64
curl -sSLo cosign.sig https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64-keyless.sig
curl -sSLo cosign.pem https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-amd64-keyless.pem

# Verifikasi biner Cosign menggunakan Cosign yang sudah ada (bootstrap)
# atau checksum SHA256 dari release notes
sha256sum cosign
# Bandingkan hasil dengan checksum di https://github.com/sigstore/cosign/releases

sudo install -m 0755 cosign /usr/local/bin/cosign
cosign version

Untuk lingkungan CI yang efemeral, jauh lebih cepat memakai action resmi:

# GitHub Actions step
- uses: sigstore/[email protected]
  with:
    cosign-release: 'v2.4.1'

Snippet CI-ready untuk pipeline GitLab:

# .gitlab-ci.yml
install-cosign:
  image: alpine:3.20
  script:
    - apk add --no-cache curl
    - curl -sSLo /usr/local/bin/cosign https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64
    - chmod +x /usr/local/bin/cosign
    - cosign version

Cara menandatangani container image tanpa kunci

Setelah image di-push ke registry, penandatanganan cukup satu perintah. Cosign akan membuka browser (untuk workflow interaktif) atau otomatis mengambil token dari environment CI (untuk keyless non-interaktif). Perhatikan kalau tanda tangan dibuat terhadap digest image (SHA256), bukan tag. Ini penting. Tag adalah mutable, digest tidak.

# Push image dulu, lalu ambil digest
IMAGE="ghcr.io/mycorp/api:1.4.2"
DIGEST=$(crane digest ${IMAGE})
REF="${IMAGE}@${DIGEST}"

# Interactive keyless signing (buka browser untuk OIDC)
cosign sign ${REF}

# Non-interaktif di CI, token OIDC dari environment
COSIGN_EXPERIMENTAL=1 cosign sign --yes ${REF}

Untuk penggunaan di GitHub Actions, tambahkan permission id-token: write pada job. Tanpa itu, panggilan ke Fulcio akan gagal dengan error could not fetch OIDC token (saya kena bug persis ini pertama kali migrasi ke keyless, sampai saya baca ulang docs). Snippet CI-ready lengkap:

# .github/workflows/sign-image.yml
name: build-and-sign
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write   # WAJIB untuk keyless signing

jobs:
  build:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: sigstore/[email protected]
      - name: Build and push
        id: push
        uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
      - name: Sign the image
        env:
          DIGEST: ${{ steps.push.outputs.digest }}
          TAGS: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
        run: cosign sign --yes "${TAGS}@${DIGEST}"

Cara memverifikasi tanda tangan image

Verifikasi harus memeriksa tiga hal secara ketat: (1) tanda tangan valid untuk digest image, (2) sertifikat diterbitkan untuk identitas signer yang diharapkan, dan (3) sertifikat berasal dari issuer OIDC yang diharapkan. Kalau Anda hanya memeriksa (1), penyerang dengan akun GitHub apapun bisa menandatangani image dengan namanya sendiri dan lolos begitu saja.

# Verifikasi image yang ditandatangani oleh workflow GitHub Actions tertentu
cosign verify \
  --certificate-identity-regexp="^https://github.com/mycorp/api/.github/workflows/.+@refs/tags/v.+" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  ghcr.io/mycorp/api@sha256:abc123...

Perhatikan flag --certificate-identity-regexp. Pakai regex, Anda bisa menuliskan pola seperti "workflow apapun di repo mycorp/api yang berjalan dari tag semver". Untuk verifikasi manusia (developer yang menandatangani dari laptop), gunakan email:

cosign verify \
  --certificate-identity="[email protected]" \
  --certificate-oidc-issuer="https://accounts.google.com" \
  ghcr.io/mycorp/api@sha256:abc123...

Snippet CI-ready untuk gate deployment. Tolak deploy kalau verifikasi gagal:

# Deploy gate di pipeline apapun
IMAGE="ghcr.io/mycorp/api@sha256:abc123..."
if ! cosign verify \
     --certificate-identity-regexp="^https://github.com/mycorp/api/\.github/workflows/build\.yml@refs/tags/v.+" \
     --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
     "${IMAGE}" > /dev/null 2>&1; then
  echo "SIGNATURE VERIFICATION FAILED, refusing to deploy ${IMAGE}"
  exit 1
fi
echo "Signature OK, proceeding with deploy"

SBOM dan attestation in-toto

Tanda tangan biasa hanya membuktikan "seseorang dengan identitas X menandatangani digest Y". Attestation menambahkan payload metadata berformat in-toto predicate, misalnya SBOM, hasil build provenance SLSA, atau hasil pemindaian keamanan, yang juga ditandatangani dan dicatat di Rekor. Ini memungkinkan admission controller memutuskan berdasarkan konten, bukan hanya keberadaan tanda tangan.

Alur khasnya begini: hasilkan SBOM dengan Syft, sematkan sebagai attestation SPDX, lalu verifikasi di sisi konsumen. Untuk pipeline yang membangun container dan juga menjalankan Trivy, kombinasi ini adalah lapisan defense-in-depth yang natural. Kalau Anda belum menerapkan pemindaian runtime, lihat panduan Falco untuk runtime security container untuk menutup celah setelah image berjalan.

# Generate SBOM dan attach sebagai attestation
syft ghcr.io/mycorp/api@sha256:abc123... -o spdx-json > sbom.spdx.json

cosign attest --yes \
  --predicate sbom.spdx.json \
  --type spdxjson \
  ghcr.io/mycorp/api@sha256:abc123...

# Verifikasi attestation SBOM
cosign verify-attestation \
  --certificate-identity-regexp="^https://github.com/mycorp/api/\.github/workflows/.+" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  --type spdxjson \
  ghcr.io/mycorp/api@sha256:abc123...

Snippet CI-ready untuk GitHub Actions yang membuat build provenance SLSA plus SBOM attestation dalam satu job:

# .github/workflows/sign-and-attest.yml (potongan)
- name: Generate SBOM
  uses: anchore/sbom-action@v0
  with:
    image: ghcr.io/${{ github.repository }}@${{ steps.push.outputs.digest }}
    format: spdx-json
    output-file: sbom.spdx.json

- name: Attach SBOM attestation
  run: |
    cosign attest --yes \
      --predicate sbom.spdx.json \
      --type spdxjson \
      "ghcr.io/${{ github.repository }}@${{ steps.push.outputs.digest }}"

Integrasi Cosign di GitHub Actions

GitHub Actions adalah OIDC provider paling matang untuk Sigstore karena setiap workflow otomatis mendapat token dengan klaim yang kaya (repo, workflow, ref, actor). Ini memungkinkan verifikasi granular seperti "hanya terima image dari workflow release.yml pada tag semver di repo produksi". Konfigurasi lengkap membutuhkan tiga elemen: permission id-token: write, cosign-installer action, dan penulisan digest secara eksplisit.

Praktik yang saya rekomendasikan setelah beberapa pipeline produksi: pisahkan job build dari job sign, gunakan artifact atau output digest untuk mengoper referensi, dan simpan checksum Cosign yang dipasang di lockfile atau pinning versi eksplisit. Action yang di-pin ke tag mixed-latest bisa berubah tanpa pemberitahuan (belajar dari kejadian bulan Maret lalu, di mana bump minor merusak flag CLI). Untuk penguatan lebih lanjut pada worker runner sendiri, teknik hardening SSH dan network yang saya bahas di panduan nftables untuk server tetap relevan biar runner tidak jadi titik lemah.

# Reusable workflow untuk sign + attest
name: sign-and-verify
on:
  workflow_call:
    inputs:
      image:
        required: true
        type: string
      digest:
        required: true
        type: string

permissions:
  id-token: write
  contents: read
  packages: write

jobs:
  cosign:
    runs-on: ubuntu-24.04
    steps:
      - uses: sigstore/[email protected]
        with:
          cosign-release: 'v2.4.1'
      - uses: anchore/sbom-action@v0
        with:
          image: ${{ inputs.image }}@${{ inputs.digest }}
          format: spdx-json
          output-file: sbom.spdx.json
      - name: Sign & attest
        run: |
          cosign sign --yes "${{ inputs.image }}@${{ inputs.digest }}"
          cosign attest --yes \
            --predicate sbom.spdx.json \
            --type spdxjson \
            "${{ inputs.image }}@${{ inputs.digest }}"
      - name: Self-verify
        run: |
          cosign verify \
            --certificate-identity-regexp="^https://github.com/${{ github.repository }}/.+" \
            --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
            "${{ inputs.image }}@${{ inputs.digest }}"

Enforcement di Kubernetes dengan Policy Controller

Menandatangani image tidak berarti apa-apa kalau cluster tetap menjalankan image tak bertanda tangan. Enforcement di sisi cluster dilakukan dengan admission controller. Dua opsi mainstream adalah Sigstore Policy Controller (proyek resmi Sigstore) dan Kyverno dengan verifyImages rule. Keduanya mem-block pod pada tahap admission sebelum menyentuh node.

Berikut contoh Kyverno ClusterPolicy yang menolak pod dari registry ghcr.io/mycorp/* kecuali image ditandatangani oleh workflow GitHub Actions tertentu:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-cosign-signature
spec:
  validationFailureAction: Enforce
  webhookTimeoutSeconds: 30
  rules:
    - name: verify-images
      match:
        any:
          - resources:
              kinds: [Pod]
      verifyImages:
        - imageReferences:
            - "ghcr.io/mycorp/*"
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/mycorp/api/.github/workflows/release.yml@refs/tags/*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev
          mutateDigest: true
          verifyDigest: true

Perhatikan mutateDigest: true. Kyverno akan menulis ulang tag :v1.4.2 menjadi @sha256:... di pod spec setelah verifikasi lulus, mencegah race condition di mana tag berubah antara admission dan pull. Menurut dokumentasi Kyverno untuk Sigstore, ini adalah default best practice untuk verifyImages.

Snippet CI-ready untuk mendeploy policy via ArgoCD atau kubectl:

# Terapkan policy dan verifikasi sedang enforce
kubectl apply -f require-cosign-signature.yaml
kubectl get clusterpolicy require-cosign-signature -o jsonpath='{.status.ready}'
# expected: true

# Test negatif, deploy image tak bertanda tangan
kubectl run test-unsigned --image=nginx:latest --dry-run=server
# expected error: image signature verification failed

Menghindari noise dan false positive verifikasi

Verifikasi Cosign yang overzealous bisa menghasilkan banyak alert palsu yang bikin tim mengabaikan yang asli. Beberapa pola noise yang biasa saya matikan biar pipeline tetap tenang: (a) verifikasi terhadap image third-party yang tidak Anda kontrol, jangan paksa signature untuk image seperti nginx, redis, atau postgres resmi kecuali Anda sudah mem-mirror ke registry internal dan menandatangani sendiri; (b) alarm tentang sertifikat Fulcio yang "expired", sertifikat Fulcio memang hanya hidup 10 menit dan itu fitur, bukan bug, verifikasi tetap valid karena timestamp dari Rekor; (c) peringatan tentang predicate type yang tidak dikenal ketika Anda hanya peduli tanda tangan dasar.

Untuk cluster hybrid dengan image internal dan external, gunakan namespace selector atau exclusion di policy, jangan memaksa satu aturan global. Kombinasi log dari Kyverno plus auditd untuk audit compliance di node sudah cukup untuk trail forensik lengkap tanpa membanjiri SIEM dengan false positive.

# Pola exclusion Kyverno, biarkan image system namespace lewat
spec:
  rules:
    - name: verify-images
      match:
        any:
          - resources:
              kinds: [Pod]
      exclude:
        any:
          - resources:
              namespaces: [kube-system, kyverno, cert-manager]

Pertanyaan yang Sering Diajukan

Apa perbedaan Cosign dengan Docker Content Trust (Notary)?

Docker Content Trust berbasis Notary v1 mengharuskan setiap organisasi mengelola root of trust dan kunci offline sendiri, dan sudah tidak dikembangkan aktif. Cosign menggunakan model keyless via Sigstore, jadi tidak perlu kunci jangka panjang, transparency log publik gratis, dan didukung native oleh registry OCI modern seperti GHCR, ECR, dan Harbor.

Apakah Cosign dan Sigstore gratis untuk penggunaan produksi?

Ya. CLI Cosign adalah Apache-2.0 dan layanan publik Fulcio serta Rekor dioperasikan Linux Foundation tanpa biaya, dengan SLA best-effort yang cukup baik (99.5%+ selama 2025-2026). Organisasi yang memerlukan SLA lebih ketat atau isolasi bisa menjalankan instance private Fulcio/Rekor sendiri.

Apakah Cosign mendukung image multi-arch atau OCI index?

Ya, tapi Anda harus menandatangani digest index dan setiap platform-specific digest kalau ingin verifikasi berjalan di semua arsitektur. Cosign v2.4 punya flag --recursive yang otomatis menandatangani semua manifest dalam sebuah index, menyederhanakan alur multi-arch build.

Bisakah saya menandatangani image di registry private tanpa internet publik?

Bisa. Untuk lingkungan air-gapped, jalankan instance Fulcio dan Rekor sendiri di dalam network Anda, atau gunakan mode kunci statis (cosign generate-key-pair). Kunci pribadi disimpan di HSM atau KMS (AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault), dan Cosign mendukung semuanya via URI seperti awskms://....

Bagaimana cara memverifikasi image saat docker pull tanpa admission controller?

Sertakan langkah cosign verify sebagai gate di pipeline deployment sebelum image direferensikan dalam manifest Kubernetes atau docker-compose. Untuk verifikasi ad-hoc di workstation, jalankan cosign verify sebelum docker pull dan gunakan digest hasil verifikasi (bukan tag) di perintah pull berikutnya.

Raj Patel
Tentang Penulis Raj Patel

DevSecOps engineer who's gradually turning every CI pipeline he sees into a security-checking machine.