Files
docker-nexus3/Jenkinsfile-sbom-release
2024-09-12 00:53:55 +02:00

214 lines
9.3 KiB
Plaintext

/*
* Copyright (c) 2011-present Sonatype, Inc. All rights reserved.
* Includes the third-party code listed at http://links.sonatype.com/products/clm/attributions.
* "Sonatype" is a trademark of Sonatype, Inc.
*/
@Library(['private-pipeline-library', 'jenkins-shared']) _
import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
IQ_URL_BASE = "https://sonatype.sonatype.app/platform"
REPO_BASE_URL = "https://repo.sonatype.com/service/rest"
TARGET_REPO_NAME = "sonatype-sboms"
SBOM_DEPLOYER_CREDENTIALS = "sonatype-sbom-deployer"
REDHAT_SBOM_REPO_URL_BASE = "https://access.redhat.com/security/data/sbom/beta"
REDHAT_CONTAINER_API_URL_BASE = "https://catalog.redhat.com/api/containers/v1"
CYCLONEDX_VERSION = "1.5"
SPDXMERGE_VERSION_TAG = "v0.2.0"
NEXUS3_REPORT_BY_TAG = [
"^(\\d+\\.\\d+\\.\\d+)(-java\\d+)?-alpine\$" : "docker-nexus3-alpine",
"^(\\d+\\.\\d+\\.\\d+)(-java\\d+)?(-ubi)?\$" : "docker-nexus3"
]
DOCKER_NEXUS_IMAGE_NAME = "docker-all.repo.sonatype.com/sonatype/nexus3"
DEFAULT_NEXUS3_REPORT = "docker-nexus3"
properties([
parameters([
string(name: 'docker_nexus3_tag', defaultValue: '',
description: 'NXRM Docker image tag. The result SBOMs will be tagged with this version.')
])
])
def getComponentSbom(String buildDir, String componentName, String componentVersion) {
def componentId = getComponentInfo(componentName).applications[0].id
withCredentials([usernamePassword(credentialsId: 'jenkins-saas-service-acct', usernameVariable: 'IQ_USER', passwordVariable: 'IQ_PASSWORD')]) {
def formats = ['spdx', 'cyclonedx']
formats.each { format ->
def urlPath = format == 'spdx' ? "spdx/${componentId}/stages/release?format=json" : "cycloneDx/${CYCLONEDX_VERSION}/${componentId}/stages/release"
sh "curl -s -L -u \$IQ_USER:\$IQ_PASSWORD -o '${buildDir}/${format}/${componentName}-${componentVersion}-${format}.json' -X GET -H 'Accept: application/json' '${IQ_URL_BASE}/api/v2/${urlPath}'"
}
}
}
def getUbiImageSbom(String buildDir, String ubiMinimalName, String ubiMinimalVersion) {
// Get ubi-minimal SBOM (as RedHat SBOM repo is still in beta, this has to be optional)
def httpStatus = sh(
script: "curl -s -w \"%{http_code}\" \
-X GET ${REDHAT_SBOM_REPO_URL_BASE}/spdx/${ubiMinimalName}.json.bz2 \
-o '${buildDir}/spdx/ubi-minimal-${ubiMinimalVersion}.json.bz2'",
returnStdout: true)
if (!"200".equals(httpStatus)) {
echo """ Error ${httpStatus}: Could not load UBI minimal SBOM version ${ubiMinimalVersion}.
This could happen because RedHat SBOM repo is still in beta. UBI SBOM will be skipped.
Please visit https://access.redhat.com/security/data for further information.
"""
sh "rm '${buildDir}/spdx/ubi-minimal-${ubiMinimalVersion}.json.bz2'"
return false
} else {
sh "(cd ${buildDir}/spdx && bzip2 -d 'ubi-minimal-${ubiMinimalVersion}.json.bz2')"
return true
}
}
def getComponentInfo(String componentName) {
def jsonSlurper = new JsonSlurper()
def response = null
withCredentials([
usernamePassword(
credentialsId: 'jenkins-saas-service-acct',
usernameVariable: 'IQ_USER',
passwordVariable: 'IQ_PASSWORD')
]) {
def rawResponse = sh(returnStdout: true, script: "curl -s -u \$IQ_USER:\$IQ_PASSWORD -X GET '${IQ_URL_BASE}/api/v2/applications?publicId=${componentName}'")
response = jsonSlurper.parseText(rawResponse)
}
}
def publishComponentSbom(String buildDir, String componentName, String componentVersion, boolean cyclonedxAvailable = true) {
def publishCommand = "curl -v -s -w 'Status: %{http_code}' -u \$NXRM_USER:\$NXRM_PASSWORD -X POST '${REPO_BASE_URL}/v1/components?repository=${TARGET_REPO_NAME}' \
-F 'raw.directory=/${componentName}/${componentVersion}/' \
-F 'raw.asset1=@${buildDir}/spdx/${componentName}-${componentVersion}-spdx.json' \
-F 'raw.asset1.filename=${componentName}-${componentVersion}-spdx.json'"
if (cyclonedxAvailable) {
publishCommand = "${publishCommand} \
-F 'raw.asset2=@${buildDir}/cyclonedx/${componentName}-${componentVersion}-cyclonedx.json' \
-F 'raw.asset2.filename=${componentName}-${componentVersion}-cyclonedx.json'"
}
withCredentials([
usernamePassword(
credentialsId: SBOM_DEPLOYER_CREDENTIALS,
usernameVariable: 'NXRM_USER',
passwordVariable: 'NXRM_PASSWORD')
]) {
def publishStatus = sh(script: publishCommand, returnStdout: true).trim()
if( !(publishStatus ==~ "Status: 2\\d\\d") ) {
error "Could not publish SBOM of component ${componentName}:${componentVersion}"
}
}
}
def mergeSpdxComponents(String buildDir, String finalComponentName, String finalComponentVersion, String finalNamespace) {
def pythonEnvDir = "${buildDir}/.spdxmerge"
sh """#!/bin/bash
if ! [ -d "${buildDir}/SPDXMerge" ]; then
git clone --branch '${SPDXMERGE_VERSION_TAG}' https://github.com/philips-software/SPDXMerge.git '${buildDir}/SPDXMerge'
fi
"""
sh """#!/bin/bash
if mkdir -p '${pythonEnvDir}' && python3 -m venv '${pythonEnvDir}' && ls '${pythonEnvDir}' && . '${pythonEnvDir}/bin/activate'; then
if python3 -m pip install -r '${buildDir}/SPDXMerge/requirements.txt' \
&& python3 -m pip install setuptools \
&& python3 '${buildDir}/SPDXMerge/spdxmerge/SPDXMerge.py' --docpath '${buildDir}/spdx' --outpath '${buildDir}/' \
--name "docker-nexus3-aggregate" --mergetype "1" --author "Sonatype Inc." --email "support@sonatype.com" \
--docnamespace "${finalNamespace}" \
--filetype J \
&& mv '${buildDir}/merged-SBoM-deep.json' '${buildDir}/spdx/${finalComponentName}-${finalComponentVersion}-spdx.json'; then
echo 'Merge completed!'
else
echo 'Merge failed!'
FAILED=1
fi
deactivate
fi
exit \${FAILED:-0}
"""
}
def getNexusReportName(String tag) {
for(entry in NEXUS3_REPORT_BY_TAG) {
if(tag ==~ entry.key) {
return entry.value
}
}
return DEFAULT_NEXUS3_REPORT
}
def dockerInspectLabel(String image, String tag, String label) {
sh(script: "docker inspect ${image}:${tag} | jq -r '.[0].Config.Labels[\"${label}\"]'", returnStdout: true).trim()
}
dockerizedRunPipeline(
skipVulnerabilityScan: true,
pathToDockerfile: "./build-images/Dockerfile.sbom-deployer",
prepare: {
withSonatypeDockerRegistry() {
sh "docker pull ${DOCKER_NEXUS_IMAGE_NAME}:${params.docker_nexus3_tag}"
def baseImageRef = dockerInspectLabel(DOCKER_NEXUS_IMAGE_NAME, params.docker_nexus3_tag, "base-image-ref")
env['imageTag'] = params.docker_nexus3_tag
env['nexusVersion'] = dockerInspectLabel(DOCKER_NEXUS_IMAGE_NAME, params.docker_nexus3_tag, "version")
env['dockerImageVersion'] = dockerInspectLabel(DOCKER_NEXUS_IMAGE_NAME, params.docker_nexus3_tag, "release")
env['ubiImageId'] = baseImageRef.contains("image=") ? baseImageRef.split("image=")[1] : ""
}
},
run: {
def buildDir = "./.sbom-build/job-${env.BUILD_NUMBER}/v${env.imageTag}"
def jsonSlurper = new JsonSlurper()
def nexusReportName = getNexusReportName(env.imageTag)
// Download SBOMs
sh "mkdir -p ${buildDir}/spdx && mkdir -p ${buildDir}/cyclonedx"
// Get nexus-internal SBOM
getComponentSbom(buildDir, "nexus-internal", env.nexusVersion)
// Get nxrm-db-migrator SBOM
getComponentSbom(buildDir, "nxrm-db-migrator", env.nexusVersion)
// Get we SBOM
getComponentSbom(buildDir, nexusReportName, env.dockerImageVersion)
// Get UBI Minimal SBOM
boolean ubiSbomAvailable = env.ubiImageId?.trim() ? true : false
def ubiImageName = ubiSbomAvailable ? sh(script: "curl -s -X 'GET' '${REDHAT_CONTAINER_API_URL_BASE}/images/id/${env.ubiImageId}' -H 'accept: application/json' \
| jq -r '.brew.build' \
| sed -En 's/(ubi[0-9]+-minimal)-container-([0-9]+\\.[0-9]+-[0-9]+\\.?[0-9]*)/\\1-\\2/p'",
returnStdout: true).trim() : ""
def ubiImageVersion = ubiSbomAvailable ? sh(script: "curl -s -X 'GET' '${REDHAT_CONTAINER_API_URL_BASE}/images/id/${env.ubiImageId}' -H 'accept: application/json' \
| jq -r '.brew.build' \
| sed -En 's/ubi[0-9]+-minimal-container-([0-9]+\\.[0-9]+-[0-9]+\\.?[0-9]*)/\\1/p'",
returnStdout: true).trim() : ""
ubiSbomAvailable = ubiSbomAvailable ? getUbiImageSbom(buildDir, ubiImageName, ubiImageVersion) : false
sh "echo 'Available SPDX SBOMS' && ls ${buildDir}/spdx"
sh "echo 'Available CycloneDx SBOMS' && ls ${buildDir}/cyclonedx"
// Merge supported sboms
def dockerImageNamespace = sh(script: "cat ${buildDir}/spdx/${nexusReportName}-${env.dockerImageVersion}-spdx.json | jq -r '.documentNamespace'", returnStdout: true).trim()
mergeSpdxComponents(buildDir, "${nexusReportName}-aggregate", env.dockerImageVersion, dockerImageNamespace)
// Publish SBOMs
if (ubiSbomAvailable) {
publishComponent(buildDir, "ubi-minimal", ubiImageVersion, false)
}
publishComponentSbom(buildDir, "nexus-internal", env.nexusVersion)
publishComponentSbom(buildDir, "nxrm-db-migrator", env.nexusVersion)
publishComponentSbom(buildDir, nexusReportName, env.dockerImageVersion)
publishComponentSbom(buildDir, "${nexusReportName}-aggregate", env.dockerImageVersion, false)
sh "rm -rf '${buildDir}'"
}
)