GitLab ile DevSecOps: Pipeline’a Güvenlik Taraması Entegrasyonu
Üretim ortamında bir güvenlik açığı keşfetmenin en kötü zamanı, o açığın aktif olarak istismar edildiği andır. GitLab’ın DevSecOps yeteneklerini tam anlamıyla kullandığınızda, bu tür sürprizlerle karşılaşma olasılığınız dramatik biçimde düşüyor. Ben bu entegrasyonları onlarca farklı müşteri ortamında kurdum ve şunu net söyleyebilirim: “güvenlik ekibinin işi” diye bir şey artık yok, güvenlik herkesin işi ve pipeline’a gömülmesi şart.
DevSecOps Nedir ve GitLab Neden Merkeze Oturur
DevSecOps, güvenliği yazılım geliştirme döngüsünün sonuna değil, başına koymak demek. Klasik yaklaşımda güvenlik taramaları genellikle release öncesi yapılır, bir sürü bulgu çıkar, geliştirici takımı “bunları kim düzeltecek” diye birbirine bakar ve çoğu şey ertelenir. GitLab bu döngüyü kırmak için ciddi bir altyapı sunuyor.
GitLab’ın güzel tarafı, SAST (Static Application Security Testing), DAST (Dynamic Application Security Testing), Container Scanning, Dependency Scanning ve Secret Detection gibi araçların hepsini kendi ekosistemi içinde sunması. Ayrı araç lisansları, ayrı entegrasyon çalışmaları yok. Tek bir .gitlab-ci.yml dosyasıyla bunların tamamını devreye alabilirsiniz.
Tabii bu kadar kolay olmadığını da söylemeliyim. Varsayılan şablonları olduğu gibi yapıştırmak yerine ortamınıza göre uyarlamak, false positive oranlarını düşürmek ve bulgular için anlamlı bir triage süreci kurmak ayrı bir iş. Bu yazıda hem teorik çerçeveyi hem de sahadan edindiğim pratik notları paylaşacağım.
Temel Güvenlik Taraması Türleri
GitLab’da güvenlik taraması denince akla birkaç farklı katman gelmeli:
SAST (Statik Analiz): Kaynak kodu çalıştırmadan analiz eder. SQL injection, XSS, hardcoded credential gibi yaygın zaafiyetleri pattern matching ve veri akışı analizi ile bulur. Python, Java, Go, JavaScript dahil onlarca dili destekler.
Dependency Scanning: Kullandığınız üçüncü parti kütüphanelerin bilinen CVE’lerini tarar. requirements.txt, package.json, pom.xml gibi dosyaları okuyarak NVD ve diğer veritabanlarıyla karşılaştırır.
Container Scanning: Docker imajlarınızın içindeki OS paketlerini tarar. Trivy veya Grype kullanır, image registry’ye push etmeden önce yakalarsınız.
DAST (Dinamik Analiz): Çalışan bir uygulamaya karşı OWASP ZAP tabanlı tarama yapar. Bu biraz daha karmaşık kurulum gerektirir ama runtime zaafiyetleri yakalamak için değerli.
Secret Detection: Commit geçmişi dahil kaynak kodda API key, token, şifre gibi sızdırılmış bilgileri arar. Benim en çok değer verdiğim özellik bu, çünkü geliştirici “geçici olarak koydum” dediği şeyi commit’ler ve orası tarihe geçer.
Pipeline Entegrasyonu: Sıfırdan Başlamak
Yeni bir projeye entegrasyon yaparken ben genellikle önce minimal bir yapı kuruyorum, sonra kademeli olarak genişletiyorum. Şu şekilde başlayabilirsiniz:
# .gitlab-ci.yml - Temel yapı
stages:
- build
- test
- security
- deploy
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
Bu kadar basit. GitLab’ın built-in template’leri projenizin diline göre otomatik olarak doğru analyzer’ı seçiyor. Java projesi ise SpotBugs kullanır, Python ise Bandit ve Semgrep devreye girer.
Ama bu template’leri direkt include ettiğinizde bazı sorunlarla karşılaşırsınız. Özellikle büyük projelerde tarama süresi uzuyor, her commit’te tam tarama yapmak pipeline maliyetini artırıyor. Bunu yönetmek için rules tanımlaması gerekiyor:
# Güvenlik taramalarını sadece belirli koşullarda çalıştır
variables:
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp, vendor"
DS_EXCLUDED_PATHS: "spec, test, tests"
SECRET_DETECTION_HISTORIC_SCAN: "false"
sast:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^release/.*/
variables:
SAST_EXCLUDED_ANALYZERS: "eslint"
Burada SECRET_DETECTION_HISTORIC_SCAN değişkenine dikkat edin. Bunu true yaptığınızda tüm commit geçmişini tarar, bu ilk kurulumda bir kez yapılması gereken bir şey. Sonrasında incremental tarama yeterli.
SAST Konfigürasyonu ve Özelleştirme
Varsayılan SAST kuralları çoğu zaman gereğinden fazla gürültü üretiyor. Özellikle test dosyalarındaki mock veriler, örnek kodlar sürekli false positive yaratıyor. Bunu .sast-rules.yml dosyasıyla yönetebilirsiniz:
# .gitlab/sast-ruleset.toml
[semgrep]
[[semgrep.passthrough]]
type = "raw"
value = '''
rules:
- id: custom-sql-injection-check
patterns:
- pattern: |
$QUERY = "..." + $INPUT
- pattern-not-inside: |
// nosec
message: "Potansiyel SQL injection: kullanici girdisi dogrudan sorguya ekleniyor"
languages: [python, java]
severity: ERROR
metadata:
category: security
confidence: HIGH
'''
Kendi organizasyonunuzun güvenlik politikalarına özel kurallar yazabilirsiniz. Benim bir müşteride şirket içi bir API’nin yanlış kullanımını tespit eden özel kural yazmıştım, standart araçların yakalayamayacağı bir şeydi.
Dependency Scanning ve License Compliance
Dependency scanning sadece CVE bulmakla kalmıyor, lisans uyumluluğunu da kontrol edebiliyorsunuz. GPL lisanslı bir kütüphaneyi ticari projede kullanmak yasal risk yaratır, bunu otomatik olarak yakalamak mümkün:
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
dependency_scanning:
variables:
DS_MAX_DEPTH: 5
GEMNASIUM_DB_UPDATE_INTERVAL: "24h"
artifacts:
reports:
dependency_scanning: gl-dependency-scanning-report.json
license_scanning:
variables:
LICENSE_FINDER_CLI_OPTS: "--decisions-file=.license_decisions.yml"
.license_decisions.yml dosyasında hangi lisansların kabul edildiğini, hangilerinin reddedildiğini tanımlıyorsunuz. Büyük organizasyonlarda hukuk ekibiyle koordineli şekilde bu listeyi oluşturmak gerekiyor.
Container Scanning: Image Güvenliği
Kubernetes kullanan ortamlarda container scanning kritik. En temiz çalışan deploy pipeline’ım şuna benziyor:
stages:
- build
- scan
- deploy
build_image:
stage: build
image: docker:24.0
services:
- docker:24.0-dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH
container_scanning:
stage: scan
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
CS_SEVERITY_THRESHOLD: "HIGH"
TRIVY_NO_PROGRESS: "true"
CS_IGNORE_UNFIXED: "true"
needs:
- build_image
deploy_to_staging:
stage: deploy
script:
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
CS_IGNORE_UNFIXED: "true" ayarı tartışmalı ama pratikte çok önemli. Bir OS paketinin CVE’si var ama henüz upstream’den fix gelmemişse yapabileceğiniz pek bir şey yok. Bu bulgular için sürekli alarm almak ekibi körleştiriyor. Fix geldiğinde uyarılmak istiyorsunuz, fix olmayan şeyler için değil.
CS_SEVERITY_THRESHOLD ile pipeline’ı sadece HIGH ve CRITICAL bulgularda kırmayı tercih ediyorum. MEDIUM ve LOW için report üretip Security Dashboard’a düşmesini sağlıyorum ama deploy’u engellemiyorum.
Secret Detection ve Pre-commit Hook Entegrasyonu
GitLab’ın secret detection’ı pipeline’da çalışıyor ama en iyi savunma geliştirici bilgisayarında, commit öncesinde başlıyor. İkisini birlikte kullanmak katmanlı güvenlik sağlıyor:
# .gitlab-ci.yml içinde secret detection
secret_detection:
stage: security
variables:
SECRET_DETECTION_LOG_OPTIONS: "--all"
artifacts:
reports:
secret_detection: gl-secret-detection-report.json
rules:
- if: $CI_PIPELINE_SOURCE == "push"
when: always
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
Geliştirici tarafında pre-commit hook için şu scripti onboarding sürecinde standart hale getirdim:
#!/bin/bash
# .git/hooks/pre-commit
# gitleaks kurulu olmasi gerekiyor: brew install gitleaks veya apt install gitleaks
echo "Secret detection taramasi basliyor..."
if command -v gitleaks &> /dev/null; then
gitleaks detect --staged --no-git -v
if [ $? -ne 0 ]; then
echo "HATA: Potansiyel secret tespit edildi. Commit engellendi."
echo "False positive ise 'git commit --no-verify' kullanabilirsiniz"
echo "ama lutfen oncelikle bulgulari inceleyin."
exit 1
fi
else
echo "UYARI: gitleaks bulunamadi. Secret detection atlandı."
echo "Kurulum: https://github.com/gitleaks/gitleaks"
fi
echo "Secret detection tamamlandi, temiz."
exit 0
Bu hook’u repo’ya ekleyip core.hooksPath ile team’e dağıtabilirsiniz. Alternatif olarak pre-commit framework kullanıyorsanız:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
args: ['--verbose']
Security Dashboard ve MR Entegrasyonu
Pipeline’dan rapor üretmek tek başına yeterli değil. Bulguların gözden geçirildiğinden emin olmanız gerekiyor. GitLab’ın Security Dashboard bu noktada devreye giriyor.
Merge Request’lerde güvenlik bulgularının görünmesi için proje ayarlarından Security Dashboard’u aktifleştirmeniz yeterli. Bunun üstüne approval rule ekleyebilirsiniz:
# .gitlab/CODEOWNERS
# Guvenlik hassas dosyalar icin security team review zorunlu
/config/secrets.yml @security-team
/deploy/k8s/secrets/ @security-team @devops-team
*.env.example @security-team
Approval rule’ları UI’dan değil, API üzerinden da yönetebilirsiniz. Bu özellikle çok sayıda proje yönetirken işe yarıyor:
# GitLab API ile approval rule olusturma
curl --request POST
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"
--header "Content-Type: application/json"
--data '{
"name": "Security Review",
"approvals_required": 1,
"usernames": ["security-lead"],
"rule_type": "regular"
}'
"https://gitlab.example.com/api/v4/projects/${PROJECT_ID}/approval_rules"
Policy as Code: Güvenlik Politikalarını Otomatize Etmek
GitLab Ultimate’da bulunan Security Policies özelliği, güvenlik kurallarını merkezi olarak yönetmenizi sağlıyor. Ama daha erişilebilir bir yaklaşım olarak, compliance framework’ü YAML ile tanımlayabilirsiniz:
# security-policy.yml - Ornek politika tanimlamasi
scan_execution_policy:
- name: "Zorunlu SAST Taramasi"
description: "Main branch'e her push'ta SAST zorunlu"
enabled: true
rules:
- type: pipeline
branches:
- main
- /^release/.*/
actions:
- scan: sast
- scan: secret_detection
- name: "MR Icin Container Scan"
description: "Container degisiklikleri icin imaj taramasi"
enabled: true
rules:
- type: merge_request
branches:
- main
actions:
- scan: container_scanning
variables:
CS_SEVERITY_THRESHOLD: CRITICAL
Bu tür politikaları parent group seviyesinde tanımlayıp alt gruplara ve projelere inherited olarak uygulayabilirsiniz. Onlarca proje yönetiyorsanız bu yaklaşım hayat kurtarıyor, her projeye ayrı ayrı dokunmak zorunda kalmıyorsunuz.
Gerçek Dünya Senaryosu: Yanlış Pozitif Yönetimi
Pratikte en can sıkıcı konu false positive yönetimi. Bir e-ticaret projesinde test fixture dosyalarındaki sahte kredi kartı numaraları sürekli alarm üretiyordu. GitLab bunu yönetmek için vulnerability dismissal mekanizması sunuyor ama bunu doğru kullanmak önemli:
Bir bulguyu “accepted risk” olarak işaretlediğinizde neden kabul ettiğinizi, kim kabul ettiğini ve ne zaman review edileceğini kaydetmelisiniz. Bunu GitLab UI’da yapabilirsiniz, ama API ile daha sistematik yönetmek mümkün:
#!/bin/bash
# Belirli bir vulnerability'yi dismiss etme scripti
# Kullanim: ./dismiss-vuln.sh PROJECT_ID VULN_ID "Aciklama"
PROJECT_ID=$1
VULN_ID=$2
COMMENT=$3
REVIEW_DATE=$(date -d "+90 days" +%Y-%m-%d)
curl --request POST
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"
--header "Content-Type: application/json"
--data "{
"state": "dismissed",
"dismissal_reason": "accepted_risk",
"comment": "${COMMENT} | Review tarihi: ${REVIEW_DATE} | Onaylayan: ${GITLAB_USER_LOGIN}"
}"
"https://gitlab.example.com/api/v4/projects/${PROJECT_ID}/vulnerabilities/${VULN_ID}/dismiss"
echo "Vulnerability ${VULN_ID} dismiss edildi. Review tarihi: ${REVIEW_DATE}"
90 günlük review döngüsü koymanızı tavsiye ederim. “Accepted risk” dediğiniz şey 3 ay sonra hala geçerli mi, exploit geldi mi, fix çıktı mı kontrol etmek gerekiyor.
Pipeline Performansı ve Maliyet Optimizasyonu
Güvenlik taramaları pipeline süresini ciddi uzatabiliyor. Bunu yönetmek için birkaç strateji:
Paralel çalıştırma: Taramaları aynı stage içine alarak paralel çalıştırın, sıralı değil.
Önbellekleme: Dependency scanning için bağımlılık veritabanlarını önbellekleyin.
Kademeli tarama: Her commit’te tam tarama yerine, sadece değişen dosyaları hedef alan incremental tarama:
dependency_scanning:
stage: security
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- vendor/
- .npm/
parallel:
matrix:
- DS_ANALYZER: "gemnasium"
- DS_ANALYZER: "retire.js"
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
changes:
- "**/*.json"
- "**/*.lock"
- "**/requirements*.txt"
- "**/Gemfile*"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
changes keyword’ü ile sadece bağımlılık dosyaları değiştiğinde dependency scanning çalışır. Bu basit optimizasyon bir müşteride pipeline süresini 18 dakikadan 7 dakikaya indirmişti.
Monitoring ve Alerting
Pipeline’da bulguları yakalamak yetmez, bunların takibini yapmak gerekiyor. GitLab’ın Security Dashboard’una ek olarak Slack/Teams entegrasyonu kurmak bulguların gözden kaçmasını önlüyor:
# Pipeline sonunda kritik bulgu varsa Slack bildirimi
notify_security_findings:
stage: .post
image: alpine:latest
script:
- |
if [ -f "gl-sast-report.json" ]; then
CRITICAL_COUNT=$(cat gl-sast-report.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
critical = [v for v in data.get('vulnerabilities', [])
if v.get('severity') in ['Critical', 'High']]
print(len(critical))
")
if [ "$CRITICAL_COUNT" -gt "0" ]; then
curl -X POST -H 'Content-type: application/json'
--data "{
"text": ":red_circle: *Guvenlik Uyarisi* - ${CI_PROJECT_NAME}",
"attachments": [{
"color": "danger",
"text": "${CRITICAL_COUNT} Critical/High bulgu tespit edildi.nBranch: ${CI_COMMIT_BRANCH}nPipeline: ${CI_PIPELINE_URL}"
}]
}"
"${SLACK_WEBHOOK_URL}"
fi
fi
rules:
- when: always
Sonuç
GitLab ile DevSecOps entegrasyonu yapmak, tek seferlik bir kurulum değil, sürekli iyileştirme gerektiren bir süreç. İlk hafta false positive oranı yüksek çıkar, ekip şikayet eder, “bu araç işe yaramıyor” denir. Bu normal. Kuralları ayarlamak, threshold’ları doğru belirlemek, ekibi bulgular konusunda eğitmek zaman alıyor.
Benim önerim şu: Önce secret detection’ı devreye alın, çünkü en net sonuç veren o. Sonra dependency scanning ekleyin, CVE düzeltmek geliştirici için somut ve anlaşılır bir görev. SAST’ı en sona bırakın, çünkü en fazla tuning gerektiren o.
Uzun vadede bu yatırımın karşılığını kesinlikle alıyorsunuz. Bir üretim açığını kapatmak, pipeline’da yakalamaktan onlarca kat daha pahalı. Güvenlik taramalarını “ekstra maliyet” olarak değil, teknik borç sigortası olarak görmek gerekiyor. Ve GitLab bu sigortayı geliştirme sürecinizin doğal bir parçası haline getirmek için gerçekten güçlü bir platform sunuyor.
