GitHub Actions ile Güvenlik Taraması: CodeQL Kullanım Rehberi
Kod tabanınızda bir güvenlik açığı keşfetmek, özellikle bu açığın production’a çıkmış olması durumunda, bir sysadmin olarak yaşayabileceğiniz en stresli anlardan biridir. GitHub’ın sunduğu CodeQL entegrasyonu, bu tür sürprizleri CI/CD pipeline’ınıza gömdüğünüz statik analiz araçlarıyla minimize etmenizi sağlıyor. Bu yazıda, GitHub Actions üzerinde CodeQL’i sıfırdan kurarak gerçek dünya senaryolarında nasıl kullanacağınızı ele alacağız.
CodeQL Nedir ve Neden Önemlidir
CodeQL, GitHub’ın satın aldığı Semmle şirketinin geliştirdiği bir kod analiz motorudur. Klasik linter araçlarından farklı olarak kodunuzu bir veritabanına dönüştürür ve bu veritabanı üzerinde SQL benzeri sorgular çalıştırarak güvenlik açıklarını tespit eder. Yani sadece “bu satırda şu pattern var mı?” sorusunu sormakla kalmaz, veri akışını, değişken yaşam döngüsünü ve fonksiyon çağrı zincirlerini analiz edebilir.
Desteklenen diller arasında şunlar var:
- C/C++: Buffer overflow, use-after-free gibi bellek güvenliği sorunları
- Java/Kotlin: SQL injection, XXE, deserialization açıkları
- Python: Code injection, path traversal, SSRF
- JavaScript/TypeScript: XSS, prototype pollution, ReDoS
- Go: SQL injection, command injection
- Ruby: Mass assignment, SQL injection
- Swift: Path traversal, insecure randomness
Public repository’ler için CodeQL tamamen ücretsizdir. Private repository’ler için ise GitHub Advanced Security lisansı gerekiyor, ancak birçok kurumsal müşteri zaten bu lisansı kullanıyor.
Temel Kurulum: İlk CodeQL Workflow’u
En hızlı başlangıç yöntemi GitHub’ın otomatik kurulum sihirbazını kullanmak. Ancak sysadmin olarak kontrol bizde olsun istiyoruz, o yüzden manuel kurulumla gidelim.
mkdir -p .github/workflows
touch .github/workflows/codeql-analysis.yml
Basit bir workflow dosyası şu şekilde görünür:
name: "CodeQL Security Analysis"
on:
push:
branches: [ "main", "develop" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '0 2 * * 1' # Her Pazartesi gece 02:00'da
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
security-events: write
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
- language: python
build-mode: none
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
Bu basit konfigürasyon bile çoğu proje için oldukça etkilidir. Ancak production ortamlarında bu kadarıyla yetinmek istemeyiz.
Java Projesi için Build-Mode Konfigürasyonu
Java, C++ gibi derlenen diller için CodeQL’in kodu doğru analiz edebilmesi için build sürecini takip etmesi gerekir. autobuild modu çoğu zaman işe yarasa da özelleştirilmiş build süreçlerinde sorun çıkarabilir.
name: "CodeQL - Java Application"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
analyze:
name: CodeQL Java Analysis
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: java-kotlin
build-mode: manual
- name: Build with Maven
run: |
mvn clean compile -B
-Dmaven.test.skip=true
-Dmaven.javadoc.skip=true
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:java-kotlin"
upload: always
build-mode: manual seçeneği özellikle şu durumlarda kritik:
- Multi-module Maven/Gradle projeleri: Modüller arası bağımlılıklar nedeniyle autobuild bazen yalnızca bir kısmını analiz eder
- Özel kod üretimi yapan projeler: Protobuf, JAXB gibi araçlarla üretilen kodların önce generate edilmesi gerekir
- Kurumsal artifact repository kullananlar: Nexus veya Artifactory’den bağımlılık çeken projelerde autobuild credentials sorunuyla karşılaşabilir
Özel Query Suite Tanımlamak
CodeQL’in gücünden tam anlamıyla yararlanmak için kendi query suite’inizi tanımlayabilirsiniz. Varsayılan security-extended suite’i iyi bir başlangıç noktası olsa da kimi zaman daha fazlasını istersiniz.
mkdir -p .github/codeql
touch .github/codeql/codeql-config.yml
# .github/codeql/codeql-config.yml
name: "Custom Security Configuration"
disable-default-queries: false
queries:
- uses: security-and-quality
- uses: security-extended
query-filters:
- exclude:
tags contain: experimental
- include:
tags contain: security
severity: error
- include:
tags contain: security
severity: warning
paths-ignore:
- "vendor/**"
- "node_modules/**"
- "**/*.test.js"
- "**/*.spec.ts"
- "test/**"
- "tests/**"
- "docs/**"
Bu konfigürasyonu workflow dosyanıza şöyle bağlayın:
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript-typescript
config-file: .github/codeql/codeql-config.yml
paths-ignore ayarı özellikle önemli. Test dosyalarını ve vendor klasörlerini analiz dışında bırakmak hem false positive sayısını düşürür hem de analiz süresini kısaltır. Büyük monorepo’larda bu optimizasyon, analiz süresini yarıya indirebilir.
Gerçek Dünya Senaryosu: Mikro Servis Mimarisi
Diyelim ki birden fazla dil kullanan bir mikro servis mimariniz var. Backend’de Python/Django, frontend’de React/TypeScript, ve bir tane de Go ile yazılmış API gateway’iniz var. Her birini ayrı ayrı analiz etmek yerine bir matrix stratejisi kuralım.
name: "Microservices Security Scan"
on:
push:
branches: [ "main", "release/**" ]
pull_request:
types: [ opened, synchronize, reopened ]
branches: [ "main" ]
env:
CODEQL_THREADS: 4
CODEQL_RAM: 6144
jobs:
changes:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python }}
javascript: ${{ steps.filter.outputs.javascript }}
go: ${{ steps.filter.outputs.go }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
python:
- 'backend/**'
javascript:
- 'frontend/**'
go:
- 'gateway/**'
codeql-analyze:
needs: changes
strategy:
fail-fast: false
matrix:
include:
- language: python
path: backend
condition: ${{ needs.changes.outputs.python == 'true' }}
- language: javascript-typescript
path: frontend
condition: ${{ needs.changes.outputs.javascript == 'true' }}
- language: go
path: gateway
condition: ${{ needs.changes.outputs.go == 'true' }}
name: "CodeQL - ${{ matrix.language }}"
runs-on: ubuntu-latest
if: ${{ matrix.condition }}
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
source-root: ${{ matrix.path }}
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}/service:${{ matrix.path }}"
output: sarif-results
upload: always
- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: sarif-results/${{ matrix.language }}.sarif
category: "/language:${{ matrix.language }}/service:${{ matrix.path }}"
Bu yaklaşımın avantajları:
- Selective scanning: Sadece değişen servisin kodu analiz edilir, gereksiz CI dakikaları harcanmaz
- Parallel execution: Üç dil aynı anda analiz edilir
- Granüler raporlama: Her servis için ayrı SARIF kategorisi, Security tabında filtrelemeyi kolaylaştırır
SARIF Sonuçlarını Dışa Aktarmak ve İşlemek
SARIF (Static Analysis Results Interchange Format) dosyaları JSON tabanlı bir standarttır. Bu dosyaları GitHub Security tab’ında görüntülemenin yanı sıra Jira, Slack veya kendi ticketing sisteminize de gönderebilirsiniz.
- name: Download SARIF artifact
uses: actions/download-artifact@v4
with:
name: sarif-results
- name: Parse and notify critical findings
run: |
python3 << 'EOF'
import json
import sys
import os
sarif_file = "javascript-typescript.sarif"
try:
with open(sarif_file, 'r') as f:
sarif_data = json.load(f)
except FileNotFoundError:
print("SARIF dosyasi bulunamadi, analiz atlanmis olabilir.")
sys.exit(0)
critical_findings = []
high_findings = []
for run in sarif_data.get('runs', []):
tool_name = run.get('tool', {}).get('driver', {}).get('name', 'Unknown')
results = run.get('results', [])
# Rules map olustur
rules = {}
for rule in run.get('tool', {}).get('driver', {}).get('rules', []):
rules[rule['id']] = rule
for result in results:
rule_id = result.get('ruleId', '')
rule_info = rules.get(rule_id, {})
severity = rule_info.get('defaultConfiguration', {}).get('level', 'warning')
location = result.get('locations', [{}])[0]
file_path = location.get('physicalLocation', {}).get('artifactLocation', {}).get('uri', 'unknown')
line = location.get('physicalLocation', {}).get('region', {}).get('startLine', 0)
finding = {
'rule': rule_id,
'message': result.get('message', {}).get('text', ''),
'file': file_path,
'line': line,
'severity': severity
}
if severity == 'error':
critical_findings.append(finding)
elif severity == 'warning':
high_findings.append(finding)
print(f"Kritik bulgular: {len(critical_findings)}")
print(f"Uyari seviyesi bulgular: {len(high_findings)}")
if critical_findings:
print("n=== KRITIK GUVENLIK BULGULARI ===")
for f in critical_findings:
print(f"[{f['rule']}] {f['file']}:{f['line']} - {f['message'][:100]}")
# GitHub Output olarak ayarla
with open(os.environ.get('GITHUB_OUTPUT', '/dev/null'), 'a') as fh:
print(f"critical_count={len(critical_findings)}", file=fh)
sys.exit(1) # Kritik bulgu varsa workflow'u fail et
EOF
Branch Protection ile CodeQL Entegrasyonu
CodeQL sonuçlarını sadece rapor olarak bırakmak yerine pull request’lerin merge edilmesini engellemek için kullanabilirsiniz. Bunun için iki adım gerekiyor:
Birinci adım, workflow’unuzda check’in her zaman sonuç döndürdüğünden emin olun:
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
upload: always
continue-on-error: false
İkinci adım, GitHub repository ayarlarından branch protection rules bölümüne gidin. “Require status checks to pass before merging” seçeneğini aktive edin ve CodeQL check’ini required olarak işaretleyin.
Artık CodeQL bulgu tespit ettiğinde PR merge edilemez hale gelir. Ancak burada dikkat edilmesi gereken bir nokta var: CodeQL sadece yeni eklenen kod için uyarı üretir, var olan açıklara karşı silent kalabilir. Bu nedenle scheduled scan’leri de mutlaka aktif tutun.
Performans Optimizasyonu
Büyük projelerde CodeQL analizi ciddi zaman alabilir. 500k+ satır kod içeren projelerde 2 saati aşan analiz süreleri sık karşılaşılan bir durumdur. Bu süreyi kısaltmak için şunları yapabilirsiniz:
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: java-kotlin
build-mode: manual
# RAM ve thread optimizasyonu
ram: 8192
threads: 0 # 0 = tum CPU corelerini kullan
- name: Cache CodeQL databases
uses: actions/cache@v4
with:
path: ~/.codeql/databases
key: codeql-${{ runner.os }}-${{ hashFiles('**/pom.xml') }}
restore-keys: |
codeql-${{ runner.os }}-
- name: Build Application
run: |
# Sadece main source compile et, test kaynaklarini dahil etme
mvn compile
-pl "!integration-tests,!performance-tests"
-am
-T 1C
-B
Özellikle şu ayarlar kritik:
threads: 0: Tüm CPU çekirdeklerini kullanır, runner’ın kapasitesine göre bu ciddi hızlanma sağlarram: 8192: Büyük projelerde yetersiz bellek analizi yarıda kesebilir, en az 6GB ayrılması önerilir- Test kaynaklarını dışarıda bırakmak: Test kodunu derlemek ve analiz etmek hem zaman hem kaynak israfı,
paths-ignoreile bunu engelleyin
Secret Scanning ile CodeQL’i Tamamlamak
CodeQL güvenlik açıklarını tespit etmekte harika ama hardcoded secret’ları bulma konusunda uzmanlaşmamış. Bu boşluğu doldurmak için workflow’a ek araçlar ekleyebilirsiniz:
secret-scan:
name: Secret Scanning
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Tum commit gecmisi icin
- name: TruffleHog Secret Scan
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --debug --only-verified
- name: Detect Hardcoded Secrets with Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
Bu iki araç birbirini tamamlar:
- TruffleHog: Entropy analizi yaparak rastgele görünen string’leri tespit eder, regex tabanlı kuralları da var
- Gitleaks: Git history’nin tamamını tarar, geçmişte commit edilip sonradan silinen secret’ları bile bulabilir
Sonuç
CodeQL’i CI/CD pipeline’ınıza entegre etmek, güvenlik açıklarını production’a ulaşmadan önce yakalamak için en etkili yöntemlerden biridir. Başlangıç olarak en basit workflow ile başlayın, ekibiniz CodeQL uyarılarına alıştıkça konfigürasyonu derinleştirin.
Pratik önerim şu yönde: İlk iki hafta workflow’u continue-on-error: true ile çalıştırın ve sadece raporlama yapın. Bu sürede hangi bulgu kategorilerinin gerçek tehdit, hangilerinin false positive olduğunu öğrenin. Sonra query filter’larınızı oluşturun ve ancak bundan sonra branch protection rule’larına ekleyin. Ekibi hazırlamadan sert bir kural koymak, güvenlik kültürü yerine direnç yaratır.
Scheduled scan’leri asla ihmal etmeyin. Push tetiklemeli analizler yeni kodu yakalar ama var olan teknik borç için haftalık tam tarama şarttır. Özellikle büyük dependency güncellemelerinden sonra manuel bir tam analiz tetiklemek iyi bir alışkanlıktır.
Son olarak, CodeQL sonuçlarını sadece GitHub Security tabında bırakmayın. SARIF dosyalarını parse ederek Slack’e bildirim göndermek veya Jira ticket açmak, bulguların gerçekten ilgilenilmesini sağlar. Görmediğiniz uyarı, olmayan uyarı kadar değersizdir.
