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.

Bir yanıt yazın

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