GitHub Actions’da Secrets Yönetimi: Güvenli Değişkenler Nasıl Kullanılır?

Production ortamında bir pipeline’ın çökmesi ve ardından logları incelerken API anahtarının düz metin olarak commit geçmişinde durduğunu fark etmek… Bu senaryoyu yaşayan herkes biliyor, o soğuk ter anını. GitHub Actions’ta secrets yönetimi, sadece “değişkenleri nereye koyacağım” sorusunun çok ötesinde bir konu. Yanlış yapılandırılmış bir pipeline, production veritabanı şifrenizi, cloud provider credential’larınızı veya third-party API anahtarlarınızı açık havaya bırakabilir. Bu yazıda, GitHub Actions’ta güvenli değişken yönetimini baştan sona ele alacağız; hem teorik hem de sahadan örneklerle.

Secrets ve Variables Arasındaki Fark

GitHub Actions, iki farklı tip gizli bilgi yönetimi sunar ve bu ikisini karıştırmak ciddi güvenlik açıklarına yol açar.

Secrets (Gizli Bilgiler): Şifreler, API anahtarları, private key’ler gibi hassas veriler. Bir kez kaydedildikten sonra UI üzerinden görüntülenemez, sadece üzerine yazılabilir. Workflow loglarında otomatik olarak maskelenir.

Variables (Değişkenler): Ortam adları, konfigürasyon değerleri gibi hassas olmayan veriler. Değeri okunabilir, UI’da görüntülenebilir. Maskeleme yoktur.

Pratikte şunu yapın: Production URL’inizi variable olarak saklayın, ama o URL’e bağlanmak için kullanacağınız şifreyi kesinlikle secret olarak saklayın.

Secret Seviyeleri ve Kapsamları

GitHub Actions’ta üç farklı seviyede secret tanımlayabilirsiniz:

Repository Secrets: Sadece ilgili repository’nin workflow’larında kullanılabilir. En kısıtlı ve güvenli seçenek.

Environment Secrets: Belirli bir deployment environment’ına (staging, production gibi) özgü secretlar. Environment protection rules ile birleşince çok güçlü bir yapı oluşturur.

Organization Secrets: Tüm organizasyon repository’lerinde kullanılabilir. Paylaşılan credential’lar için ideal, ama kapsam yönetimine dikkat etmek gerekir.

Repository secrets’a ulaşmak için: Settings > Secrets and variables > Actions > New repository secret

Temel Secret Kullanımı

En basit haliyle bir secret’ı workflow’da nasıl kullanırsınız:

name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy application
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.THIRD_PARTY_API_KEY }}
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deployment basliyor..."
          ./scripts/deploy.sh

Burada dikkat edilmesi gereken birkaç nokta var. ${{ secrets.SECRET_NAME }} sözdizimini kullanıyorsunuz ve bu değeri doğrudan run bloğuna değil, env bloğuna geçiriyorsunuz. Neden? Çünkü doğrudan run içinde echo ${{ secrets.MY_SECRET }} yaparsanız, GitHub maskeleme işlemi yapsa da bu pratik bir güvenlik açığı yaratır.

Environment’a Özgü Secret Yönetimi

Gerçek dünya senaryolarında staging ve production için farklı credential’larınız olur. Bunu environment secrets ile yönetelim:

name: Multi-Environment Deployment

on:
  push:
    branches:
      - main
      - staging

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    if: github.ref == 'refs/heads/staging'
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Staging
        env:
          DB_HOST: ${{ vars.DB_HOST }}
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          ./deploy.sh --env staging

  deploy-production:
    runs-on: ubuntu-latest
    environment: production
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Production
        env:
          DB_HOST: ${{ vars.DB_HOST }}
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        run: |
          ./deploy.sh --env production

Burada aynı secret adlarını (DB_PASSWORD, AWS_ACCESS_KEY_ID) kullandığınıza dikkat edin, ama her environment altında farklı değerler tanımlıyorsunuz. staging environment’ında staging DB şifresi, production environment’ında production DB şifresi bulunuyor. Workflow kodu aynı kalıyor, sadece environment bağlamı değişiyor.

Environment Protection Rules ile Güvenliği Artırma

Environment secrets’ın asıl gücü, protection rules ile ortaya çıkar. Settings > Environments > production yolundan şunları yapılandırabilirsiniz:

  • Required reviewers: Production deploy’u tetiklemeden önce belirli kişilerin onayı gereksin.
  • Wait timer: Deploy’dan önce X dakika beklensin.
  • Deployment branches: Sadece main branch’ten deploy mümkün olsun.

Bu sayede birisi yanlışlıkla production secret’larını kullanmaya çalışırsa, workflow otomatik olarak onay bekleme durumuna geçer.

AWS Credential Yönetimi: OIDC ile Anahtarsız Yaklaşım

