TL;DR
Supply-Chain-Angriffe wie SolarWinds und XZ Utils beweisen: Wer die CI/CD-Pipeline kompromittiert, kontrolliert den gesamten ausgelieferten Code. Dieser Artikel zeigt, wie Sie GitHub Actions, GitLab CI und Jenkins absichern - konkret mit commit-hash-gepinnten Actions statt mutierbaren Tags, OIDC-basierter AWS-Authentifizierung ohne langlebige Access Keys sowie integrierten Scan-Stufen: Semgrep für SAST, Trivy für Container-Images und Dependabot für Dependency-Updates. Statische Credentials in Pipeline-Konfigurationen gehören eliminiert; stattdessen holen laufzeitgebundene Vault-Integrationen Secrets erst beim Ausführen.
Diese Zusammenfassung wurde KI-gestützt erstellt (EU AI Act Art. 52).
Inhaltsverzeichnis (5 Abschnitte)
Die CI/CD-Pipeline ist das Herzstück moderner Softwareentwicklung - und ein attraktives Ziel für Angreifer. Wer die Pipeline kontrolliert, kontrolliert den Code der in Produktion geht. SolarWinds, 3CX, XZ Utils - Supply-Chain-Angriffe über Build-Systeme sind keine Ausnahme mehr, sondern eine etablierte Angriffskategorie.
Warum CI/CD ein Sicherheitsrisiko ist
Angriffsszenarien auf CI/CD-Systeme:
1. Geheimnisdiebstahl aus Pipeline-Logs:
→ Entwickler printed versehentlich: echo $PROD_API_KEY
→ CI-Log ist 90 Tage öffentlich sichtbar (bei Public Repos!)
→ Oder: Secrets in Umgebungsvariablen landen in Core Dumps
2. Dependency Confusion / Typosquatting:
→ npm install firma-internal-lib
→ Angreifer registriert "firma-internal-lib" auf npm.org mit höherer Version
→ npm bevorzugt öffentliches Registry → Malware-Paket installiert
→ SolarWinds-Prinzip: über Lieferant einschleusen
3. Kompromittierter Third-Party Action:
GitHub Actions: uses: actions/checkout@v4
→ Wenn "actions/checkout" Repository kompromittiert wird...
→ ODER: @v4 Tag zeigt auf beliebigen Commit (Tags sind mutable!)
→ Schutz: uses: actions/checkout@SHA (commit hash, immutable!)
4. CI/CD-Server selbst kompromittiert:
→ Jenkins ohne Authentifizierung: öffentlich erreichbar
→ Angreifer baut bösartige Pipeline → Credentials aus Vault gestohlen
→ Code-Injection in Build-Artifacts
5. Überprivilegierte Service Accounts:
→ CI-Token hat Schreibrechte auf ALLE Repositories
→ Oder: Deployment-Account hat Production-Admin-Rechte
→ Least-Privilege verletzt → Blast Radius riesig
Secrets Management in Pipelines
Anti-Pattern - was NIEMALS tun:
# FALSCH: Direktes Einchecken in Pipeline-Config
env:
DATABASE_URL: "postgresql://user:password123@db.firma.de/prod"
AWS_ACCESS_KEY: "AKIA..."
# FALSCH: Hartcoded in Script
aws s3 cp output/ s3://bucket/ --profile myprofile
# (Credentials im ~/.aws/credentials im Build-Agent)
# FALSCH: In Test-Config-Datei committed
// test/config.js
const apiKey = "sk-prod-12345...";
---
Richtig: GitHub Actions Secrets
GitHub Repository → Settings → Secrets and variables → Actions
.github/workflows/deploy.yml:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to AWS
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws s3 sync ./dist s3://my-bucket
OIDC statt statische Credentials (Best Practice!):
→ AWS OIDC Identity Provider: kein AWS Access Key mehr nötig!
→ GitHub Actions beweist Identität via OIDC Token
→ AWS gibt temporäre Credentials aus (für 1 Stunde)
.github/workflows/deploy-oidc.yml:
jobs:
deploy:
permissions:
id-token: write # OIDC Token anfordern
contents: read
steps:
- name: Configure AWS Credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: eu-central-1
- name: Deploy
run: aws s3 sync ./dist s3://my-bucket
Vorteil: kein langlebiger AWS-Key, automatisch rotierend, GitHub-Repository-gebunden
---
HashiCorp Vault Integration:
# Pipeline holt Secrets zur Laufzeit:
- name: Get Secrets from Vault
id: secrets
uses: hashicorp/vault-action@v3
with:
url: https://vault.firma.de
method: jwt
role: ci-deploy-role
secrets: |
secret/data/production/db password | DB_PASSWORD ;
secret/data/production/api key | API_KEY
- name: Deploy
env:
DB_PASSWORD: ${{ steps.secrets.outputs.DB_PASSWORD }}
run: ./deploy.sh
Security Scanning in der Pipeline
SAST (Static Application Security Testing):
Semgrep - schnell, open source:
# GitHub Actions:
- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: >
p/owasp-top-ten
p/secrets
p/dockerfile
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
# Lokal testen:
semgrep --config "p/owasp-top-ten" src/
Bandit (Python):
pip install bandit
bandit -r src/ -ll -ii # Nur HIGH Confidence, HIGH Severity
ESLint Security Plugin (JavaScript/TypeScript):
npm install --save-dev eslint-plugin-security
# .eslintrc.json:
{
"plugins": ["security"],
"extends": ["plugin:security/recommended"]
}
---
SCA (Software Composition Analysis) - Dependency Scanning:
Dependabot (GitHub native, kostenlos):
# .github/dependabot.yml:
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "lodash" # Falls bekannte Breaking Changes
versions: ["4.x"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
Trivy (Aqua Security, open source):
# Container Image scannen:
trivy image --severity CRITICAL,HIGH --exit-code 1 \
--ignore-unfixed my-app:latest
# Filesystem scannen (vor Build):
trivy fs --severity CRITICAL,HIGH --exit-code 1 .
# GitHub Actions Integration:
- name: Scan Image with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: 'my-app:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '1'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
- name: Upload Trivy Results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
---
Secrets Scanning:
GitLeaks (lokal + CI):
# In GitHub Actions:
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
# Lokal (pre-commit Hook):
# .pre-commit-config.yaml:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
truffleHog:
# Scannt git-History auf Secrets:
trufflehog git file://. --only-verified --json
GitHub Secret Scanning (automatisch für Public Repos):
→ Erkennt > 100 Secret-Typen (AWS Keys, Stripe, GitHub PATs)
→ Bei Enterprise: auch für Private Repos aktivierbar
→ Push Protection: Blockiert Push wenn Secret erkannt
→ Settings → Code security → Secret scanning → Push protection: Enable
Sichere GitHub Actions Konfiguration
Sicherheitsregeln für GitHub Actions:
1. Commit-Hash statt Tags (immutable references):
# UNSICHER - Tag kann überschrieben werden:
uses: actions/checkout@v4
# SICHER - SHA ist immutable:
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
Tool: pin-github-action (automatisches Pinnen)
2. Minimale Permissions:
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read # Nur lesen, nicht schreiben
# packages: NICHT angeben wenn nicht nötig
# id-token: NICHT angeben wenn kein OIDC
steps: ...
Globale Default: Einschränken!
# .github/workflows/*.yml - oder in Repository Settings:
permissions:
contents: read
3. Third-Party Actions minimieren:
→ Eigene Actions in .github/actions/ (kein Third-Party-Vertrauen)
→ Wenn Third-Party: nur verifizierte, etablierte Actions
→ Allowlist in Organization-Settings: nur erlaubte Actions
4. Selbst-gehostete Runner absichern:
→ Kein Shared Runner für privilegierte Deployment-Pipelines
→ Ephemeral Runner (pro Job eine neue VM → keine Persistence)
→ Netzwerkisolation: Runner hat nur Zugriff auf benötigte Systeme
→ KEINE self-hosted Runner für Public Repositories (RCE-Risiko!)
5. Workflow-Schutz:
# .github/workflows/protected-deploy.yml:
on:
workflow_dispatch: # Nur manuell auslösbar
inputs:
environment:
type: choice
options: [staging, production]
required: true
environment:
name: production # Requires approval!
url: https://a7.de
Environment: Settings → Environments → production →
"Required reviewers": Senior DevOps, CTO
"Wait timer": 5 Minuten (Review-Zeit)
"Restrict to protected branches": main
---
GitLab CI Sicherheit:
# .gitlab-ci.yml
# Variable Protection:
variables:
DEPLOY_TOKEN: $CI_DEPLOY_TOKEN # Aus Protected CI/CD Variables
deploy_production:
stage: deploy
only:
- main # Nur aus main-Branch
when: manual # Manuelle Bestätigung
environment:
name: production
script:
- echo "Deploying to production..."
# Secrets aus GitLab CI/CD Variables (Protected + Masked!)
- aws s3 sync dist/ s3://prod-bucket
# Masked + Protected Variables in GitLab:
# Settings → CI/CD → Variables → Add Variable
# Protected: nur auf protected branches verfügbar
# Masked: nicht in Logs sichtbar
Supply Chain Security
SLSA Framework (Supply Chain Levels for Software Artifacts):
Level 1 (Grundlegende Reproduzierbarkeit):
→ Build-Prozess dokumentiert, automatisiert
→ Provenance-Metadaten verfügbar
Level 2 (Versionierter Build-Service):
→ Build läuft auf verwaltetem CI-System (nicht lokal!)
→ Provenance authentifiziert
Level 3 (Sichere Build-Platform):
→ Isolation: Build hat keinen Einfluss auf Build-Script
→ Alle Builds über CI (kein manueller Build möglich!)
Level 4 (Maximale Sicherheit):
→ Zweiäugige Reviews für alle Code-Änderungen
→ Hermetischer, reproduzierbarer Build
---
Software Bill of Materials (SBOM):
Was ist ein SBOM?
→ Vollständige Liste aller Komponenten in einer Software
→ Inklusive: direkte + transitive Dependencies, Versionen, Lizenzen
→ Standard-Formate: CycloneDX (JSON), SPDX (SPDX-JSON)
SBOM generieren:
# syft (Anchore):
syft my-app:latest -o cyclonedx-json > sbom.json
# grype (Schwachstellenabgleich mit SBOM):
grype sbom:./sbom.json
# npm:
npm sbom --sbom-format cyclonedx > sbom.json
# GitHub: automatisches SBOM für Releases aktivieren
# Repository Settings → Code security → Dependency graph → SBOM
Warum SBOM wichtig ist:
Log4Shell 2021: Unternehmen hatten keine Ahnung ob Log4j in ihrer Software
Mit SBOM: in 5 Minuten prüfbar ob betroffen
Ohne SBOM: 3-5 Arbeitstage manuelle Suche
---
Sicherheits-Gates in der Pipeline (fail fast!):
Empfohlene Pipeline-Struktur:
Stage 1 - Code Quality (< 2 Minuten):
→ Linting, Formatierung
→ Unit Tests
→ Secrets Scanning (gitleaks)
Stage 2 - Security Scanning (< 10 Minuten):
→ SAST (Semgrep, CodeQL)
→ SCA (trivy fs, npm audit)
→ License Check (FOSSA, SPDX)
Stage 3 - Build & Image Scan (< 15 Minuten):
→ Docker Image Build
→ Image Scan (trivy image)
→ SBOM Generierung
→ Image Signing (Cosign)
Stage 4 - Deploy Staging (automatisch):
→ DAST (OWASP ZAP, Nuclei)
→ Integration Tests
Stage 5 - Deploy Production (manuell + Approval):
→ Nur nach Stage-4-Erfolg
→ Required Reviewer Approval
→ Canary-Deployment optional
Pipeline-Regel: JEDER Security-Gate-Fehler → Pipeline fehlgeschlagen → KEIN Deployment!
DevSecOps ist keine Verlangsamung der Entwicklung - es ist die Vermeidung teurer Sicherheitsvorfälle in Produktion. AWARE7 analysiert CI/CD-Pipelines auf Schwachstellen, implementiert Security-Gates und schult Entwicklungsteams in sicherer Softwareentwicklung.
DevSecOps-Beratung anfragen | Penetrationstest Web-Applikationen
Nächster Schritt
Unsere zertifizierten Sicherheitsexperten beraten Sie zu den Themen aus diesem Artikel — unverbindlich und kostenlos.
Kostenlos · 30 Minuten · Unverbindlich
