Jenkins Pipeline Integration
Jenkins Pipeline Integration
Jenkins remains popular in enterprise environments. Here's a comprehensive Jenkinsfile implementing container security scanning:
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
}
environment {
DOCKER_REGISTRY = 'registry.company.com'
IMAGE_NAME = "${env.JOB_NAME.toLowerCase()}"
IMAGE_TAG = "${DOCKER_REGISTRY}/${IMAGE_NAME}:${env.BUILD_NUMBER}"
TRIVY_VERSION = '0.45.0'
SNYK_TOKEN = credentials('snyk-api-token')
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: "git rev-parse --short HEAD",
returnStdout: true
).trim()
}
}
}
stage('Build Image') {
steps {
script {
docker.build(IMAGE_TAG,
"--build-arg BUILD_NUMBER=${env.BUILD_NUMBER} " +
"--build-arg GIT_COMMIT=${env.GIT_COMMIT_SHORT} " +
"--label build.number=${env.BUILD_NUMBER} " +
"--label git.commit=${env.GIT_COMMIT_SHORT} " +
".")
}
}
}
stage('Security Scans') {
parallel {
stage('Trivy Scan') {
steps {
script {
// Run Trivy in Docker
def trivyExitCode = sh(
script: """
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${WORKSPACE}:/workspace \
aquasec/trivy:${TRIVY_VERSION} \
image --format json \
--output /workspace/trivy-report.json \
${IMAGE_TAG}
""",
returnStatus: true
)
// Parse results
def trivyReport = readJSON file: 'trivy-report.json'
def criticalCount = 0
def highCount = 0
trivyReport.Results.each { result ->
result.Vulnerabilities?.each { vuln ->
if (vuln.Severity == 'CRITICAL') criticalCount++
if (vuln.Severity == 'HIGH') highCount++
}
}
echo "Trivy found ${criticalCount} CRITICAL and ${highCount} HIGH vulnerabilities"
// Generate HTML report
publishHTML(target: [
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'trivy-report.json',
reportName: 'Trivy Security Report'
])
if (criticalCount > 0) {
error("Build failed due to ${criticalCount} critical vulnerabilities")
}
}
}
}
stage('Snyk Scan') {
steps {
script {
sh """
docker run --rm \
-e SNYK_TOKEN=${SNYK_TOKEN} \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${WORKSPACE}:/project \
snyk/snyk:docker \
container test ${IMAGE_TAG} \
--json > snyk-report.json || true
"""
def snykReport = readJSON file: 'snyk-report.json'
if (snykReport.vulnerabilities) {
echo "Snyk found ${snykReport.vulnerabilities.size()} vulnerabilities"
// Create issues for high/critical vulnerabilities
snykReport.vulnerabilities.findAll {
it.severity in ['high', 'critical']
}.each { vuln ->
echo "High/Critical: ${vuln.title} in ${vuln.packageName}"
}
}
}
}
}
stage('License Check') {
steps {
script {
sh """
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:${TRIVY_VERSION} \
image --license-full \
--format json \
${IMAGE_TAG} > license-report.json
"""
def licenses = readJSON file: 'license-report.json'
// Check for problematic licenses
def problematicLicenses = ['GPL', 'AGPL', 'LGPL']
def found = []
licenses.Results?.each { result ->
result.Licenses?.each { license ->
if (problematicLicenses.any {
license.Name?.contains(it)
}) {
found.add(license)
}
}
}
if (found.size() > 0) {
echo "WARNING: Found ${found.size()} potentially problematic licenses"
found.each { echo " - ${it.Name}" }
}
}
}
}
}
}
stage('Generate SBOM') {
steps {
script {
sh """
# Generate SBOM using Syft
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
anchore/syft:latest \
${IMAGE_TAG} \
-o cyclonedx-json > sbom.json
"""
archiveArtifacts artifacts: 'sbom.json',
fingerprint: true
}
}
}
stage('Security Gate') {
steps {
script {
def proceed = input(
message: 'Security scan complete. Review results and proceed?',
parameters: [
booleanParam(
name: 'OVERRIDE_SECURITY',
defaultValue: false,
description: 'Override security failures?'
)
]
)
if (!proceed && env.BRANCH_NAME == 'main') {
error("Security gate failed - deployment blocked")
}
}
}
}
stage('Push Image') {
when {
branch pattern: "(main|develop)", comparator: "REGEXP"
}
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-creds') {
docker.image(IMAGE_TAG).push()
docker.image(IMAGE_TAG).push('latest')
}
}
}
}
}
post {
always {
// Clean up
sh "docker rmi ${IMAGE_TAG} || true"
// Archive reports
archiveArtifacts artifacts: '*-report.json',
allowEmptyArchive: true,
fingerprint: true
// Send notifications
emailext(
subject: "Security Scan Results: ${currentBuild.fullDisplayName}",
body: '''${FILE,path="security-summary.md"}''',
to: '${DEFAULT_RECIPIENTS}',
attachmentsPattern: '*-report.json'
)
}
failure {
slackSend(
color: 'danger',
message: "Security scan failed for ${env.JOB_NAME} #${env.BUILD_NUMBER}"
)
}
}
}