A múlt héten egy teljes szombatot azzal töltöttem, hogy végigauditáltam minden GitHub Actions workflowt az összes repónkban. Nem azért, mert akartam, hanem azért, mert a Trivy elleni supply chain támadás ráébresztett, milyen vékony jégen táncolok.
Ha lemaradtál volna: valaki sikeresen becsempészett egy rosszindulatú commitot az actions/checkout actionbe, kihasználva a GitHub fork commit elérhetőségi mechanizmusát. Kicseréltek egy SHA pint a Trivy release workflowjában úgy, hogy egy fork-ban lévő árva commitra mutasson. A commit legitimnek tűnt, a komment azt mondta # v6.0.2, a szerző egy valódi maintainer nevével volt meghamisítva. A tényleges payload Go fájlokat töltött le egy elgépelt domainről és kicserélte a Trivy forráskódját a build során.
Két sor változott. Összesen tizennégy sor a diffben, a legtöbb kozmetikai zaj, mint egyes idézőjelek cseréje kettősre. Az a fajta PR, amit azért hagynak jóvá, mert “nincs mit nézni rajta.”
Miért nem elég a SHA pinning
Évek óta mondom mindenkinek, hogy tag helyett SHA-val pineljék az actionöket. Ez továbbra is jó tanács. De a Trivy támadás megmutatta, hogy a SHA pinningnek van egy holtfoltja, amire a legtöbben nem gondoltunk.
A GitHub architektúrája lehetővé teszi, hogy a fork commitok SHA alapján elérhetők legyenek a szülő repóból. Tehát ha valaki forkol egy actions/checkout-ot, push-ol egy rosszindulatú commitot, és te ezt a SHA-t hivatkozod a szülő repóból, a GitHub boldogan feloldja. A commitnak nem kell branchon lennie. Nem kell mergelve lennie. Csak léteznie kell valahol a fork hálózatban.
Ez teljesen megváltoztatja a fenyegetettségi modellt. Nem csak az action karbantartóiban bízol. Abban is bízol, hogy a teljes fork gráfban senki nem push-olt valami rosszindulatút, ami épp megfelel egy általad hivatkozott SHA-nak.
Mit változtattam konkrétan
Így nézett ki az auditom és mit javítottam.
1. Action allowlist az org szintjén
A GitHub lehetővé teszi, hogy korlátozd, mely actionök futhatnak az orgodban. Az “összes engedélyezése” beállításról átálltam explicit allowlistre:
# .github/settings.yml (vagy org settings UI)
actions_permissions:
allowed_actions: selected
github_owned_allowed: true
verified_creators_allowed: true
patterns_allowed:
- "aquasecurity/*"
- "docker/*"
- "hashicorp/*"
Ez nem fog mindent megállítani, de korlátozza a robbanási sugarat.
2. Pinelés ÉS ellenőrzés
A SHA pinning marad, de mostantól ellenőrzöm, mi van a másik végén. Írtam egy kis scriptet, ami feloldja az egyes pinelt SHA-kat és megnézi, hogy tényleg egy tages release branchen élnek-e:
#!/bin/bash
# verify-action-pins.sh
set -euo pipefail
grep -rh "uses:" .github/workflows/*.yml | \
grep "@" | \
sed 's/.*uses: //' | \
sort -u | \
while read -r action; do
repo=$(echo "$action" | cut -d@ -f1)
sha=$(echo "$action" | cut -d@ -f2)
tags=$(gh api "repos/${repo}/git/ref/tags" --paginate -q '.[].ref' 2>/dev/null || echo "")
found=false
for tag_ref in $tags; do
tag_sha=$(gh api "repos/${repo}/git/${tag_ref}" -q '.object.sha' 2>/dev/null || echo "")
if [[ "$tag_sha" == "$sha" ]]; then
found=true
break
fi
done
if [[ "$found" == "false" ]]; then
echo "FIGYELEM: $action - SHA nem található egyetlen tagen sem"
fi
done
Nem szép, de elkapja az árva commitokat.
3. Read-only tokenek alapértelmezésben
Minden workflow mostantól explicit jogosultságokat használ:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # csak ha tényleg kell
Ha a workflowod nem deklarál jogosultságokat, a GitHub egy alapértelmezett tokent ad neki, amivel írhat a repódba. Ez a különbség a “a támadó elolvasta a forrásodat” és a “a támadó pusholt a main branchedre” között.
4. Hálózati kimenő forgalom figyelése
A Trivy payload a scan.aquasecurtiy[.]org címre hívott ki (figyeld az elgépelést a “security”-ben). Ha self-hosted runnereket használsz, blokkolhatod a váratlan kimenő forgalmat. GitHub-hosted runnereknél korlátozottabbak a lehetőségeid, de legalább hozzáadhatsz egy lépést, ami logolja a DNS lekérdezéseket:
- name: DNS baseline rögzítése
run: |
cat /etc/resolv.conf
resolvectl query github.com || true
Nem igazi védelem, de forensic adatot ad egy incidens után.
5. Review bot a workflow változásokhoz
Minden PR, ami .github/workflows/ fájlokat érint, mostantól a security csapat jóváhagyását igényli. Ezt CODEOWNERS-szel kényszerítjük ki:
# .github/CODEOWNERS
.github/workflows/ @org/security-team
Egyszerű, hatékony, és elkapta volna a Trivy commitot, ha PR-on ment volna keresztül (nem ment, direct push volt, ami egy külön probléma).
A kényelmetlen igazság
Az igazi tanulság itt nem technikai. Hanem az, hogy a CI/CD pipeline-ok tetszőleges kódot futtatnak az internetről, és ezt teljesen normálisnak tartjuk. Productiönben nem csinálnánk curl | bash-t, de a GitHub Actions workflowjainkban pontosan ennek az ekvivalensét csináljuk.
Éveket töltöttem Kubernetes clusterek keményítésével, network policy-k beállításával, admission controllerek futtatásával, container image-ek aláírásával. Közben a build pipeline-om ellenőrizetlen kódot húzott fork hálózatokból és futtatta írási joggal a repóimhoz.
A Trivy incidenst viszonylag gyorsan elkapták. A következőt lehet, hogy nem fogják. Ha mostanában nem auditáltad a workflowjaidat, foglalj le egy szombatot. Valószínűleg fogsz találni dolgokat, amik nem fognak tetszeni.
Gyors ellenőrzőlista
- Org szintű action allowlist bekapcsolva
- Minden action SHA-val pinelve, kommentben a verzióval
- Workflow jogosultságok explicitek és minimálisak
- CODEOWNERS védi a
.github/workflows/könyvtárat - Self-hosted runnereken hálózati kimenő forgalom kontroll
- Script vagy tool ellenőrzi a pinelt SHA-kat tages release-ek ellen
-
--skip=validateés hasonló flagek tiltva a CI konfigokban
Semmi sem tökéletes ebből. A supply chain biztonság egy spektrum, nem egy checkbox. De a múlt hét után kicsit nyugodtabban alszom, tudva, hogy a pipeline-jaim nem csak abban bíznak, hogy az internet kedves lesz.