Bu kısım, çoğu sysadmin’in gözden kaçırdığı ve çok kritik olan bir konudur. AWS erişimi için statik access key/secret key çifti saklamak yerine, OIDC (OpenID Connect) kullanarak anahtarsız kimlik doğrulaması yapabilirsiniz.

Neden OIDC? Statik anahtarlar döner, sızabilir, expire olabilir. OIDC ile GitHub Actions, her workflow çalıştığında geçici token alır. Bu token workflow bittikten sonra geçersizdir.

Önce AWS tarafında IAM Identity Provider oluşturun:

# AWS CLI ile OIDC Provider olusturma
aws iam create-open-id-connect-provider 
  --url https://token.actions.githubusercontent.com 
  --client-id-list sts.amazonaws.com 
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

# IAM Role olusturma (trust policy)
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:GITHUB_ORG/REPO_NAME:*"
        }
      }
    }
  ]
}
EOF

aws iam create-role 
  --role-name GitHubActionsRole 
  --assume-role-policy-document file://trust-policy.json

Sonra workflow’da bu role’ü kullanın:

name: Deploy with OIDC

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
          aws-region: eu-west-1
          
      - name: Deploy to S3
        run: |
          aws s3 sync ./dist s3://my-production-bucket
          aws cloudfront create-invalidation 
            --distribution-id ${{ vars.CF_DISTRIBUTION_ID }} 
            --paths "/*"

Dikkat: permissions bloğu kritik. id-token: write olmadan OIDC token alınamaz.

Docker Registry ile Güvenli Image Push

Birçok pipeline, Docker image build edip registry’ye push eder. Bu süreçte credential yönetimi:

name: Build and Push Docker Image

on:
  push:
    branches: [main]
    tags: ['v*.*.*']

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to DockerHub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            myorg/myapp:latest
            myorg/myapp:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_DATE=${{ github.run_id }}
            GIT_COMMIT=${{ github.sha }}

Burada DOCKERHUB_TOKEN olarak şifrenizi değil, DockerHub’dan oluşturduğunuz access token’ı kullanın. Şifre değiştiğinde tüm token’ları sıfırlamak zorunda kalmazsınız, sadece ilgili token’ı iptal edersiniz.

Secret Maskeleme ve Güvenli Loglama

GitHub Actions, bilinen secret değerlerini otomatik olarak maskeler. Ama bu maskelemenin sınırları var:

name: Debug-Safe Logging

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Guvenli degisken kullanimi
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DB_CONN: ${{ secrets.DATABASE_CONNECTION_STRING }}
        run: |
          # YANLIS: Secret degerini echo etmek
          # echo "API Key: $API_KEY"  -- BUNU YAPMAYIN
          
          # DOGRU: Sadece var mi yok mu kontrol et
          if [ -z "$API_KEY" ]; then
            echo "HATA: API_KEY secret tanimlanmamis!"
            exit 1
          else
            echo "API_KEY mevcut ve dolu."
          fi
          
          # Connection string'den hassas kisimlari cikarmadan loglama
          # YANLIS: echo $DB_CONN
          # DOGRU:
          DB_HOST=$(echo $DB_CONN | cut -d'@' -f2 | cut -d'/' -f1)
          echo "Veritabanina baglaniliyor: $DB_HOST"

Base64 encoded secret’lar maskelenmez. Eğer bir secret’ı base64 ile encode edip log’a basarsanız, GitHub bunu maskeleyemez. Bu yüzden decode edilmiş hallerde dikkatli olun.

Dinamik Secret Yönetimi: HashiCorp Vault Entegrasyonu

Büyük ölçekli yapılarda, GitHub’ın kendi secret yönetiminin yetmediği durumlar olur. HashiCorp Vault gibi external secret manager’larla entegrasyon kurmak mümkün:

name: Vault Integration

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Import Secrets from Vault
        uses: hashicorp/vault-action@v3
        with:
          url: ${{ secrets.VAULT_ADDR }}
          token: ${{ secrets.VAULT_TOKEN }}
          secrets: |
            secret/data/myapp/production db_password | DB_PASSWORD ;
            secret/data/myapp/production api_key | EXTERNAL_API_KEY ;
            secret/data/aws/creds/deploy access_key | AWS_ACCESS_KEY_ID ;
            secret/data/aws/creds/deploy secret_key | AWS_SECRET_ACCESS_KEY

      - name: Use imported secrets
        run: |
          # Bu noktada DB_PASSWORD, EXTERNAL_API_KEY vb.
          # environment variable olarak kullanilabilir
          ./deploy.sh

Bu yaklaşımda GitHub sadece Vault’a erişim için kullanılan VAULT_TOKEN‘ı tutar. Uygulama secret’larının tamamı Vault’ta merkezi olarak yönetilir. Secret rotation yaptığınızda GitHub tarafında hiçbir şeyi güncellemeniz gerekmez.

Ortak Hatalar ve Çözümleri

