CI/CD Pipeline Security: DevSecOps Yaklaşımı
Modern yazılım geliştirme süreçlerinde güvenlik, artık bir afterthought değil, development lifecycle'ının her aşamasına entegre edilmesi gereken temel bir bileşendir. DevSecOps yaklaşımı ile güvenlik kontrollerini CI/CD pipeline'larına nasıl entegre edeceğinizi ve automated security testing stratejileri oluşturmayı öğreneceksiniz.
İçindekiler
- DevSecOps Temelleri
- Secure CI/CD Pipeline Tasarımı
- Static Application Security Testing (SAST)
- Dynamic Application Security Testing (DAST)
- Container Security
- Infrastructure Security
- Secrets Management
- Compliance ve Audit
DevSecOps Temelleri {#devsecops-temelleri}
Shift-Left Security Prensibi
DevSecOps'un kalbi "shift-left" yaklaşımındadır - güvenliği development cycle'ın en başına taşımak:
graph LR
A[Plan] --> B[Code]
B --> C[Build]
C --> D[Test]
D --> E[Release]
E --> F[Deploy]
F --> G[Operate]
G --> H[Monitor]
S1[Security Planning] --> A
S2[Secure Coding] --> B
S3[Security Scanning] --> C
S4[Security Testing] --> D
S5[Security Validation] --> E
S6[Secure Deployment] --> F
S7[Runtime Security] --> G
S8[Security Monitoring] --> H
DevSecOps Kültürü
# devsecops-principles.yaml
DevSecOps_Culture:
shared_responsibility:
- "Everyone is responsible for security"
- "Security is not a bottleneck"
- "Automate security wherever possible"
collaboration:
- "Dev, Ops, and Sec teams work together"
- "Security requirements are clear and actionable"
- "Fast feedback loops for security issues"
automation:
- "Security tests run automatically"
- "Vulnerability remediation is automated where possible"
- "Compliance checks are part of the pipeline"
Secure CI/CD Pipeline Tasarımı {#secure-cicd-pipeline}
GitHub Actions Security Pipeline
# .github/workflows/security-pipeline.yml
name: Security Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Pre-commit security checks
pre-commit-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run TruffleHog OSS
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: main
head: HEAD
extra_args: --debug --only-verified
- name: Run git-secrets
run: |
git clone https://github.com/awslabs/git-secrets.git
cd git-secrets && make install
git secrets --register-aws
git secrets --scan
# Static Code Analysis
sast:
runs-on: ubuntu-latest
needs: pre-commit-security
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
p/kubernetes
p/dockerfile
- name: Run CodeQL
uses: github/codeql-action/init@v2
with:
languages: 'go, javascript, python'
queries: security-extended,security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
# Dependency Vulnerability Scanning
dependency-scan:
runs-on: ubuntu-latest
needs: pre-commit-security
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
# Container Security
container-security:
runs-on: ubuntu-latest
needs: [sast, dependency-scan]
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} .
- name: Run Trivy container scan
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'sarif'
output: 'container-trivy-results.sarif'
- name: Run Dockle security scanner
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
goodwithtech/dockle:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
- name: Run Docker Bench Security
run: |
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker/docker-bench-security
# Infrastructure as Code Security
iac-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform,kubernetes,dockerfile
output_format: sarif
output_file_path: checkov-results.sarif
- name: Run TFSec
uses: aquasecurity/tfsec-action@v1.0.0
with:
soft_fail: true
- name: Upload Checkov scan results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: checkov-results.sarif
# Security Policy Validation
policy-validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Open Policy Agent Tests
run: |
curl -L -o opa https://openpolicyagent.org/downloads/v0.57.0/opa_linux_amd64_static
chmod 755 ./opa
./opa fmt --diff policies/
./opa test policies/
- name: Run Conftest
run: |
docker run --rm -v $(pwd):/project openpolicyagent/conftest verify --policy policies/ k8s/
Static Application Security Testing (SAST) {#sast-implementation}
Semgrep Konfigürasyonu
# .semgrep.yml
rules:
- id: hardcoded-secrets
patterns:
- pattern-either:
- pattern: password = "..."
- pattern: api_key = "..."
- pattern: secret_key = "..."
- pattern: private_key = "..."
message: "Hardcoded secret detected"
languages: [python, javascript, go]
severity: ERROR
- id: sql-injection
patterns:
- pattern-either:
- pattern: db.exec("SELECT * FROM users WHERE id = " + $ID)
- pattern: db.query("DELETE FROM " + $TABLE + " WHERE id = " + $ID)
message: "Potential SQL injection vulnerability"
languages: [python, javascript, go]
severity: ERROR
- id: command-injection
patterns:
- pattern-either:
- pattern: exec.Command($CMD, ...)
- pattern: os.system($CMD)
message: "Potential command injection vulnerability"
languages: [go, python]
severity: WARNING
SonarQube Entegrasyonu
# sonarqube-analysis.yml
name: SonarQube Analysis
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run tests with coverage
run: |
go test -coverprofile=coverage.out -covermode=atomic ./...
go tool cover -func coverage.out
- name: SonarQube Scan
uses: sonarqube-quality-gate-action@master
with:
scanMetadataReportFile: target/sonar/report-task.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
Dynamic Application Security Testing (DAST) {#dast-implementation}
OWASP ZAP Integration
# dast-testing.yml
name: DAST Security Testing
on:
deployment_status
jobs:
dast:
runs-on: ubuntu-latest
if: github.event.deployment_status.state == 'success'
steps:
- name: Run OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: ${{ github.event.deployment_status.target_url }}
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: Run OWASP ZAP Full Scan
uses: zaproxy/action-full-scan@v0.4.0
with:
target: ${{ github.event.deployment_status.target_url }}
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a -j'
allow_issue_writing: false
- name: Archive ZAP Results
uses: actions/upload-artifact@v3
with:
name: zap-results
path: report_html.html
Custom Security Tests
# security_tests.py
import requests
import pytest
from urllib.parse import urljoin
class TestSecurityHeaders:
def setup_method(self):
self.base_url = "https://your-app.com"
def test_security_headers(self):
"""Test for essential security headers"""
response = requests.get(self.base_url)
headers = response.headers
# Test for Security Headers
assert 'X-Frame-Options' in headers
assert 'X-Content-Type-Options' in headers
assert 'X-XSS-Protection' in headers
assert 'Strict-Transport-Security' in headers
assert 'Content-Security-Policy' in headers
# Verify HSTS configuration
assert 'max-age' in headers.get('Strict-Transport-Security', '')
# Verify CSP is not too permissive
csp = headers.get('Content-Security-Policy', '')
assert 'unsafe-eval' not in csp
assert 'unsafe-inline' not in csp or 'nonce-' in csp
def test_no_sensitive_data_exposure(self):
"""Test for sensitive data in responses"""
endpoints = ['/api/health', '/api/status', '/']
for endpoint in endpoints:
response = requests.get(urljoin(self.base_url, endpoint))
content = response.text.lower()
# Check for sensitive data
sensitive_patterns = [
'password', 'secret', 'key', 'token',
'api_key', 'private_key', 'credential'
]
for pattern in sensitive_patterns:
assert pattern not in content, f"Sensitive data '{pattern}' found in {endpoint}"
def test_authentication_endpoints(self):
"""Test authentication security"""
# Test login endpoint exists and requires HTTPS
login_response = requests.get(urljoin(self.base_url, '/api/auth/login'))
assert login_response.status_code in [200, 405] # Should exist but may not accept GET
# Test for rate limiting on auth endpoints
auth_endpoint = urljoin(self.base_url, '/api/auth/login')
responses = []
for i in range(10):
resp = requests.post(auth_endpoint, json={'username': 'test', 'password': 'test'})
responses.append(resp.status_code)
# Should have some rate limiting after multiple failed attempts
assert 429 in responses or 403 in responses, "No rate limiting detected on auth endpoint"
Container Security {#container-security}
Secure Dockerfile Best Practices
# Secure Dockerfile example
FROM node:18-alpine as builder
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Set working directory
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY . .
# Build application
RUN npm run build
# Production image
FROM node:18-alpine as runner
# Install security updates
RUN apk upgrade --no-cache
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
# Copy built application
COPY --from=builder --chown=nextjs:nodejs /app ./
# Remove unnecessary files
RUN rm -rf .git* README.md *.md
# Set security headers and configurations
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# Use non-root user
USER nextjs
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
# Expose port
EXPOSE 3000
CMD ["npm", "start"]
Container Security Scanning
# container-security-scan.yml
name: Container Security Scan
on:
push:
branches: [ main ]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
- name: Run Grype vulnerability scanner
uses: anchore/scan-action@v3
with:
image: 'myapp:${{ github.sha }}'
fail-build: true
severity-cutoff: high
- name: Run Syft SBOM generator
uses: anchore/sbom-action@v0
with:
image: 'myapp:${{ github.sha }}'
format: spdx-json
output-file: sbom.spdx.json
- name: Upload SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.spdx.json
Infrastructure Security {#infrastructure-security}
Terraform Security Scanning
# security-policies.tf
# Security group with restrictive rules
resource "aws_security_group" "web" {
name_description = "Web server security group"
vpc_id = aws_vpc.main.id
# Only allow HTTPS inbound
ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Allow HTTP redirect to HTTPS
ingress {
description = "HTTP redirect"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# Restrictive outbound rules
egress {
description = "HTTPS outbound"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "DNS"
from_port = 53
to_port = 53
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "web-sg"
Environment = var.environment
}
}
# S3 bucket with security configurations
resource "aws_s3_bucket" "app_data" {
bucket = "myapp-data-${random_id.bucket_suffix.hex}"
}
resource "aws_s3_bucket_versioning" "app_data" {
bucket = aws_s3_bucket.app_data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_encryption" "app_data" {
bucket = aws_s3_bucket.app_data.id
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
resource "aws_s3_bucket_public_access_block" "app_data" {
bucket = aws_s3_bucket.app_data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
OPA Policy Validation
# policies/security.rego
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Pod"
input.request.object.spec.containers[_].securityContext.runAsRoot == true
msg := "Containers must not run as root"
}
deny[msg] {
input.request.kind.kind == "Pod"
not input.request.object.spec.containers[_].securityContext.readOnlyRootFilesystem
msg := "Containers must have read-only root filesystem"
}
deny[msg] {
input.request.kind.kind == "Pod"
input.request.object.spec.containers[_].securityContext.privileged == true
msg := "Privileged containers are not allowed"
}
deny[msg] {
input.request.kind.kind == "Pod"
input.request.object.spec.containers[_].image
not starts_with(input.request.object.spec.containers[_].image, "myregistry.com/")
msg := "Images must come from approved registry"
}
Secrets Management {#secrets-management}
Kubernetes Secrets Management
# external-secrets.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
namespace: production
spec:
provider:
vault:
server: "https://vault.company.com:8200"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "demo"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: database-url
remoteRef:
key: myapp
property: database_url
- secretKey: api-key
remoteRef:
key: myapp
property: api_key
CI/CD Secrets Security
# secrets-scanning.yml
name: Secrets Detection
on: [push, pull_request]
jobs:
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run TruffleHog
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
- name: Run GitLeaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
- name: Run detect-secrets
run: |
pip install detect-secrets
detect-secrets scan --all-files --baseline .secrets.baseline
detect-secrets audit .secrets.baseline
Compliance ve Audit {#compliance-audit}
Security Compliance Pipeline
# compliance-check.yml
name: Compliance Check
on:
schedule:
- cron: '0 2 * * 1' # Weekly on Mondays
workflow_dispatch:
jobs:
compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run CIS Kubernetes Benchmark
run: |
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl wait --for=condition=complete job/kube-bench --timeout=300s
kubectl logs job/kube-bench > kube-bench-report.txt
- name: Run Falco Security Monitoring
run: |
docker run --rm -v /var/run/docker.sock:/host/var/run/docker.sock \
-v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro \
-v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro \
falcosecurity/falco:latest --validate /etc/falco/falco_rules.yaml
- name: Generate Security Report
run: |
python3 scripts/generate-security-report.py \
--kube-bench kube-bench-report.txt \
--trivy trivy-results.sarif \
--output security-compliance-report.json
- name: Upload Compliance Report
uses: actions/upload-artifact@v3
with:
name: compliance-report
path: security-compliance-report.json
Audit Logging Konfigürasyonu
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all security-related events
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "configmaps"]
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# Log all authentication events
- level: Request
users: ["system:anonymous"]
namespaces: ["kube-system", "default"]
# Log all privileged operations
- level: RequestResponse
users: ["system:admin"]
# Log resource modifications
- level: Request
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "apps"
resources: ["deployments", "daemonsets", "statefulsets"]
Sonuç
DevSecOps yaklaşımı ile güvenliği CI/CD pipeline'ınıza entegre etmek, modern yazılım geliştirme süreçlerinin vazgeçilmez bir parçasıdır. Bu rehberde ele aldığımız konular:
- Shift-Left Security: Güvenliği development cycle'ın başına taşımak
- Automated Security Testing: SAST, DAST ve container scanning
- Infrastructure Security: IaC security ve policy validation
- Secrets Management: Güvenli secret yönetimi ve rotation
- Compliance: Automated compliance checking ve audit logging
Bu stratejileri uygulayarak, güvenli, hızlı ve sürdürülebilir software delivery pipeline'ları oluşturabilirsiniz.
TekTık Yazılım olarak, DevSecOps implementasyonu ve güvenli CI/CD pipeline tasarımı konularında profesyonel danışmanlık hizmeti vermekteyiz. İletişim sayfamızdan bizimle iletişime geçebilirsiniz.