GitHub Actions'ta Secret'ı Log'a Düşürmek
echo ile debug, maskenin görmediği satır

Job kırmızıydı. Debug için echo "$CONNECTION_STRING" koydum. Actions, secret değerini log'da *** yapar — birebir eşleşirse. Connection string'i bir JSON'a sarıp jq ile yazdırınca eşleşme bozuldu. Maske bakmadı. Satır durdu.
İkinci kaçış set -x. Bash her komutu basar. curl -H "Authorization: $TOKEN" satırı, değişkeni açılmış haliyle gider. Maske bazen yakalar, bazen URL-encode edilmiş kopyayı yakalamaz. Debug ucuz durur. Log saklama süresi boyunca ucuz değildir.
Maske neyi görür
Kayıtlı secret'ın tam metni. Türevleri değil. Base64, substring, printf %s ile bölünmüş parça. ::add-mask:: ile ek maske koyabilirsin; unuttuğun türev yine düşer. Bu yüzden strateji 'daha çok maske' değil, 'değeri hiç yazdırma'.
name: migrate
on:
workflow_dispatch:
jobs:
apply:
runs-on: ubuntu-24.04
env:
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
steps:
- uses: actions/checkout@v4
- name: Run migrate
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
set +x
echo "::add-mask::${DATABASE_URL}"
./migrate --url "$DATABASE_URL"
- name: Never dump env
run: |
if printenv | grep -Ei 'SECRET|TOKEN|PASSWORD|DATABASE_URL'; then
echo "env dump yasak" >&2
exit 1
fi
printenv ve env bir PR'da 'neden inject olmadı' diye duruyordu. Inject olmuştu. Log'a da olmuştu. O adımı sildim. Yerine secret'ın varlığını uzunluk ile kontrol eden, değeri basmayan bir satır koydum. test -n yeter.
Build-arg ve docker
docker build --build-arg secret'ı katman geçmişine ve docker history çıktısına yazabilir. ARG + ENV ikilisi daha kötü: değer imajda kalır. Secret build'e girecekse BuildKit secret mount. Log'a --progress=plain açınca mount bile konuşkan olur; plain'i kırmızı işte geçici aç, kapat.
# syntax=docker/dockerfile:1.7
FROM alpine:3.19.1
RUN --mount=type=secret,id=dburl \
test -s /run/secrets/dburl
set-output ve eski yüzey
::set-output deprecated. Bir nedeni de çıktının log ve inter-step kanalında düz metin dolaşmasıydı. GITHUB_OUTPUT daha iyi, secret yazmak hâlâ yanlış. Job özeti, annotation, actions/upload-artifact — üçü de kalıcı. Token'ı artifact yapma.
Debug flag ve türetilmiş değer
ACTIONS_STEP_DEBUG ve ACTIONS_RUNNER_DEBUG runner'ı konuşkan yapar. Konuşkan runner, env ve komut satırını basar. Secret birebir duruyorsa maske yer. jq ile sarıp, base64'leyip, URL-encode edip basınca maske bakmaz. Debug'ı kırmızı işte aç, iş bitince kapat. Repo ayarında kalıcı debug, kalıcı yüzey.
docker login --password log'da durabilir. docker/login-action parolayı stdin'den verir; yine de set -x açıkken action öncesi kendi echo'nuz varsa iş bitmiştir. printf '%s' "$TOKEN" | docker login --password-stdin yazıp set -x bırakmak, stdin'i değil, komut satırındaki diğer değişkenleri basar. Yine set +x o blokta.
- name: Login
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set +x
echo "::add-mask::${REGISTRY_TOKEN}"
printf '%s' "$REGISTRY_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
unset REGISTRY_TOKEN
unset sonrası aynı shell'de printenv boş döner. Sonraki step yeni shell'dir; secret'ı env: ile tekrar basmadığın sürece orada yoktur. Step sınırını bu yüzden seviyorum. Tek kocaman run: bloğu, sızıntıyı tek yerde toplar.
Fork ve kendi branch
Fork PR'larında secret yok. Bunu 'güvenlik' sanmayın; asıl sızıntı kendi branch job'unuzda, debug açıkken. Public log, 90 gün. Bir echo 90 gün yaşar. Secret rotate etmeden log'u silmek, ekran görüntüsünü silmez. Rotate et, workflow'u düzelt, sonra log retention'ı kıs.
Build log'u ayrı kanal
docker build --progress=plain her RUN satırını basar. echo $SECRET Dockerfile'da durmasa bile ARG TOKEN plain log'da durur. BuildKit secret mount, /run/secrets altında dosyadır; cat edersen yine düşer. Mount, yazdırmama disiplinini kaldırmaz. RUN --mount=type=secret + set +x + cat yasağı.
# syntax=docker/dockerfile:1.7
FROM alpine:3.19.1
RUN --mount=type=secret,id=npm,target=/run/secrets/npmrc \
cp /run/secrets/npmrc /root/.npmrc \
&& npm ci \
&& rm -f /root/.npmrc
/root/.npmrc son katmanda kalırsa token imajdadır. rm aynı RUN içinde olmalı. Ayrı satır, ayrı katman, token altta durur. docker history onu göstermese de docker save | tar gösterir. Multi-stage ile npmrc'yi build odasında bırakmak daha dürüst: son imaja kopyalama.
::add-mask:: 'ı workflow'un başında, secret'ı ilk kullanmadan önce yaz. Ortada yazmak, önceki satırları geri maskelemez. Actions bazen eski satırı da tarar; güvenme. Sıra: maskele, kullan, unset et, kapat.
GITHUB_TOKEN varsayılanı log'da maskelenir. Kendi ürettiğin PAT, secrets. altında değilse maske tanımaz. PAT'ı repo secret'ı yap. echo $PAT 'ı 'kısa test' diye commit etmek, token'ı git tarihine gömer. Rotate, force-push yetmez; fork'lar kopyayı tutar.
name: secret-guard
on: [push]
jobs:
guard:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Ban noisy shells
run: |
if grep -R --include='*.yml' --include='*.yaml' -nE 'set -x|printenv|echo "\$\{?secrets' .github/workflows; then
echo "noisy debug in workflow" >&2
exit 1
fi
- name: Migrate
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: |
set +x
echo "::add-mask::${DATABASE_URL}"
test -n "$DATABASE_URL"
./migrate --url "$DATABASE_URL"
unset DATABASE_URL
Özetlediğim yasak listesi
echo,printenv,envveset -xsecret'ın durduğu step'te yok.::set-outputyok;GITHUB_OUTPUT'a secret yazılmaz.- Artifact, job summary, annotation secret taşımaz.
docker build --build-argile token yok; BuildKit secret mount var.ACTIONS_STEP_DEBUGkalıcı açık değil.- Fork dışındaki asıl sızıntı kendi branch log'unuz.
Listeyi workflow comment'i olarak bırakmıyorum. Yorum çürür. printenv | grep kapısını kırmızı yapan adım duruyor. Kapı, hatırlatmadan daha az kibar, daha az unutulur. Secret bir kez düşünce kibarlık pahalı.
Kaan'ın yorumundaki set-output notu tam buraya oturuyor. Deprecated olmasının bir nedeni sızıntı yüzeyiydi. Yeni sözdizimi sihir değil; değeri yazmazsan çalışır. Yazarsan 90 gün yaşar. 90 gün, rotate etmeyi unuttuğun PAT'ın ömründen uzundur. Log'u kıs, token'ı döndür, workflow'u kilitle — üçünü aynı günde bitir. Birini ertelemek, üçünü de bozar.
Maskeye güvenmek ikinci hataydı. Birinci hata, değeri görmeden teşhis edemeyeceğimi sanmaktı. Teşhis için uzunluk, exit code ve karşı tarafın 401'i yeter. Metin gerekmez. Metin gerekirse önce rotate et, sonra bak; bakmadan yazdırma. Yazdırdıysan log'u sır olarak kabul et.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap