blob: 244126b78ac6e7e0fdc58d463b677bd0be6c7c84 [file] [log] [blame]
Vasyl Saienko4e8ec642018-09-17 10:08:08 +00001/**
2 *
3 * Run openscap xccdf evaluation on given nodes
4 *
5 * Expected parametes:
6 * SALT_MASTER_URL Full Salt API address.
7 * SALT_MASTER_CREDENTIALS Credentials to the Salt API.
8 *
9 * XCCDF_BENCHMARKS_DIR The XCCDF benchmarks base directory (default /usr/share/xccdf-benchmarks/mirantis/)
10 * XCCDF_BENCHMARKS List of pairs XCCDF benchmark filename and corresponding profile separated with ','
11 * these pairs are separated with semicolon.
12 * (e.g. manila/openstack_manila-xccdf.xml,profilename;horizon/openstack_horizon-xccdf.xml,profile)
13 * XCCDF_VERSION The XCCDF version (default 1.2)
14 * XCCDF_TAILORING_ID The tailoring id (default None)
15 *
16 * TARGET_SERVERS The target Salt nodes (default *)
17 *
18 * ARTIFACTORY_URL The artifactory URL
19 * ARTIFACTORY_NAMESPACE The artifactory namespace (default 'mirantis/openscap')
20 * ARTIFACTORY_REPO The artifactory repo (default 'binary-dev-local')
21 *
22 * UPLOAD_TO_DASHBOARD Boolean. Upload results to the WORP or not
23 * DASHBOARD_API_URL The WORP api base url. Mandatory if UPLOAD_TO_DASHBOARD is true
24 */
25
26
27
28/**
29 * Upload results to the `WORP` dashboard
30 *
31 * @param apiUrl The base dashboard api url
32 * @param cloudName The cloud name (mostly, the given node's domain name)
33 * @param nodeName The node name
34 * @param results The scanning results
35 */
36def uploadResultToDashboard(apiUrl, cloudName, nodeName, results) {
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +030037 def common = new com.mirantis.mk.Common()
38 def http = new com.mirantis.mk.Http()
39
Vasyl Saienko4e8ec642018-09-17 10:08:08 +000040 // Yes, we do not care of performance and will create at least 4 requests per each result
41 def requestData = [:]
42
43 def cloudId
44 def nodeId
45
46 // Let's take a look, may be our minion is already presented on the dashboard
47 // Get available environments
48 environments = common.parseJSON(http.sendHttpGetRequest("${apiUrl}/environment/"))
49 for (environment in environments) {
50 if (environment['name'] == cloudName) {
51 cloudId = environment['uuid']
52 break
53 }
54 }
55 // Cloud wasn't presented, let's create it
56 if (! cloudId ) {
57 // Create cloud
58 resuestData['name'] = cloudName
59 cloudId = common.parseJSON(http.sendHttpPostRequest("${apiUrl}/environment/", requestData))['env']['uuid']
60
61 // And the node
62 // It was done here to reduce count of requests to the api.
63 // Because if there was not cloud presented on the dashboard, then the node was not presented as well.
64 requestData['nodes'] = [nodeName]
65 nodeId = common.parseJSON(http.sendHttpPutRequest("${apiUrl}/environment/${cloudId}/nodes/", requestData))['uuid']
66 }
67
68 if (! nodeId ) {
69 // Get available nodes in our environment
70 nodes = common.parseJSON(http.sendHttpGetRequest("${apiUrl}/environment/${cloudId}/nodes/"))
71 for (node in nodes) {
72 if (node['name'] == nodeName) {
73 nodeId = node['id']
74 break
75 }
76 }
77 }
78
79 // Node wasn't presented, let's create it
80 if (! nodeId ) {
81 // Create node
82 requestData['nodes'] = [nodeName]
83 nodeId = common.parseJSON(http.sendHttpPutRequest("${apiUrl}/environment/${cloudId}/nodes/", requestData))['uuid']
84 }
85
86 // Get report_id
87 requestData['env_uuid'] = cloudId
88 def reportId = common.parseJSON(http.sendHttpPostRequest("${apiUrl}/reports/openscap/", requestData))['report']['uuid']
89
90 // Upload results
91 requestData['results'] = results
92 requestData['node_name'] = nodeName
93 http.sendHttpPutRequest("${apiUrl}/reports/openscap/${reportId}/", requestData)
94}
95
96
97node('python') {
98 def pepperEnv = 'pepperEnv'
99
100 // XCCDF related variables
101 def benchmarksAndProfilesArray = XCCDF_BENCHMARKS.tokenize(';')
102 def benchmarksDir = XCCDF_BENCHMARKS_DIR ?: '/usr/share/xccdf-benchmarks/mirantis/'
103 def xccdfVersion = XCCDF_VERSION ?: '1.2'
104 def xccdfTailoringId = XCCDF_TAILORING_ID ?: 'None'
105 def targetServers = TARGET_SERVERS ?: '*'
106
107 def salt = new com.mirantis.mk.Salt()
108 def python = new com.mirantis.mk.Python()
109 def common = new com.mirantis.mk.Common()
110 def http = new com.mirantis.mk.Http()
111
112 // To have an ability to work in heavy concurrency conditions
113 def scanUUID = UUID.randomUUID().toString()
114
115 def artifactsArchiveName = "openscap-${scanUUID}.zip"
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300116 def resultsBaseDir = "/var/log/openscap/${scanUUID}"
117 def artifactsDir = "openscap"
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000118
119 def liveMinions
120
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300121
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000122 stage ('Setup virtualenv for Pepper') {
123 python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
124 }
125
126 stage ('Run openscap xccdf evaluation and attempt to upload the results to a dashboard') {
127 liveMinions = salt.getMinions(pepperEnv, targetServers)
128
129 if (liveMinions.isEmpty()) {
130 throw new Exception('There are no alive minions')
131 }
132
133 common.infoMsg("Scan UUID: ${scanUUID}")
134
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300135 // Clean all results before proceeding with results from every minion
136 dir(artifactsDir) {
137 deleteDir()
138 }
139
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000140 for (minion in liveMinions) {
141
142 // Iterate oscap evaluation over the benchmarks
143 for (benchmark in benchmarksAndProfilesArray) {
144 def (benchmarkFilePath, profile) = benchmark.tokenize(',').collect({it.trim()})
145
146 // Remove extension from the benchmark name
147 def benchmarkPathWithoutExtension = benchmarkFilePath.replaceFirst('[.][^.]+$', '')
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300148
149 // Get benchmark name
150 def benchmarkName = benchmarkPathWithoutExtension.tokenize('/')[-1]
151
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000152 // And build resultsDir based on this path
153 def resultsDir = "${resultsBaseDir}/${benchmarkPathWithoutExtension}"
154
155 def benchmarkFile = "${benchmarksDir}${benchmarkFilePath}"
156
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300157 def nodeShortName = minion.tokenize('.')[0]
158
159 def archiveName = "${scanUUID}_${nodeShortName}_${benchmarkName}.tar"
160
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000161 // Evaluate the benchmark
162 salt.runSaltProcessStep(pepperEnv, minion, 'oscap.eval', [
163 'xccdf', benchmarkFile, "results_dir=${resultsDir}",
164 "profile=${profile}", "xccdf_version=${xccdfVersion}",
165 "tailoring_id=${xccdfTailoringId}"
166 ])
167
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300168 salt.cmdRun(pepperEnv, minion, "tar -cf /tmp/${archiveName} -C ${resultsBaseDir} .")
169 fileContents = salt.cmdRun(pepperEnv, minion, "cat /tmp/${archiveName}", true, null, false)['return'][0].values()[0].replaceAll('Salt command execution success', '')
170
171 sh "mkdir -p ${artifactsDir}/${scanUUID}/${nodeShortName}"
172 writeFile file: "${archiveName}", text: fileContents
173 sh "tar --strip-components 1 -xf ${archiveName} --directory ${artifactsDir}/${scanUUID}/${nodeShortName}; rm -f ${archiveName}"
174
175 // Remove archive which is not needed anymore
176 salt.runSaltProcessStep(pepperEnv, minion, 'file.remove', "/tmp/${archiveName}")
177
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000178 // Attempt to upload the scanning results to the dashboard
179 if (UPLOAD_TO_DASHBOARD.toBoolean()) {
180 if (common.validInputParam('DASHBOARD_API_URL')) {
181 def cloudName = salt.getGrain(pepperEnv, minion, 'domain')['return'][0].values()[0].values()[0]
182 uploadResultToDashboard(DASHBOARD_API_URL, cloudName, minion, salt.getFileContent(pepperEnv, minion, "${resultsDir}/results.json"))
183 } else {
184 throw new Exception('Uploading to the dashboard is enabled but the DASHBOARD_API_URL was not set')
185 }
186 }
187 }
188 }
Ivan Udovichenkod1bd28c2018-10-02 12:41:04 +0300189
190 // Prepare archive
191 sh "tar -cJf ${artifactsDir}.tar.xz ${artifactsDir}"
192
193 // Archive the build output artifacts
194 archiveArtifacts artifacts: "*.xz"
Vasyl Saienko4e8ec642018-09-17 10:08:08 +0000195 }
196
197/* // Will be implemented later
198 stage ('Attempt to upload results to an artifactory') {
199 if (common.validInputParam('ARTIFACTORY_URL')) {
200 for (minion in liveMinions) {
201 def destDir = "${artifactsDir}/${minion}"
202 def archiveName = "openscap-${scanUUID}.tar.gz"
203 def tempArchive = "/tmp/${archiveName}"
204 def destination = "${destDir}/${archiveName}"
205
206 dir(destDir) {
207 // Archive scanning results on the remote target
208 salt.runSaltProcessStep(pepperEnv, minion, 'archive.tar', ['czf', tempArchive, resultsBaseDir])
209
210 // Get it content and save it
211 writeFile file: destination, text: salt.getFileContent(pepperEnv, minion, tempArchive)
212
213 // Remove scanning results and the temp archive on the remote target
214 salt.runSaltProcessStep(pepperEnv, minion, 'file.remove', resultsBaseDir)
215 salt.runSaltProcessStep(pepperEnv, minion, 'file.remove', tempArchive)
216 }
217 }
218
219 def artifactory = new com.mirantis.mcp.MCPArtifactory()
220 def artifactoryName = 'mcp-ci'
221 def artifactoryRepo = ARTIFACTORY_REPO ?: 'binary-dev-local'
222 def artifactoryNamespace = ARTIFACTORY_NAMESPACE ?: 'mirantis/openscap'
223 def artifactoryServer = Artifactory.server(artifactoryName)
224 def publishInfo = true
225 def buildInfo = Artifactory.newBuildInfo()
226 def zipName = "${env.WORKSPACE}/openscap/${scanUUID}/results.zip"
227
228 // Zip scan results
229 zip zipFile: zipName, archive: false, dir: artifactsDir
230
231 // Mandatory and additional properties
232 def properties = artifactory.getBinaryBuildProperties([
233 "scanUuid=${scanUUID}",
234 "project=openscap"
235 ])
236
237 // Build Artifactory spec object
238 def uploadSpec = """{
239 "files":
240 [
241 {
242 "pattern": "${zipName}",
243 "target": "${artifactoryRepo}/${artifactoryNamespace}/openscap",
244 "props": "${properties}"
245 }
246 ]
247 }"""
248
249 // Upload artifacts to the given Artifactory
250 artifactory.uploadBinariesToArtifactory(artifactoryServer, buildInfo, uploadSpec, publishInfo)
251
252 } else {
253 common.warningMsg('ARTIFACTORY_URL was not given, skip uploading to artifactory')
254 }
255 }
256*/
257
258}