Secret Management — Secrets, Env Vars, Vault Entegrasyonu
Bu bölümün son dersine geldik. Şu ana kadar image güvenliğini, runtime güvenliğini öğrendik. Şimdi belki de en çok ihmal edilen ama en kritik konuya dalıyoruz: secret management — yani şifreler, API anahtarları, token'lar gibi hassas bilgilerin güvenli yönetimi.
Anahtarı Paspasın Altına Koymak
Evin anahtarını düşün. Anahtarı kapının altındaki paspasın altına koyarsan, "sakladım" diyebilirsin — ama güvende değil. Herkes bilir oraya bakacağını. Anahtarı cüzdanında taşırsan daha güvenli, ama cüzdanını kaybedebilirsin. Bir akıllı kilit kullanırsan — parmak izi veya PIN ile — fiziksel anahtar bile gerekmez ve güvenlik seviyesi çok daha yüksek.
Docker'da secret yönetimi de aynı mantıkta çalışıyor. Environment variable'a şifre koymak "paspasın altına anahtar" gibi — docker inspect ile herkes görebilir. Docker secrets ile dosya bazlı çalışmak cüzdanda taşımak gibi — daha güvenli ama hâlâ sınırlı. HashiCorp Vault gibi bir secret manager kullanmak ise akıllı kilit — en güvenli ve en profesyonel yöntem.
Secret Nedir?
Önce tanımlayalım. Secret, uygulamanın çalışması için gereken ama hiç kimsenin görmemesi gereken bilgilerdir:
Veritabanı şifreleri (
POSTGRES_PASSWORD,MYSQL_ROOT_PASSWORD)API anahtarları (
STRIPE_SECRET_KEY,AWS_ACCESS_KEY)JWT secret'ları (
JWT_SECRET,SESSION_SECRET)SSH private key'leri
TLS sertifikaları ve private key'leri
OAuth client secret'ları
Encryption key'leri
Bu bilgilerin herhangi birinin sızması, ciddi sonuçlara yol açabilir. 2019'da Capital One'da bir AWS credential sızıntısı 100 milyon kullanıcının verilerinin ele geçirilmesine neden oldu. GitHub'da her gün binlerce secret yanlışlıkla commit'leniyor — ve bot'lar bunları saniyeler içinde tespit edip kötüye kullanıyor. Bir AWS secret key'i GitHub'a push'ladığında, dakikalar içinde hesabında kripto miner'lar çalışmaya başlayabilir. Faturalar 10.000$'dan 100.000$'a kadar çıkabiliyor.
Yanlış Yöntemler: Bunları YAPMA!
Önce yanlış yöntemleri görelim ki neden yanlış olduklarını anlayalım.
Yanlış 1: Dockerfile'a Secret Gömmek
# ❌ EN KÖTÜ YOL: Secret image katmanlarına yazılır
ENV DATABASE_PASSWORD=supersecret123
ENV API_KEY=sk-1234567890abcdefBu neden tehlikeli? Çünkü docker history komutuyla herkes bu secret'ları görebilir:
docker history myapp:latest
# ... ENV DATABASE_PASSWORD=supersecret123Image'ı registry'ye push ettiğinde, registry'ye erişimi olan herkes bu secret'ları okuyabilir. Image'ı birine gönderdiğinde, alan kişi docker history yapar ve tüm secret'lar orada.
Yanlış 2: Build Argument Kullanmak
# ❌ ARG da image history'de görünebilir
ARG DB_PASSWORD
ENV DATABASE_URL=postgres://user:${DB_PASSWORD}@db:5432/myapp"ARG build sırasında kullanılır, image'da kalmaz" diye düşünebilirsin. Ama ENV ile kullandığın anda veya RUN komutunda kullandığın anda, o bilgi image katmanlarına yazılır.
Yanlış 3: docker run -e ile Açık Metin
# ❌ docker inspect ile görünür!
docker run -e DB_PASSWORD=supersecret myapp
# Herkes görebilir:
docker inspect myapp --format '{{.Config.Env}}'
# [DB_PASSWORD=supersecret NODE_ENV=production ...]Bu yöntem en yaygın kullanılanı ve en riskli olanı. Sunucuya SSH erişimi olan herkes docker inspect yapabilir. Hatta process listesinde bile görünebilir:
ps aux | grep docker
# ... -e DB_PASSWORD=supersecret ...Yanlış 4: .env Dosyasını Image'a COPY Etmek
# ❌ .env dosyası image'a gömülür
COPY .env /app/.envImage katmanlarında .env dosyası kalır — docker cp ile çıkarılabilir.
Doğru Yöntem 1: .env + Docker Compose (Basit Projeler)
En basit güvenli yöntem. Secret'ları .env dosyasında tut ve .gitignore ile koru:
# .env dosyası (Git'e COMMIT'LENMEZ!)
DB_USER=appuser
DB_PASS=Kj8$mN2pQr5vX9
JWT_SECRET=a1b2c3d4e5f6g7h8i9j0# .env.example (Git'e commit'lenir — template)
DB_USER=appuser
DB_PASS=CHANGE_ME
JWT_SECRET=CHANGE_ME# .gitignore — çok önemli!
.env
.env.production
.env.staging
!.env.exampleDocker Compose'da kullanımı:
services:
api:
build: ./api
environment:
DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@db:5432/mydb
JWT_SECRET: ${JWT_SECRET}
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}Docker Compose otomatik olarak aynı dizindeki .env dosyasını okur ve variable'ları yerleştirir.
Bu yöntemin güvenlik seviyesi: ⭐⭐ (temel). Git'e commit'lenmez ama docker inspect ile hâlâ görünür. Kişisel projeler ve geliştirme ortamı için uygun.
Doğru Yöntem 2: Docker Secrets (Dosya Bazlı)
Docker secrets, hassas bilgileri dosya olarak container'a mount eder. Environment variable'dan daha güvenli çünkü docker inspect'te görünmez.
Önce secret dosyalarını oluşturalım:
mkdir -p secrets
echo -n "Kj8\$mN2pQr5vX9" > secrets/db_password.txt
echo -n "a1b2c3d4e5f6g7h8" > secrets/jwt_secret.txt
# Dosya izinlerini kısıtla — sadece sahip okuyabilsin
chmod 600 secrets/*
# .gitignore'a ekle
echo "secrets/" >> .gitignoreDocker Compose'da kullanımı:
services:
api:
build: ./api
environment:
DB_HOST: db
DB_PORT: "5432"
DB_NAME: myapp
secrets:
- db_password
- jwt_secret
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: appuser
POSTGRES_DB: myapp
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
jwt_secret:
file: ./secrets/jwt_secret.txtDikkat ettin mi? PostgreSQL image'ında POSTGRES_PASSWORD yerine POSTGRES_PASSWORD_FILE kullanıyoruz. Bu, resmi PostgreSQL image'ının desteklediği bir özellik — secret'ı dosyadan okuyor. MySQL, Redis gibi birçok resmi image de bu _FILE suffix'ini destekliyor.
Peki uygulama tarafında secret'ı nasıl okuyacaksın? Dosyadan:
// Node.js — secret'ı dosyadan oku
const fs = require('fs');
function readSecret(name) {
const secretPath = `/run/secrets/${name}`;
try {
return fs.readFileSync(secretPath, 'utf8').trim();
} catch (err) {
// Fallback: Swarm dışında çalışıyorsa ENV'den oku
const envName = name.toUpperCase().replace(/-/g, '_');
return process.env[envName];
}
}
const dbPassword = readSecret('db_password');
const jwtSecret = readSecret('jwt_secret');# Python — secret'ı dosyadan oku
import os
def read_secret(name: str) -> str:
secret_path = f"/run/secrets/{name}"
try:
with open(secret_path, 'r') as f:
return f.read().strip()
except FileNotFoundError:
env_name = name.upper().replace('-', '_')
return os.environ.get(env_name, '')
db_password = read_secret('db_password')Bu fonksiyonların güzel tarafı: önce /run/secrets/ altına bakar (production). Dosya yoksa environment variable'dan okur (development). Böylece aynı kod her iki ortamda da çalışır.
Güvenlik seviyesi: ⭐⭐⭐ (iyi). docker inspect'te görünmez, dosya olarak mount edilir. Ama secret dosyaları hâlâ host'ta açık metin.
Doğru Yöntem 3: BuildKit Secrets (Build Zamanı)
Build sırasında private registry'ye erişim, private package çekme gibi durumlarda secret gerekebilir. BuildKit secrets ile bunları güvenle kullanırsın:
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
# Private npm registry'den paket çekme
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci
# .npmrc BUILD SIRASINDA kullanılır ama image'a GÖMÜLMEZ!
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]Build komutunda secret'ı geçiriyorsun:
docker build \
--secret id=npmrc,src=$HOME/.npmrc \
-t myapp:latest .docker history ile baktığında hiçbir iz yok — secret image katmanlarında kalmaz.
Doğru Yöntem 4: HashiCorp Vault (Enterprise Grade)
Büyük projeler ve production ortamları için en iyi çözüm, merkezi bir secret manager kullanmak. HashiCorp Vault en popüler seçenek:
services:
vault:
image: hashicorp/vault:1.15
ports:
- "8200:8200"
environment:
VAULT_DEV_ROOT_TOKEN_ID: dev-token
cap_add:
- IPC_LOCK
volumes:
- vault-data:/vault/data
api:
build: ./api
environment:
VAULT_ADDR: http://vault:8200
VAULT_TOKEN: ${VAULT_TOKEN}Vault'a secret yazmak:
vault kv put secret/myapp \
db_password="Kj8\$mN2pQr5vX9" \
jwt_secret="a1b2c3d4e5f6g7h8"Uygulamadan okumak:
const vault = require('node-vault')({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN,
});
async function getSecrets() {
const result = await vault.read('secret/data/myapp');
return result.data.data;
}Vault'un süper güçleri:
Merkezi yönetim: Tüm secret'lar tek yerde, şifreli
Erişim kontrolü: Kim hangi secret'a erişebilir, policy ile belirlenir
Audit log: Kim, ne zaman, hangi secret'a erişti — tam iz kaydı
Secret rotation: Şifreler otomatik olarak periyodik değiştirilebilir
Dynamic secrets: Geçici veritabanı credential'ları oluşturulabilir — 1 saat sonra otomatik expire olur
Güvenlik seviyesi: ⭐⭐⭐⭐⭐ (en iyi). Ama kurulum ve yönetimi karmaşık.
Secret Sızıntısını Önlemek: Pre-commit Hook'ları
En iyi güvenlik önlemi, secret'ın hiç sızmamasını sağlamak. Pre-commit hook'ları ile secret commit'lenmeden önce yakala:
# git-secrets kurulumu
git secrets --install
git secrets --register-aws
# Commit'te secret varsa ENGELLE
git commit -m "update config"
# [ERROR] secrets/db.env:3: DB_PASSWORD=supersecret123
# Commit BLOCKED ✗TruffleHog ile tüm Git geçmişini tarayabilirsin:
docker run --rm -v $(pwd):/repo trufflesecurity/trufflehog:latest \
git file:///repo --since-commit HEAD~50Bu araç, geçmişte commit'lenmiş tüm secret'ları bulur — sonradan silinmiş olsalar bile.
Secret Git Geçmişine Sızmışsa Ne Yaparsın?
Diyelim ki bir secret yanlışlıkla commit'lendi. Sonra sildin ve yeni commit attın. Ama o secret hâlâ Git geçmişinde duruyor! Birisi git log ile geçmişe bakabilir ve secret'ı görebilir.
Çözüm iki adımlı:
Adım 1: Git geçmişinden temizle
# BFG Repo-Cleaner ile (hızlı ve güvenli)
bfg --delete-files .env
bfg --replace-text passwords.txt
git push --forceAdım 2: Secret'ı ROTATE et
Bu çok önemli — geçmişten silmek yetmez! O secret zaten sızmış olabilir. Eski şifreyi değiştir, yeni şifre belirle. Tüm servisleri yeni şifreyle güncelle.
Hangi Yöntemi Ne Zaman Kullanmalısın?
Kafan karışmış olabilir — bu kadar yöntem var, hangisini seçeceğim? İşte basit bir karar ağacı:
Kişisel proje / geliştirme ortamı?
→ .env + Docker Compose (Yöntem 1)
Küçük takım / staging?
→ Docker Secrets - dosya bazlı (Yöntem 2)
Production / enterprise?
→ HashiCorp Vault veya Cloud secret manager (Yöntem 4)
Build sırasında private erişim?
→ BuildKit secrets (Yöntem 3)CI/CD'de Secret Yönetimi
Production'a deploy ederken CI/CD pipeline'ın da secret'lara erişmesi gerekiyor — registry şifresi, deploy server SSH key'i, veritabanı şifreleri... Bunları nasıl güvenle yöneteceksin?
GitHub Actions
GitHub Actions'ta secret'lar Settings → Secrets and variables → Actions altında saklanır. Pipeline içinden ${{ secrets.XXX }} ile erişilir:
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Build and Push
env:
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
echo "$REGISTRY_PASSWORD" | docker login -u user --password-stdin registry.example.com
docker build -t registry.example.com/myapp:${{ github.sha }} .
docker push registry.example.com/myapp:${{ github.sha }}
- name: Deploy to Server
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
run: |
# Secret'ları geçici dosyalara yaz
echo "$DB_PASSWORD" > /tmp/db_password
echo "$JWT_SECRET" > /tmp/jwt_secret
# Sunucuya gönder
scp /tmp/db_password /tmp/jwt_secret deploy@server:/app/secrets/
ssh deploy@server 'cd /app && docker compose up -d'
# Geçici dosyaları temizle
rm -f /tmp/db_password /tmp/jwt_secretGitHub Actions loglarında secret'lar otomatik olarak *** ile maskelenir — yani echo $DB_PASSWORD yapsan bile logda görünmez. Bu güzel bir güvenlik önlemi.
GitLab CI
GitLab'da secret'lar Settings → CI/CD → Variables altında saklanır. "Masked" ve "Protected" seçenekleriyle ekstra güvenlik sağlanır:
deploy:
stage: deploy
script:
- echo "$DB_PASSWORD" > secrets/db_password.txt
- docker compose up -d
after_script:
- rm -rf secrets/ # Temizlik — pipeline bitince sil
variables:
DB_PASSWORD: $DB_PASSWORDCloud Provider Secret Manager'ları
Büyük ölçekli projelerde Vault yerine cloud provider'ın kendi secret manager'ını da kullanabilirsin:
AWS Secrets Manager:
# Secret oluştur
aws secretsmanager create-secret \
--name myapp/production \
--secret-string '{"db_password":"Kj8$mN2p","jwt_secret":"a1b2c3d4"}'Uygulamanın başlangıcında AWS SDK ile secret'ı çekersin. Container'a sadece AWS credential'ları verirsin (veya daha iyisi, IAM role kullanırsın) — secret'ların kendisi environment'a gömülmez, runtime'da çekilir.
AWS ECS kullanıyorsan, secret injection native olarak desteklenir:
{
"containerDefinitions": [{
"secrets": [{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:eu-west-1:123456:secret:myapp/production:db_password::"
}]
}]
}ECS otomatik olarak Secrets Manager'dan çeker ve environment variable olarak inject eder. Secret hiçbir zaman açık metin olarak bir yerde durmaz.
Azure Key Vault ve Google Cloud Secret Manager da benzer şekilde çalışır. Hangi cloud provider'daysan, onun secret manager'ını tercih et — integration'lar daha doğal ve kolay olur.
Secret Rotation: Şifreleri Düzenli Değiştirmek
Bir secret'ın ne kadar süre geçerli olması gerektiğini hiç düşündün mü? Çoğu projede veritabanı şifresi bir kere belirlenir ve yıllarca aynı kalır. Bu büyük bir risk — çünkü o şifre zamanla daha fazla kişinin eline geçer, daha fazla yerde kullanılır ve sızma ihtimali artar.
Secret rotation, şifreleri periyodik olarak otomatik değiştirmek demek. Vault ile bunu yapmak mümkün:
# Vault, PostgreSQL'e bağlanır ve geçici kullanıcı oluşturur
vault write database/roles/app-role \
db_name=mydb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';" \
default_ttl="1h" \
max_ttl="24h"
# Uygulama her seferinde geçici credential alır
vault read database/creds/app-role
# username: v-app-role-abc123
# password: A1b2C3d4E5f6G7h8
# lease_duration: 1h
# → 1 saat sonra otomatik expire olur!Bu yaklaşımda kalıcı şifre yok — her seferinde geçici bir credential oluşturuluyor ve belirli süre sonra otomatik olarak geçersiz hale geliyor. Sızma olsa bile, sızan credential kısa sürede işe yaramaz hale gelir.
Yöntemlerin Karşılaştırması
Hangi yöntemin hangi durumda uygun olduğunu toparlayalım:
┌──────────────────┬─────────┬──────────────┬───────────┐
│ Yöntem │ Güvenlik│ docker inspect│ Rotation │
├──────────────────┼─────────┼──────────────┼───────────┤
│ Dockerfile ENV │ ⭐ │ GÖRÜNÜR │ Yok │
│ docker run -e │ ⭐⭐ │ GÖRÜNÜR │ Manuel │
│ .env + compose │ ⭐⭐ │ GÖRÜNÜR │ Manuel │
│ Docker secrets │ ⭐⭐⭐ │ Görünmez │ Manuel │
│ BuildKit secret │ ⭐⭐⭐ │ N/A (build) │ N/A │
│ Vault/Cloud SM │ ⭐⭐⭐⭐⭐│ Görünmez │ Otomatik │
└──────────────────┴─────────┴──────────────┴───────────┘Her ortam için farklı secret kullanmayı da unutma. Development, staging ve production ortamlarında aynı şifreyi kullanmak, tek bir kırılma noktası yaratır. Dev ortamındaki bir sızıntı, production'ı da tehlikeye atar.
Secret'ları Asla Loglama!
Son bir uyarı — çok sık yapılan bir hata:
// ❌ ASLA yapma!
console.log(`Connecting with password: ${dbPassword}`);
console.log('Config:', JSON.stringify(config));
// config içinde secret olabilir!
// ✅ Doğru yaklaşım
console.log(`Connecting to ${dbHost}:${dbPort}/${dbName}`);
console.log('Config:', JSON.stringify(config, (key, value) => {
if (['password', 'secret', 'key', 'token'].some(s =>
key.toLowerCase().includes(s))) {
return '***REDACTED***';
}
return value;
}));Log'lar genellikle merkezi bir sisteme gönderilir, dosyalara yazılır, bazen üçüncü parti servislere iletilir. Secret'ı logladığın anda, kontrolün dışına çıkar.
Bu Derste Ne Öğrendik?
Environment variable ile secret geçmek en yaygın ama en az güvenli yöntem —
docker inspectile görünürDocker secrets (dosya bazlı) daha güvenli —
/run/secrets/altında, inspect'te görünmezBuildKit secrets ile build-time secret'lar güvenle kullanılır — image katmanlarına yazılmaz
HashiCorp Vault enterprise-grade çözüm — merkezi, şifreli, audit'li, rotation destekli
Pre-commit hook ile secret sızıntılarını commit öncesi yakala
Secret Git geçmişine sızmışsa, sadece silmek yetmez — mutlaka rotate et
Secret'ları asla loglama — bu en sık yapılan hatalardan biri
Her ortam (dev/staging/prod) farklı secret'lar kullanmalı
Bu dersle birlikte Docker güvenliği bölümünü tamamladık! Image güvenliği, runtime güvenliği ve secret management — bu üç katman Docker'ını production'da güvenle çalıştırmanın temelini oluşturuyor. Sonraki bölümde container orchestration dünyasına gireceğiz.
AI Asistan
Sorularını yanıtlamaya hazır