1
+ @Library(['private-pipeline-library', 'jenkins-shared']) _
2
+
3
+ import com.sonatype.jenkins.pipeline.OsTools
4
+
5
+ import groovy.json.JsonSlurper
6
+
7
+ IQ_URL_BASE = "https://sonatype.sonatype.app/platform"
8
+ REPO_BASE_URL = "https://repo.sonatype.com/service/rest"
9
+ TARGET_REPO_NAME = "sonatype-sboms"
10
+ REDHAT_SBOM_REPO_URL_BASE = "https://access.redhat.com/security/data/sbom/beta"
11
+ REDHAT_CONTAINER_API_URL_BASE = "https://catalog.redhat.com/api/containers/v1"
12
+ CYCLONEDX_VERSION = "1.5"
13
+
14
+ properties([
15
+ parameters([
16
+ string(name: 'docker_nexus3_tag', defaultValue: '',
17
+ description: 'Nexus Repository Manager Version. The result SBOMs will be tagged with this version.')
18
+ ])
19
+ ])
20
+
21
+
22
+ node('ubuntu-zion') {
23
+ def buildDir = "./.sbom-build/job-${env.BUILD_NUMBER}"
24
+ def ubiImageName = ''
25
+ def ubiImageVersion = ''
26
+ def nexusVersion = ''
27
+ def dockerImageVersion = ''
28
+ def ubiSbomAvailable = true
29
+
30
+ try {
31
+ stage('Inspect Release Image') {
32
+ // Get RedHat UBI version
33
+ sh "docker pull sonatype/nexus3:${params.docker_nexus3_tag}"
34
+
35
+ nexusVersion = sh(script: "docker inspect sonatype/nexus3:${params.docker_nexus3_tag} \
36
+ | jq -r '.[0].Config.Labels.version' ",
37
+ returnStdout: true).trim()
38
+ dockerImageVersion = sh(script: "docker inspect sonatype/nexus3:${params.docker_nexus3_tag} \
39
+ | jq -r '.[0].Config.Labels.release' ",
40
+ returnStdout: true).trim()
41
+
42
+ def ubiImageId = sh(script: "docker inspect sonatype/nexus3:${params.docker_nexus3_tag} \
43
+ | jq -r '.[0].Config.Labels.\"base-image-ref\"' \
44
+ | sed -En 's/^.+image=(.+)\$/\\1/p'",
45
+ returnStdout: true).trim()
46
+
47
+ ubiImageName = sh(script: "curl -s -X 'GET' '${REDHAT_CONTAINER_API_URL_BASE}/images/id/${ubiImageId}' -H 'accept: application/json' \
48
+ | jq -r '.brew.build' \
49
+ | sed -En 's/(ubi[0-9]+-minimal)-container-([0-9]+\\.[0-9]+-[0-9]+\\.?[0-9]*)/\\1-\\2/p'",
50
+ returnStdout: true).trim()
51
+
52
+ ubiImageVersion = sh(script: "curl -s -X 'GET' '${REDHAT_CONTAINER_API_URL_BASE}/images/id/${ubiImageId}' -H 'accept: application/json' \
53
+ | jq -r '.brew.build' \
54
+ | sed -En 's/ubi[0-9]+-minimal-container-([0-9]+\\.[0-9]+-[0-9]+\\.?[0-9]*)/\\1/p'",
55
+ returnStdout: true).trim()
56
+ }
57
+ stage('Download SBOMs') {
58
+ sh "mkdir -p ${buildDir}/spdx && mkdir -p ${buildDir}/cyclonedx"
59
+
60
+ // Get nexus-internal SBOM
61
+ getComponentSbom(buildDir, "nexus-internal", nexusVersion)
62
+ // Get nxrm-db-migrator SBOM
63
+ getComponentSbom(buildDir, "nxrm-db-migrator", nexusVersion)
64
+ // Get docker-nexus3 SBOM
65
+ getComponentSbom(buildDir, "docker-nexus3", dockerImageVersion)
66
+ // Get UBI Minimal SBOM
67
+ ubiSbomAvailable = getUbiImageSbom(buildDir, ubiImageName, ubiImageVersion)
68
+
69
+ sh "echo 'Available SPDX SBOMS' && ls ${buildDir}/spdx"
70
+ sh "echo 'Available CycloneDx SBOMS' && ls ${buildDir}/cyclonedx"
71
+ }
72
+
73
+ stage('Merge supported sboms') {
74
+ def pythonEnvDir = "${buildDir}/.spdxmerge"
75
+
76
+ sh """#!/bin/bash
77
+ if ! [ -d "${buildDir}/SPDXMerge" ]; then
78
+ git clone https://github.com/philips-software/SPDXMerge.git '${buildDir}/SPDXMerge'
79
+ fi
80
+ """
81
+
82
+ sh """#!/bin/bash
83
+ if mkdir -p '${pythonEnvDir}' && python3 -m venv '${pythonEnvDir}' && ls '${pythonEnvDir}' && . '${pythonEnvDir}/bin/activate'; then
84
+ if python3 -m pip install -r '${buildDir}/SPDXMerge/requirements.txt' \
85
+ && python3 -m pip install setuptools \
86
+ && python3 '${buildDir}/SPDXMerge/spdxmerge/SPDXMerge.py' --docpath '${buildDir}/spdx' --outpath '${buildDir}/' \
87
+ --name "docker-nexus3-aggregate" --mergetype "1" --author "Sonatype Inc." --email "
[email protected] " \
88
+ --docnamespace "https://sonatype.sonatype.app/platform/ui/links/application/docker-nexus3/report/b0c5f7f12ac84b439ded3ff255bd5eef" \
89
+ --filetype J \
90
+ && mv '${buildDir}/merged-SBoM-deep.json' '${buildDir}/spdx/docker-nexus3-aggregate-${dockerImageVersion}-spdx.json'; then
91
+ echo 'Merge completed!'
92
+ else
93
+ echo 'Merge failed!'
94
+ FAILED=1
95
+ fi
96
+
97
+ deactivate
98
+ fi
99
+
100
+ exit \${FAILED:-0}
101
+ """
102
+ }
103
+
104
+ stage('Publish SBOMs') {
105
+ if (ubiSbomAvailable) {
106
+ publishComponent(buildDir, "ubi-minimal", ubiImageVersion, false)
107
+ }
108
+ publishComponent(buildDir, "nexus-internal", nexusVersion)
109
+ publishComponent(buildDir, "nxrm-db-migrator", nexusVersion)
110
+ publishComponent(buildDir, "docker-nexus3", dockerImageVersion)
111
+ publishComponent(buildDir, "docker-nexus3-aggregate", dockerImageVersion, false)
112
+ }
113
+ } finally {
114
+ OsTools.runSafe(this, "rm -rf '${buildDir}'")
115
+ }
116
+ }
117
+
118
+ def getComponentSbom(String buildDir, String componentName, String componentVersion) {
119
+ def componentInfo = getComponentInfo(componentName)
120
+ def componentId = componentInfo.applications[0].id
121
+
122
+ withCredentials([
123
+ usernamePassword(
124
+ credentialsId: 'jenkins-saas-service-acct',
125
+ usernameVariable: 'IQ_USER',
126
+ passwordVariable: 'IQ_PASSWORD')
127
+ ]) {
128
+ // Get SPDX
129
+ sh "curl -s -L -w 'Status: %{http_code}' -u \$IQ_USER:\$IQ_PASSWORD -o '${buildDir}/spdx/${componentName}-${componentVersion}-spdx.json' -X GET \
130
+ -H 'Accept: application/json' '${IQ_URL_BASE}/api/v2/spdx/${componentId}/stages/release?format=json'"
131
+ // Get CycloneDx
132
+ sh "curl -s -L -w 'Status: %{http_code}' -u \$IQ_USER:\$IQ_PASSWORD -o '${buildDir}/cyclonedx/${componentName}-${componentVersion}-cyclonedx.json' -X GET \
133
+ -H 'Accept: application/json' '${IQ_URL_BASE}/api/v2/cycloneDx/${CYCLONEDX_VERSION}/${componentId}/stages/release'"
134
+ }
135
+ }
136
+
137
+ def getUbiImageSbom(String buildDir, String ubiMinimalName, String ubiMinimalVersion) {
138
+ // Get ubi-minimal SBOM (as RedHat SBOM repo is still in beta, this has to be optional)
139
+ def httpStatus = sh(
140
+ script: "curl -s -w \"%{http_code}\" \
141
+ -X GET ${REDHAT_SBOM_REPO_URL_BASE}/spdx/${ubiMinimalName}.json.bz2 \
142
+ -o '${buildDir}/spdx/ubi-minimal-${ubiMinimalVersion}.json.bz2'",
143
+ returnStdout: true)
144
+
145
+ if (!"200".equals(httpStatus)) {
146
+ echo """ Error ${httpStatus}: Could not load UBI minimal SBOM version ${ubiMinimalVersion}.
147
+ This could happen because RedHat SBOM repo is still in beta. UBI SBOM will be skipped.
148
+ Please visit https://access.redhat.com/security/data for further information.
149
+ """
150
+
151
+ sh "rm '${buildDir}/spdx/ubi-minimal-${ubiMinimalVersion}.json.bz2'"
152
+
153
+ return false
154
+ } else {
155
+ sh "(cd ${buildDir}/spdx && bzip2 -d 'ubi-minimal-${ubiMinimalVersion}.json.bz2')"
156
+ return true
157
+ }
158
+ }
159
+
160
+ def getComponentInfo(String componentName) {
161
+ def jsonSlurper = new JsonSlurper()
162
+ def response = null
163
+
164
+ withCredentials([
165
+ usernamePassword(
166
+ credentialsId: 'jenkins-saas-service-acct',
167
+ usernameVariable: 'IQ_USER',
168
+ passwordVariable: 'IQ_PASSWORD')
169
+ ]) {
170
+ def rawResponse = sh(returnStdout: true, script: "curl -s -u \$IQ_USER:\$IQ_PASSWORD -X GET '${IQ_URL_BASE}/api/v2/applications?publicId=${componentName}'")
171
+ response = jsonSlurper.parseText(rawResponse)
172
+ }
173
+ }
174
+
175
+ def publishComponent(String buildDir, String componentName, String componentVersion, boolean cyclonedxAvailable = true) {
176
+ 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}' \
177
+ -F 'raw.directory=/${componentName}/${componentVersion}/' \
178
+ -F 'raw.asset1=@${buildDir}/spdx/${componentName}-${componentVersion}-spdx.json' \
179
+ -F 'raw.asset1.filename=${componentName}-${componentVersion}-spdx.json'"
180
+
181
+ if (cyclonedxAvailable) {
182
+ publishCommand = "${publishCommand} \
183
+ -F 'raw.asset2=@${buildDir}/cyclonedx/${componentName}-${componentVersion}-cyclonedx.json' \
184
+ -F 'raw.asset2.filename=${componentName}-${componentVersion}-cyclonedx.json'"
185
+ }
186
+
187
+ withCredentials([
188
+ usernamePassword(
189
+ credentialsId: 'sonatype-sbom-deployer',
190
+ usernameVariable: 'NXRM_USER',
191
+ passwordVariable: 'NXRM_PASSWORD')
192
+ ]) {
193
+ sh(publishCommand)
194
+ }
195
+ }
0 commit comments