Güvenlik Taraması: GitLab CI/CD ile SAST ve DAST Kullanımı
Bir sabah uyandığınızda production ortamınızda kritik bir güvenlik açığı bulunduğunu öğrenmek, hiç bir sysadmin’in yaşamak istemeyeceği bir senaryodur. Oysa bu açıkların büyük çoğunluğu, kod geliştirme sürecinin erken aşamalarında tespit edilebilir. GitLab CI/CD pipeline’larına entegre edilecek SAST ve DAST araçları, güvenlik testlerini “birinin yapacağı bir iş” olmaktan çıkarıp otomatik ve sürekli bir süreç haline getiriyor. Bu yazıda, gerçek dünya senaryoları üzerinden GitLab’ın güvenlik tarama özelliklerini nasıl etkin kullanabileceğinizi ele alacağız.
SAST ve DAST Nedir, Neden İkisine de İhtiyaç Var?
SAST (Static Application Security Testing), uygulamanızın kaynak kodunu çalıştırmadan analiz eden bir güvenlik tarama yöntemidir. SQL injection, XSS açıkları, hardcoded credentials gibi sorunları kod üzerinde tespit eder. Geliştirici bir değişiklik yaptığı anda, henüz kod merge bile edilmeden bu tarama çalışabilir.
DAST (Dynamic Application Security Testing) ise tam tersine, çalışan uygulamaya karşı gerçek saldırı simülasyonları yapar. OWASP ZAP veya Nikto gibi araçlar, deploy edilmiş uygulamayı dışarıdan tararak runtime’da ortaya çıkan açıkları bulur. Authentication bypass, session management sorunları ve injection açıkları DAST’ın güçlü olduğu alanlardır.
İkisi birbirinin yerine geçemez. SAST kodu erken dönemde yakalar, DAST ise uygulamanın gerçek çalışma ortamındaki davranışını test eder. Bir Python Flask uygulamasında eval() kullanımı SAST tarafından tespit edilirken, yanlış konfigüre edilmiş bir HTTP header’ı ancak DAST ile ortaya çıkabilir.
GitLab’ın Dahili Güvenlik Özellikleri
GitLab, Ultimate ve bazı durumlarda Free tier üzerinde dahili güvenlik tarama şablonları sunar. Ancak bu şablonların tamamı için lisans gereksinimleri farklılık gösterir. Biz bu yazıda hem GitLab’ın native araçlarını hem de açık kaynak alternatifleri olan Semgrep, Bandit ve OWASP ZAP’ı kullanarak tam kapsamlı bir pipeline kuracağız.
GitLab’ın CI/CD şablonlarını projenize dahil etmek için .gitlab-ci.yml dosyanıza şu satırları ekleyebilirsiniz:
# .gitlab-ci.yml - Temel yapı
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
variables:
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
SECURE_LOG_LEVEL: "info"
Bu tek adımla GitLab’ın kendi analiz motorları devreye girer. Ancak daha fazla kontrol ve özelleştirme istiyorsanız, araçları kendiniz yapılandırmanız gerekir.
Pipeline Mimarisi: Aşamalar Nasıl Tasarlanmalı?
Güvenlik testlerini pipeline’ınıza entegre ederken dikkat etmeniz gereken en önemli şey hız ve kapsamlılık dengesidir. Her commit’te tam bir DAST taraması yapmak pratik değildir. Aşağıdaki mimari, gerçek dünya projelerinde iyi çalışan bir yaklaşımdır:
- build: Uygulama derleme ve imaj oluşturma
- test: Unit testler ve kod kalite kontrolleri
- security-scan: SAST, secret detection, dependency scanning
- deploy-staging: Staging ortamına deploy
- dast: Çalışan uygulamaya karşı dinamik tarama
- security-report: Bulguların raporlanması ve threshold kontrolü
# .gitlab-ci.yml - Tam pipeline tanımı
stages:
- build
- test
- security-scan
- deploy-staging
- dast
- security-report
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
APP_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
STAGING_URL: "https://staging.example.com"
default:
image: docker:24.0
services:
- docker:24.0-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
SAST Yapılandırması: Bandit ve Semgrep ile Derinlemesine Analiz
GitLab’ın dahili SAST analiz motorları dil tespiti yaparak otomatik araç seçimi yapar. Ancak Python projelerinde Bandit’i, çoklu dil projelerinde Semgrep’i manuel olarak yapılandırmak çok daha esnek bir yapı sağlar.
# Bandit ile Python SAST taraması
sast-bandit:
stage: security-scan
image: python:3.11-slim
before_script:
- pip install bandit[toml] --quiet
script:
- |
bandit -r ./src
-f json
-o bandit-report.json
-l
--severity-level medium
-x ./src/tests,./src/migrations || BANDIT_EXIT=$?
# Kritik bulgular varsa pipeline'ı durdur
HIGH_COUNT=$(python3 -c "
import json, sys
with open('bandit-report.json') as f:
data = json.load(f)
high = sum(1 for r in data['results'] if r['issue_severity'] == 'HIGH')
print(high)
")
echo "Yüksek öncelikli bulgu sayısı: $HIGH_COUNT"
if [ "$HIGH_COUNT" -gt "0" ]; then
echo "HATA: Kritik güvenlik açıkları tespit edildi!"
exit 1
fi
artifacts:
reports:
sast: bandit-report.json
paths:
- bandit-report.json
when: always
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Semgrep, çok daha geniş bir kural seti sunuyor ve özellikle polyglot projelerde çok işe yarıyor:
# Semgrep ile çoklu dil SAST taraması
sast-semgrep:
stage: security-scan
image: returntocorp/semgrep:latest
script:
- |
semgrep
--config=p/owasp-top-ten
--config=p/cwe-top-25
--config=p/secrets
--json
--output=semgrep-report.json
--severity=WARNING
--exclude="node_modules"
--exclude="*.test.js"
--exclude="vendor/"
./
# Bulgu özetini terminale yaz
python3 - <<'EOF'
import json
with open('semgrep-report.json') as f:
data = json.load(f)
results = data.get('results', [])
errors = [r for r in results if r.get('extra', {}).get('severity') == 'ERROR']
warnings = [r for r in results if r.get('extra', {}).get('severity') == 'WARNING']
print(f"Toplam bulgu: {len(results)}")
print(f"Kritik (ERROR): {len(errors)}")
print(f"Uyarı (WARNING): {len(warnings)}")
if len(errors) > 0:
print("nKritik bulgular:")
for e in errors[:5]:
print(f" - {e['path']}:{e['start']['line']} - {e['check_id']}")
EOF
artifacts:
reports:
sast: semgrep-report.json
paths:
- semgrep-report.json
when: always
allow_failure: false
Secret Detection: Hardcoded Credentials’ı Yakalamak
Üretim ortamlarında yaşanan en yaygın ve utanç verici güvenlik ihlallerinden biri, kaynak koda gömülü API anahtarları ve şifrelerin repository’de kalmasıdır. GitLab’ın secret detection şablonu bunu ele alır, ama özelleştirme eklemek işleri daha da sağlamlaştırır:
# Secret Detection - Özelleştirilmiş kural seti ile
secret-detection:
stage: security-scan
image: python:3.11-slim
before_script:
- pip install detect-secrets --quiet
script:
- |
# Mevcut baseline ile karşılaştır
if [ -f .secrets.baseline ]; then
detect-secrets scan
--baseline .secrets.baseline
--exclude-files '.lock$'
--exclude-files 'package-lock.json'
--exclude-files '.*.test.*'
> /tmp/current-secrets.json
NEW_SECRETS=$(python3 -c "
import json
with open('/tmp/current-secrets.json') as f:
data = json.load(f)
count = sum(len(v) for v in data.get('results', {}).values())
print(count)
")
echo "Tespit edilen potansiyel secret sayısı: $NEW_SECRETS"
if [ "$NEW_SECRETS" -gt "0" ]; then
echo "UYARI: Kodda potansiyel secret tespit edildi!"
cat /tmp/current-secrets.json | python3 -m json.tool
exit 1
fi
else
echo "Baseline dosyası bulunamadı, ilk tarama yapılıyor..."
detect-secrets scan . > .secrets.baseline
echo "Baseline oluşturuldu. Lütfen .secrets.baseline dosyasını commit edin."
fi
artifacts:
paths:
- .secrets.baseline
when: always
Dependency Scanning: Bağımlılık Açıklarını Yakalamak
Trivy, hem container imajlarını hem de uygulama bağımlılıklarını tarayabilen güçlü bir araçtır:
# Trivy ile dependency ve container taraması
dependency-scan:
stage: security-scan
image:
name: aquasec/trivy:latest
entrypoint: [""]
script:
- |
# Uygulama bağımlılıklarını tara
trivy fs
--exit-code 1
--severity HIGH,CRITICAL
--format json
--output trivy-deps-report.json
--ignore-unfixed
./
# Container imajını tara (build aşamasından gelir)
trivy image
--exit-code 1
--severity CRITICAL
--format json
--output trivy-image-report.json
--ignore-unfixed
$APP_IMAGE
echo "Bağımlılık taraması tamamlandı"
# Özet rapor
VULN_COUNT=$(cat trivy-deps-report.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
count = 0
for result in data.get('Results', []):
count += len(result.get('Vulnerabilities', []) or [])
print(count)
")
echo "Toplam zafiyet: $VULN_COUNT"
artifacts:
paths:
- trivy-deps-report.json
- trivy-image-report.json
when: always
expire_in: 1 week
allow_failure: false
DAST Yapılandırması: OWASP ZAP ile Dinamik Tarama
DAST, staging ortamınıza deploy ettiğiniz uygulamayı gerçek saldırı vektörleriyle test eder. OWASP ZAP, bu iş için endüstri standardı haline gelmiş açık kaynaklı bir araçtır:
# OWASP ZAP ile DAST taraması
dast-zap-scan:
stage: dast
image: ghcr.io/zaproxy/zaproxy:stable
variables:
ZAP_TARGET: $STAGING_URL
ZAP_RULES_FILE: ".zap/rules.tsv"
script:
- mkdir -p /zap/wrk
- |
# Temel Spider ve aktif tarama
zap-baseline.py
-t $ZAP_TARGET
-J zap-baseline-report.json
-r zap-baseline-report.html
-l WARN
-z "-config scanner.strength=HIGH"
--auto || ZAP_EXIT=$?
cp /zap/wrk/zap-baseline-report.json ./
cp /zap/wrk/zap-baseline-report.html ./
# Yüksek risk bulguları kontrol et
HIGH_RISKS=$(python3 -c "
import json
with open('zap-baseline-report.json') as f:
data = json.load(f)
high = sum(1 for site in data.get('site', [])
for alert in site.get('alerts', [])
if alert.get('riskcode', '0') in ['3', '4'])
print(high)
")
echo "Yüksek risk bulgu sayısı: $HIGH_RISKS"
if [ "$HIGH_RISKS" -gt "3" ]; then
echo "HATA: Çok fazla yüksek riskli bulgu tespit edildi!"
exit 1
fi
artifacts:
paths:
- zap-baseline-report.json
- zap-baseline-report.html
when: always
expire_in: 2 weeks
needs:
- job: deploy-staging
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH =~ /^release/.*/
Authenticated DAST: Giriş Gerektiren Sayfaların Taranması
Birçok uygulama authentication gerektiriyor. ZAP’ı kimlik doğrulamalı şekilde çalıştırmak için bir script dosyası oluşturmanız gerekir:
# ZAP Authentication Script
dast-authenticated:
stage: dast
image: ghcr.io/zaproxy/zaproxy:stable
variables:
ZAP_USERNAME: $STAGING_TEST_USER
ZAP_PASSWORD: $STAGING_TEST_PASS
ZAP_LOGIN_URL: "$STAGING_URL/api/auth/login"
script:
- mkdir -p /zap/wrk
- |
cat > /zap/wrk/auth-script.js << 'AUTHSCRIPT'
var HttpRequestHeader = Java.type("org.parosproxy.paros.network.HttpRequestHeader");
var URI = Java.type("org.apache.commons.httpclient.URI");
function authenticate(helper, paramsValues, credentials) {
var loginUrl = paramsValues.get("Login URL");
var requestUri = new URI(loginUrl, false);
var requestMethod = HttpRequestHeader.POST;
var msg = helper.prepareMessage();
var requestBody = '{"username":"' + credentials.getParam("Username") +
'","password":"' + credentials.getParam("Password") + '"}';
msg.setRequestHeader(new HttpRequestHeader(requestMethod, requestUri,
HttpRequestHeader.HTTP11));
msg.getRequestHeader().setHeader("Content-Type", "application/json");
msg.setRequestBody(requestBody);
helper.sendAndReceive(msg);
return msg;
}
AUTHSCRIPT
# Full scan authenticated
zap-full-scan.py
-t $STAGING_URL
-J zap-auth-report.json
-l WARN
-z "-config scanner.strength=MEDIUM
-config connection.timeoutInSecs=30"
|| ZAP_EXIT=$?
cp /zap/wrk/zap-auth-report.json ./ 2>/dev/null || true
echo "Kimlik doğrulamalı tarama tamamlandı. Exit code: $ZAP_EXIT"
artifacts:
paths:
- zap-auth-report.json
when: always
allow_failure: true
Güvenlik Raporu Toplama ve Merge Request Entegrasyonu
Tüm bu taramaları ayrı ayrı yapmak yeterli değil, bulguları anlamlı bir şekilde sunmak gerekiyor. GitLab’ın MR widget’larını aktive etmek için artifact formatlarının doğru olması kritik:
# Güvenlik raporlarını birleştir ve özet oluştur
security-summary:
stage: security-report
image: python:3.11-slim
before_script:
- pip install jinja2 --quiet
script:
- |
python3 << 'EOF'
import json, os
from datetime import datetime
summary = {
"generated_at": datetime.now().isoformat(),
"pipeline_id": os.environ.get("CI_PIPELINE_ID"),
"commit_sha": os.environ.get("CI_COMMIT_SHA"),
"findings": {
"sast": {"total": 0, "critical": 0, "high": 0},
"dast": {"total": 0, "critical": 0, "high": 0},
"secrets": {"total": 0},
"dependencies": {"total": 0, "critical": 0}
}
}
# Bandit bulgularını oku
if os.path.exists("bandit-report.json"):
with open("bandit-report.json") as f:
data = json.load(f)
results = data.get("results", [])
summary["findings"]["sast"]["total"] = len(results)
summary["findings"]["sast"]["critical"] = sum(
1 for r in results if r.get("issue_severity") == "HIGH"
)
# ZAP bulgularını oku
if os.path.exists("zap-baseline-report.json"):
with open("zap-baseline-report.json") as f:
data = json.load(f)
for site in data.get("site", []):
for alert in site.get("alerts", []):
summary["findings"]["dast"]["total"] += 1
if alert.get("riskcode") in ["3", "4"]:
summary["findings"]["dast"]["critical"] += 1
print(json.dumps(summary, indent=2))
with open("security-summary.json", "w") as f:
json.dump(summary, f, indent=2)
# Eşik kontrolü
total_critical = (
summary["findings"]["sast"]["critical"] +
summary["findings"]["dast"]["critical"]
)
print(f"nToplam kritik bulgu: {total_critical}")
if total_critical > 5:
print("HATA: Kritik bulgu eşiği aşıldı!")
exit(1)
print("Güvenlik taraması başarıyla tamamlandı.")
EOF
artifacts:
paths:
- security-summary.json
when: always
expire_in: 1 month
needs:
- job: sast-bandit
artifacts: true
- job: dast-zap-scan
artifacts: true
optional: true
Gerçek Dünya Senaryosu: E-Ticaret Uygulaması
Diyelim ki bir e-ticaret uygulaması üzerinde çalışıyorsunuz. Payment endpoint’leri içeriyor, kullanıcı verilerini işliyor ve üçüncü parti API’lere bağlanıyor. Bu senaryo için pipeline’ınızı nasıl şekillendirmelisiniz?
- merge_request üzerinde sadece SAST ve secret detection çalıştırın. Hız kritik, 3-5 dakikada sonuç alın.
- main branch commit’lerinde SAST + dependency scan + container scan çalıştırın.
- release branch oluşturulduğunda staging deploy + tam DAST taraması başlatın.
- scheduled pipeline ile haftada bir gece tüm kapsamlı taramaları çalıştırın.
Ödeme sayfası gibi kritik endpoint’ler için ZAP’ın context dosyasını yapılandırarak sadece belirli URL’leri hedef alabilirsiniz. Bu hem tarama süresini kısaltır hem de false positive oranını düşürür.
False Positive Yönetimi
Güvenlik taramalarında en büyük zorluklardan biri false positive’lerdir. Sürekli yanlış alarmlar ekibin araçlara olan güvenini sarsar. Bunları yönetmek için:
- Bandit için
.banditdosyası oluşturun veskips = B101,B601gibi kuralları projenize özel devre dışı bırakın. - Semgrep için
# nosemgrep: kural-adiinline yorumu kullanın, ancak bunları code review’da sorgulayın. - ZAP için
.zap/rules.tsvdosyasında bilinen false positive’leriIGNOREolarak işaretleyin. - Tüm bu istisnaları bir güvenlik kaydında belgelendirin ve düzenli gözden geçirin.
Her istisnayı körü körüne kabul etmek yerine, neden false positive olduğunu takım içinde tartışın ve belgelendirin. Altı ay sonra o kodu değiştirdiğinizde neden o istisnayı eklediğinizi hatırlamanız gerekecek.
GitLab Security Dashboard ve Raporlama
Ultimate lisansı varsa GitLab’ın Security Dashboard’u tüm bulguları merkezi bir yerde toplar. Ancak açık kaynak araçlarla da benzer bir görünürlük elde edebilirsiniz:
GitLab’ın GL-SAST format şemasına uygun JSON üretmek için araç çıktılarını dönüştürücüler yazabilirsiniz. Bu sayede Merge Request üzerindeki Security widget çalışır ve güvenlik bulguları doğrudan MR içinde görünür hale gelir. Ekibinizin her değişikliği review ederken güvenlik bulgularını da görmesi, güvenlik kültürünün oluşmasında kritik bir rol oynar.
Pipeline Performansını Optimize Etmek
Güvenlik taramaları pipeline süresini uzatabilir. Bunu minimize etmek için:
- SAST araçlarını paralel olarak çalıştırın,
needsdirektifiyle bağımlılıkları net tanımlayın. - Büyük projelerde
SAST_EXCLUDED_PATHSile test dizinlerini tarama dışında bırakın. - Semgrep için
--max-memoryparametresiyle bellek kullanımını sınırlayın. - Docker layer caching kullanarak araç imajlarının tekrar indirilmesini engelleyin.
- Değişmeyen dosyalar için
rules: changes:direktifiyle gereksiz tarama çalıştırmaktan kaçının.
Tipik olarak SAST taraması 2-4 dakika, DAST taraması ise 15-30 dakika alır. DAST’ı sadece belirli branch’lerde çalıştırmak bu maliyeti makul düzeyde tutar.
Sonuç
GitLab CI/CD pipeline’ınıza SAST ve DAST entegre etmek, güvenliği “sonradan düşünülen bir şey” olmaktan çıkarıp geliştirme sürecinin ayrılmaz bir parçası haline getirir. Bandit ve Semgrep ile statik analiz, OWASP ZAP ile dinamik tarama ve Trivy ile bağımlılık taramasını birleştirdiğinizde oldukça kapsamlı bir güvenlik ağı oluşturursunuz.
Bu yazıda anlattığımız yaklaşımın en önemli yan etkisi, güvenlik konusunda ekip içinde farkındalık yaratmasıdır. Bir geliştirici MR açtığında ve pipeline “Bu kodda SQL injection riski var” dediğinde, o geliştirici bir sonraki seferinde daha dikkatli kod yazar. Araçlar kural koyar, ama asıl değişimi kültür yaratır.
Başlangıç için her şeyi birden uygulamaya çalışmayın. Önce secret detection ve temel SAST ile başlayın, ekibi alıştırın, false positive yönetimini oturtun. Ardından DAST ve dependency scanning ekleyin. Küçük adımlarla ilerleyen bir güvenlik programı, bir gecede kurulan ama ertesi gün abandon edilen kompleks bir yapıdan her zaman daha değerlidir.
