Security Automation in CI/CD Pipelines
Security Automation in CI/CD Pipelines
Automation enables consistent security enforcement without slowing development velocity. Security automation should be transparent when possible, providing clear feedback when intervention is required. Progressive automation allows teams to adopt security practices gradually, building confidence before enforcing strict policies.
Pipeline security gates must balance security with development velocity. Early pipeline stages should run fast security checks providing immediate feedback. Later stages can run comprehensive scans. Parallel execution of security checks minimizes pipeline duration. Clear error messages help developers understand and fix security issues quickly.
// Example: Jenkins pipeline with progressive security checks
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'registry.company.com'
IMAGE_NAME = "${DOCKER_REGISTRY}/${env.JOB_NAME}:${env.BUILD_ID}"
SECURITY_THRESHOLD = credentials('security-thresholds')
}
stages {
stage('Quick Security Checks') {
parallel {
stage('Dockerfile Lint') {
steps {
script {
sh '''
# Hadolint for Dockerfile best practices
docker run --rm -i hadolint/hadolint < Dockerfile
'''
}
}
}
stage('Secrets Scan') {
steps {
script {
sh '''
# Scan for hardcoded secrets
docker run --rm -v $(pwd):/src \
trufflesecurity/trufflehog:latest \
filesystem /src --json | \
jq -e '.[] | select(.verified == true)' && \
echo "SECRETS FOUND!" && exit 1 || \
echo "No secrets detected"
'''
}
}
}
stage('SAST Quick Scan') {
steps {
script {
sh '''
# Quick static analysis
docker run --rm -v $(pwd):/src \
semgrep/semgrep-agent:latest \
--config=auto --error
'''
}
}
}
}
}
stage('Build Image') {
steps {
script {
// Build with security labels
sh """
docker build \
--label security.scan.required=true \
--label build.id=${env.BUILD_ID} \
--label vcs.ref=${env.GIT_COMMIT} \
--label build.date=\$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t ${IMAGE_NAME} .
"""
}
}
}
stage('Comprehensive Security Analysis') {
parallel {
stage('Vulnerability Scan') {
steps {
script {
sh """
# Trivy scan with cache
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${WORKSPACE}/trivy-cache:/tmp/trivy-cache \
aquasec/trivy:latest image \
--cache-dir /tmp/trivy-cache \
--format json \
--output vulnerability-report.json \
${IMAGE_NAME}
# Process results
python3 process_vulnerabilities.py \
--report vulnerability-report.json \
--thresholds ${SECURITY_THRESHOLD}
"""
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'vulnerability-report.html',
reportName: 'Vulnerability Scan Report'
])
}
}
}
stage('License Compliance') {
steps {
script {
sh """
# License scanning
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
licensefinder/license_finder \
--docker-image ${IMAGE_NAME}
"""
}
}
}
stage('Security Benchmarks') {
steps {
script {
sh """
# CIS Docker Benchmark
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
docker/docker-bench-security \
-i ${IMAGE_NAME}
"""
}
}
}
}
}
stage('Security Approval') {
when {
expression {
return env.BRANCH_NAME == 'main' ||
env.BRANCH_NAME == 'develop'
}
}
steps {
script {
// Automated approval based on scan results
def approved = sh(
script: '''
python3 security_approval.py \
--vulnerability-report vulnerability-report.json \
--policy-file security-policy.yaml
''',
returnStatus: true
) == 0
if (!approved) {
// Request manual approval for policy violations
input message: 'Security policy violations detected. Approve deployment?',
parameters: [
string(name: 'JUSTIFICATION',
description: 'Provide justification for override'),
string(name: 'APPROVER_EMAIL',
description: 'Your email for audit trail')
]
// Log override
sh """
echo '${env.BUILD_ID},${params.APPROVER_EMAIL},${params.JUSTIFICATION}' \
>> security-overrides.log
"""
}
}
}
}
stage('Sign and Push') {
steps {
script {
// Sign image with Cosign
sh """
# Generate ephemeral keys
cosign generate-key-pair
# Sign image
cosign sign --key cosign.key ${IMAGE_NAME}
# Attach SBOM
syft ${IMAGE_NAME} -o spdx-json > sbom.json
cosign attach sbom --sbom sbom.json ${IMAGE_NAME}
# Push to registry
docker push ${IMAGE_NAME}
"""
}
}
}
}
post {
always {
// Security metrics collection
sh '''
# Collect and send metrics
python3 collect_security_metrics.py \
--build-id ${BUILD_ID} \
--duration ${currentBuild.durationString} \
--status ${currentBuild.result}
'''
// Clean up
sh 'docker rmi ${IMAGE_NAME} || true'
}
failure {
// Security incident notification
emailext (
subject: "Security Check Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
body: '''${SCRIPT, template="security-failure-email.template"}''',
to: '${SECURITY_TEAM_EMAIL}'
)
}
}
}