GitLab CI/CD Variables ile Güvenli Yapılandırma Yönetimi
Production ortamında bir GitLab pipeline’ı çalıştırırken veritabanı şifresi, API anahtarı veya deployment token’ı gibi hassas bilgileri .gitlab-ci.yml dosyasına gömmek, ne yazık ki hala yaygın görülen bir hata. Bu dosyalar genellikle repository’de açık şekilde duruyor ve ekipteki herkes, hatta bazen dışarıdan biri tarafından okunabiliyor. GitLab’ın CI/CD Variables özelliği tam da bu sorunu çözmek için var. Bu yazıda, GitLab CI/CD Variables’ı doğru şekilde nasıl kullanacağını, masked ve protected variable’ların farkını, scope yönetimini ve gerçek dünya senaryolarında nasıl entegre edeceğini ele alacağız.
CI/CD Variables Nedir ve Neden Önemlidir
GitLab CI/CD Variables, pipeline çalışırken erişilebilen key-value çiftleridir. Bunları kabaca iki kategoride düşünebilirsin: GitLab’ın kendi tanımladığı predefined variables (CI_COMMIT_BRANCH, CI_JOB_TOKEN gibi) ve senin tanımladığın custom variables. Bizi asıl ilgilendiren kısım custom variables.
Şifreler, API anahtarları, SSL sertifikaları, ortam bazlı konfigürasyonlar gibi hassas verileri kod içinde taşımak birkaç kritik risk yaratıyor:
- Versiyon geçmişinde kalıcılık: Git history’den bir değeri silmek düşündüğünden çok daha zor. Birisi o commit’i checkout alırsa şifreye erişebilir.
- Ekip içi aşırı erişim: Her geliştiricinin production şifresine ihtiyacı yok, ama
.gitlab-ci.ymldosyasını herkes görebiliyor. - Rotation zorluğu: Şifreyi değiştirmen gerektiğinde tek bir yerden yapmak yerine kod içinde aramak zorunda kalırsın.
- Audit trail eksikliği: Kim ne zaman hangi şifreye erişti? Variables kullanırsanız GitLab bunu logluyor.
Variable Türleri: Masked, Protected ve File
GitLab’da bir variable oluştururken karşına üç önemli seçenek çıkıyor.
Masked Variables, değerin job loglarında görünmesini engeller. Birisi echo $DB_PASSWORD yazsa bile log çıktısında [MASKED] görünür. Bu özelliği aktive etmek için variable değerinin en az 8 karakter olması ve Base64 veya alphanumeric karakterlerden oluşması gerekiyor. Bazı özel karakterler maskelemeyi bozuyor, buna dikkat et.
Protected Variables, sadece protected branch veya tag üzerinde çalışan job’lara görünür. Yani bir geliştirici feature branch’inde pipeline çalıştırıyorsa production credentials’larına erişemez. Bu, en temel güvenlik katmanlarından biri.
File Variables, değeri bir dosyaya yazar ve environment variable olarak o dosyanın path’ini verir. SSL sertifikası, kubeconfig veya benzeri dosya tabanlı konfigürasyonlar için idealdir.
Pratikte çoğu zaman hem Masked hem Protected seçeneklerini birlikte aktif etmek en mantıklı yaklaşım.
Variable Scope: Project, Group ve Instance Düzeyinde Yönetim
GitLab’da variable’ları üç farklı düzeyde tanımlayabilirsin.
Project-level variables, sadece o projeye ait pipeline’larda kullanılır. Settings > CI/CD > Variables menüsünden eklenir.
Group-level variables, gruba bağlı tüm projelere otomatik olarak aktarılır. Ortak kullanılan API anahtarları veya environment URL’leri için mükemmel. Group admin’inin yönettiği bu variables, her proje için ayrı ayrı tanımlama zahmetini ortadan kaldırır.
Instance-level variables, GitLab admin’i tarafından tanımlanır ve tüm instance’daki projelere uygulanır. Self-hosted GitLab kullanıyorsan ve şirket genelinde ortak bir konfigürasyon varsa bu seviyeyi kullanabilirsin.
Scope önceliği şu şekilde işler: Aynı isimde bir variable birden fazla düzeyde tanımlanmışsa, project-level olan group-level olanı ezer. Instance-level ise en düşük önceliğe sahip.
Ortam Bazlı Variable Yönetimi
GitLab, environment scope özelliğiyle aynı variable’ı farklı ortamlar için farklı değerlerle tanımlamana olanak tanıyor. Örneğin DATABASE_URL variable’ını hem staging hem production için ayrı ayrı tanımlayabilirsin.
Settings > CI/CD > Variables bölümünde bir variable eklerken “Environment scope” alanına staging veya production yazabilirsin. Wildcard olarak * kullanmak, o variable’ı tüm ortamlara uygulanır.
# .gitlab-ci.yml
deploy_staging:
stage: deploy
environment:
name: staging
script:
- echo "Staging DB URL: $DATABASE_URL"
- ./deploy.sh
deploy_production:
stage: deploy
environment:
name: production
script:
- echo "Production deploy basliyor..."
- ./deploy.sh
only:
- main
Bu yapıda deploy_staging job’u staging scope’undaki DATABASE_URL değerini alırken, deploy_production job’u production scope’undaki değeri alır. Kod tamamen aynı, sadece GitLab variables scope’u işi hallediyor.
Gerçek Dünya Senaryosu 1: Docker Registry Authentication
Diyelim ki private bir Docker registry kullanıyorsunuz. Credentials’ı pipeline içinde güvenli şekilde kullanmak için şu yaklaşımı kullanabilirsin.
Önce GitLab’da şu variable’ları tanımla (ikisi de Masked ve Protected):
REGISTRY_USER: Registry kullanıcı adıREGISTRY_PASSWORD: Registry şifresiREGISTRY_URL: Registry URL’i (bu masked olmak zorunda değil)
# .gitlab-ci.yml
stages:
- build
- push
variables:
IMAGE_NAME: myapp
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
build_image:
stage: build
image: docker:24.0
services:
- docker:24.0-dind
before_script:
- echo "$REGISTRY_PASSWORD" | docker login $REGISTRY_URL -u $REGISTRY_USER --password-stdin
script:
- docker build -t $REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG .
- docker build -t $REGISTRY_URL/$IMAGE_NAME:latest .
after_script:
- docker logout $REGISTRY_URL
push_image:
stage: push
image: docker:24.0
services:
- docker:24.0-dind
before_script:
- echo "$REGISTRY_PASSWORD" | docker login $REGISTRY_URL -u $REGISTRY_USER --password-stdin
script:
- docker push $REGISTRY_URL/$IMAGE_NAME:$IMAGE_TAG
- docker push $REGISTRY_URL/$IMAGE_NAME:latest
only:
- main
- develop
--password-stdin kullanımına dikkat et. Şifreyi doğrudan komut satırında argüman olarak vermek (-p $REGISTRY_PASSWORD gibi) process listesinde görünür kılar. Stdin üzerinden geçirmek çok daha güvenli.
Gerçek Dünya Senaryosu 2: Kubernetes Deployment
Kubernetes’e deployment yapıyorsan, kubeconfig dosyasını File variable olarak saklamak en iyi pratik.
GitLab’da KUBECONFIG_CONTENT adında bir variable oluştur, türünü File olarak seç ve kubeconfig içeriğini yapıştır. GitLab bu içeriği otomatik olarak bir temp dosyaya yazacak ve $KUBECONFIG_CONTENT değişkeni o dosyanın path’ini tutacak.
# .gitlab-ci.yml
deploy_k8s:
stage: deploy
image: bitnami/kubectl:latest
environment:
name: production
script:
- export KUBECONFIG=$KUBECONFIG_CONTENT
- kubectl config get-contexts
- kubectl set image deployment/myapp myapp=$REGISTRY_URL/$IMAGE_NAME:$CI_COMMIT_SHORT_SHA -n production
- kubectl rollout status deployment/myapp -n production --timeout=300s
only:
- main
Bu yaklaşımda kubeconfig içeriği asla log’a düşmüyor, geçici bir dosyada tutuluyor ve job bitince temizleniyor.
Gerçek Dünya Senaryosu 3: Veritabanı Migration
Database migration işlemleri sırasında connection string’i güvenli şekilde yönetmek kritik. Özellikle staging ve production için farklı değerler kullanıyorsan environment scope tam burada işe yarıyor.
# .gitlab-ci.yml
stages:
- test
- migrate
- deploy
run_migrations_staging:
stage: migrate
image: python:3.11
environment:
name: staging
before_script:
- pip install -r requirements.txt
script:
- echo "Migration basliyor, ortam: staging"
- python manage.py migrate --verbosity=1
variables:
DJANGO_SETTINGS_MODULE: config.settings.staging
only:
- develop
run_migrations_production:
stage: migrate
image: python:3.11
environment:
name: production
before_script:
- pip install -r requirements.txt
script:
- echo "Production migration basliyor..."
- python manage.py migrate --verbosity=1
variables:
DJANGO_SETTINGS_MODULE: config.settings.production
when: manual
only:
- main
DATABASE_URL variable’ı GitLab tarafından staging veya production scope’una göre otomatik inject ediliyor. Python uygulaması os.environ.get('DATABASE_URL') ile bu değere ulaşıyor. Kodda tek satır credentials yok.
Variable’ları .gitlab-ci.yml’de Doğru Kullanmak
Variable kullanımında birkaç pratik detay var.
Inline tanımlama ile global variable’ı geçici ezme:
deploy_hotfix:
stage: deploy
variables:
DEPLOY_TIMEOUT: "600"
LOG_LEVEL: "debug"
script:
- ./deploy.sh --timeout $DEPLOY_TIMEOUT --log-level $LOG_LEVEL
Bu job’daki DEPLOY_TIMEOUT ve LOG_LEVEL sadece bu job scope’unda geçerli. Sensitive olmayan, operasyonel değerler için bu yaklaşım tamam. Ama sensitive değerleri asla buraya yazma.
Variable expansion (değişken içinde değişken):
variables:
BASE_URL: "https://api.example.com"
API_ENDPOINT: "$BASE_URL/v2/users"
GitLab, $API_ENDPOINT değerini expand ederek https://api.example.com/v2/users şeklinde kullanır. Bu özelliği ortam bazlı URL’ler oluştururken verimli kullanabilirsin.
Shell variable ile GitLab variable karıştırma:
# Script içinde
export COMPUTED_VALUE="${CI_PROJECT_NAME}-${CI_COMMIT_SHORT_SHA}-${ENVIRONMENT_NAME}"
echo "Build tag: $COMPUTED_VALUE"
Predefined variables ile kendi tanımladığın variable’ları birleştirerek dinamik değerler üretebilirsin.
API ile Variable Yönetimi
Bazen variable’ları GitLab UI’dan değil, programatik olarak yönetmek gerekir. GitLab API bunu destekliyor. Özellikle CI/CD infrastructure’ını kod olarak yönetiyorsan (Infrastructure as Code yaklaşımı) bu çok işe yarıyor.
# Yeni variable ekle
curl --request POST
--header "PRIVATE-TOKEN: $GITLAB_PERSONAL_TOKEN"
--header "Content-Type: application/json"
--data '{
"key": "DATABASE_URL",
"value": "postgresql://user:pass@host:5432/dbname",
"protected": true,
"masked": true,
"environment_scope": "production"
}'
"https://gitlab.example.com/api/v4/projects/PROJECT_ID/variables"
# Mevcut variable'ı guncelle
curl --request PUT
--header "PRIVATE-TOKEN: $GITLAB_PERSONAL_TOKEN"
--header "Content-Type: application/json"
--data '{"value": "yeni-deger"}'
"https://gitlab.example.com/api/v4/projects/PROJECT_ID/variables/DATABASE_URL"
# Tum variable'lari listele
curl --header "PRIVATE-TOKEN: $GITLAB_PERSONAL_TOKEN"
"https://gitlab.example.com/api/v4/projects/PROJECT_ID/variables"
Bu API’yi bir shell script ile sarıp variable rotation işlemini otomatize edebilirsin. Örneğin 90 günde bir API anahtarlarını otomatik rotate eden bir cron job yazabilirsin.
Vault Entegrasyonu ile Gelişmiş Güvenlik
GitLab Premium ve Ultimate kullanıcıları için HashiCorp Vault entegrasyonu mevcut. Bu entegrasyonla variable’ları GitLab’da değil, Vault’ta saklayabilirsin. GitLab job’ları JWT authentication kullanarak Vault’tan secrets çekiyor.
# .gitlab-ci.yml - Vault entegrasyonu
job_with_vault:
stage: deploy
secrets:
DATABASE_PASSWORD:
vault: production/db/password@ops-vault
file: false
API_KEY:
vault: production/services/external-api/key@ops-vault
file: false
script:
- echo "Vault'tan alinan secret kullaniliyor..."
- ./deploy.sh
Bu yaklaşımda secrets GitLab’da hiç saklanmıyor. Job çalışırken Vault’tan JWT ile doğrulanıp çekiliyor. Rotation tamamen Vault tarafında yönetiliyor. Enterprise ortamlarda önerilen yaklaşım bu.
Community edition kullanıyorsan alternatif olarak vault CLI’yı script içinde kullanabilirsin, sadece Vault token’ını veya AppRole credentials’ını GitLab variable olarak saklaman yeterli.
Güvenlik En İyi Pratikleri
Variable yönetiminde dikkat etmen gereken noktaları özetleyelim.
Debug modunu production’da kapatmak: GitLab CI’da CI_DEBUG_TRACE: "true" ayarı tüm değişkenleri loglara döker. Masked variable’lar bile bu modda görünür hale gelir. Bu ayarı sadece gerçekten debug yapman gerektiğinde ve non-production ortamlarda aç.
Variable isimlerini tutarlı tutmak: Bir naming convention belirle ve ona uyu. Örneğin PROD_DB_PASSWORD, STAGING_DB_PASSWORD gibi prefix kullanmak yerine environment scope mekanizmasını kullan ve her ikisine de DB_PASSWORD de. Bu hem kod okunabilirliğini artırır hem de yanlışlıkla staging değerini production’da kullanma riskini azaltır.
Minimum yetki prensibi: Her variable’ı ihtiyaç duyan job’lara sınırla. Group-level variable’ları gereğinden fazla kullanmaktan kaçın. Eğer bir secret sadece tek projeye aitse, group-level’da tanımlama.
Düzenli rotation: Özellikle otomatik deployment yapan service account token’larını ve API anahtarlarını düzenli olarak rotate et. GitLab API’sini kullanarak bu işlemi otomatize edebilirsin.
Audit loglarını takip et: GitLab, variable değişikliklerini audit log’a yazıyor. Settings > Audit Events bölümünden kim ne zaman hangi variable’ı değiştirdi görebilirsin. Bu özelliği düzenli gözden geçir.
# Bir job icinde mevcut environment variable'lari debug etmek icin
# (sadece gelistirme ortaminda, production'da ASLA kullanma)
debug_variables:
stage: .pre
script:
- env | grep -v "PASSWORD|SECRET|TOKEN|KEY" | sort
when: manual
only:
- develop
Bu script, hassas değerleri filtreleyen basit bir debug job’u. Sadece develop branch’inde ve manual tetiklendiğinde çalışıyor.
Sık Yapılan Hatalar
Hata 1: Variable değerini commit mesajına veya log’a yazmak. echo "Sunucuya $DB_PASSWORD ile baglaniliyor" yazmak log’da masked gösterir, ama echo "Sunucuya postgres://user:$DB_PASSWORD@host/db ile baglaniliyor" şeklinde connection string içine gömerseniz maskeleme devre dışı kalabilir. Variable değerini hiçbir zaman string içinde parçalara ayırma.
Hata 2: .env dosyasını repository’e commit etmek. .env dosyasını .gitignore‘a eklemek unutuluyor ve şifreler repository’e giriyor. .env.example dosyası oluştur, gerçek değerleri orada bırakma.
Hata 3: Shared runner’larda hassas variable’ları kullanmak. GitLab.com’un shared runner’ları kullanıyorsan ve çok hassas bir secret varsa, dedicated runner kullanmayı düşün. Shared runner’lar multiple tenant ortamında çalışıyor.
Hata 4: Variable scope’unu unutmak. Protected variable tanımladın ama branch protected değil. Job çalışıyor ama variable boş geliyor. Bu çok zaman kaybettiren bir hata. Variable oluştururken environment scope ve protected ayarlarını dikkatlice kontrol et.
Sonuç
GitLab CI/CD Variables, pipeline güvenliğinin temel taşlarından biri. Masked ve protected variable kombinasyonu, environment scope yönetimi ve File variable tipi bir arada kullanıldığında gerçekten sağlam bir güvenlik katmanı oluşturuyor. Ancak bu mekanizmaların işe yaraması için takımın bu pratiği benimsemesi gerekiyor. Kod review süreçlerine “variable dosyaya gömülmüş mü?” kontrolü eklemek, yeni ekip üyelerine bu yaklaşımı anlatmak ve otomatik secret scanning tool’larını (GitLab’ın built-in Secret Detection özelliği dahil) aktif tutmak şart.
Başlangıç noktası olarak şunu öneririm: Mevcut pipeline’larını tara, hardcoded olan tüm değerleri çıkar, GitLab Variables’a taşı ve branch protection kurallarını gözden geçir. Bu üç adım bile büyük çoğunluk için ciddi bir güvenlik iyileştirmesi sağlıyor. Geri kalanı zaman içinde olgunlaşır.
