214 lines
9.3 KiB
Plaintext
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}'"
|
|
}
|
|
)
|