Sahada en çok rastladığım hataları ve çözümlerini derledim:

Hata 1: Secret’ı doğrudan script içine gömmek

# YANLIS - Bunu workflow'unuzda gorursek ciddi konusmaliyiz
- name: Bad practice
  run: |
    curl -H "Authorization: Bearer ${{ secrets.API_KEY }}" 
         https://api.example.com/deploy
    # Bu yaklaşim workflow YAML'ini loglar ve guvensizdir
# DOGRU
- name: Good practice
  env:
    API_KEY: ${{ secrets.API_KEY }}
  run: |
    curl -H "Authorization: Bearer $API_KEY" 
         https://api.example.com/deploy

Hata 2: Fork’lardan gelen Pull Request’lerde secret erişimi

Fork’lardan açılan PR’larda, güvenlik nedeniyle secret’lar workflow’a verilmez. Bu genellikle CI testlerini kırar. Çözüm:

name: PR Tests

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run tests
        env:
          # Fork PR'larinda bu bos gelir, test bunu handle etmeli
          TEST_API_KEY: ${{ secrets.TEST_API_KEY }}
        run: |
          if [ -z "$TEST_API_KEY" ]; then
            echo "Fork PR: External API testleri atlanıyor"
            npm test -- --skip-integration
          else
            npm test
          fi

Hata 3: Secret adlandırma tutarsızlığı

Organizasyonlarda yaygın bir sorun: biri AWS_KEY, diğeri AWS_ACCESS_KEY, başkası AWS_ACCESS_KEY_ID kullanıyor. Standart bir isimlendirme politikası belirleyin ve bunu dokümante edin.

Secret Rotation Stratejisi

Secret’ları düzenli döndürmek (rotate) iyi bir güvenlik pratiğidir. Bunu otomatize edebilirsiniz:

name: Rotate API Secrets

on:
  schedule:
    # Her ay 1'inde calis
    - cron: '0 0 1 * *'
  workflow_dispatch:

jobs:
  rotate:
    runs-on: ubuntu-latest
    steps:
      - name: Generate new API key
        env:
          CURRENT_KEY: ${{ secrets.THIRD_PARTY_API_KEY }}
          SERVICE_ADMIN_TOKEN: ${{ secrets.SERVICE_ADMIN_TOKEN }}
        run: |
          # Yeni key olustur
          NEW_KEY=$(curl -s -X POST 
            -H "Authorization: Bearer $SERVICE_ADMIN_TOKEN" 
            https://api.thirdparty.com/v1/keys 
            | jq -r '.key')
          
          # Yeni key'i GitHub'a yaz
          gh secret set THIRD_PARTY_API_KEY --body "$NEW_KEY"
          
          # Eski key'i iptal et
          curl -s -X DELETE 
            -H "Authorization: Bearer $SERVICE_ADMIN_TOKEN" 
            -H "X-Old-Key: $CURRENT_KEY" 
            https://api.thirdparty.com/v1/keys/current
            
          echo "Secret rotation tamamlandi"
        env:
          GH_TOKEN: ${{ secrets.PAT_FOR_SECRET_MANAGEMENT }}

Audit ve Monitoring

Secret kullanımını izlemek de en az secret tanımlamak kadar önemli:

  • GitHub Audit Log: Organization ayarlarından secret erişimlerini izleyebilirsiniz. Settings > Audit log bölümünden secret keyword’ü ile filtreleyebilirsiniz.
  • Secret Scanning: GitHub’ın yerleşik secret scanning özelliği, repository’e yanlışlıkla commit edilen secret pattern’larını tespit eder. Settings > Security > Secret scanning kısmından aktif edin.
  • Workflow run logs: Başarısız deploymentlarda, log’larda secret değerlerini aramak yerine workflow’un hangi adımda hata aldığını takip edin.

Sonuç

GitHub Actions’ta secrets yönetimi, pipeline güvenliğinin temel taşıdır. Özet olarak uygulamanız gereken pratikler:

  • Repository, environment ve organization secret seviyelerini doğru kullanın, her şeyi aynı sepete koymayın.
  • AWS gibi cloud provider’lar için statik anahtarlar yerine OIDC ile geçici credential kullanın.
  • External vault entegrasyonu kurarak secret’ları merkezi yönetin ve rotation’ı otomatize edin.
  • Fork PR senaryolarını test akışlarınızda hesaba katın, secret olmadan da çalışan graceful fallback’ler yazın.
  • Secret scanning’i aktif tutun ve audit logları periyodik inceleyin.
  • env bloğu kullanımını alışkanlık haline getirin, secret değerlerini asla doğrudan run içine enjekte etmeyin.

Production’da bir secret sızdığında farkında olmak bazen günler sürebilir. Bu pratikleri baştan doğru uygulamak, sonradan olası bir breach’i handle etmekten kat kat kolaydır. Pipeline’larınız güvenli, deploymentlarınız sorunsuz olsun.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir