CICD with Owasp Zap, Docker and Pipeline Scripting (Part 2)

CICD with Owasp Zap, Docker and Pipeline Scripting (Part 2)

Creating the build job

What we want to do next is to create the jenkins build job. The one that we will choose is pipeline job.

And choose pipeline job

And now we put in the pipeline script below. I have put in comments in there that will hopefully explain further what the pipeline script does. The formatting is abit off as there is no proper formatter for pipeline script but there is for groovy. but you can copy and paste it into a text editor of your choice to view it properly.

//declaring common variables that will be used throughout the pipeline script
def shellOutput = ""
def dvwaContainerName = "dvwacontainer"
def zapContainerName = "zapcontainer"
def owaspZapNet = "owasp-zap-net"
 
// a groovy function that stops and removes the DVWA container
def stopAndRemoveDVWAContainer(dvwaContainerName) {
	def shellOutput = sh(script: "docker ps -aqf \"name=${dvwaContainerName}\"",returnStdout: true).trim()
 
	//note the double quotes to use variable substitution
	echo "dvwa container id: ${shellOutput}"
 
	if(shellOutput != ""){
		sh "docker container stop $shellOutput"
		sh "docker container rm $shellOutput"
	} else {
		echo "no container called ${dvwaContainerName}"				
	}					
}
 
/*
 Note that when we manually start up the DVWA web application, we had to initialize the web application by logging in and creating the database. 
 This groovy function does that programmatically.
*/
def startAndInitializeDVWAContainer(dvwaContainerPortNumber) {
 
	/*
		we dump the headers and the html response into 2 files so that we can extract out the php session id
		and also the user token
	*/
	sh "curl --dump-header headers.txt GET 'http://localhost:${dvwaContainerPortNumber}/setup.php' > htmlresponse.txt"
 
	//getting php session id
	shellOutput = sh(script: "cat headers.txt | grep \"PHPSESSID\"", returnStdout: true).trim()	
	def phpSessionId = shellOutput.substring(shellOutput.indexOf("=")+1, shellOutput.indexOf(";"))
	echo "phpSessionId:${phpSessionId}"
 
 
	//getting user token
	shellOutput = sh(script: "cat htmlresponse.txt | grep \"user_token\"", returnStdout: true).trim()	
	def firstDelimiter = "value='"
	def firstDelimiterIndex = shellOutput.indexOf(firstDelimiter)+firstDelimiter.length()
	def userToken = shellOutput.substring(shellOutput.indexOf(firstDelimiter)+firstDelimiter.length(), shellOutput.indexOf("'", firstDelimiterIndex))
	echo "userToken:${userToken}"
 
	//once we get both the php session id and user token, we can create the database
	shellOutput = sh(script: "curl --location --request POST 'http://localhost:${dvwaContainerPortNumber}/setup.php' "+
	"--header 'Cookie: PHPSESSID=${phpSessionId}; security=low' " +
	"--form 'user_token=${userToken}' " +
	"--form 'create_db=Create+%2F+Reset+Database'",
	returnStdout: true).trim()
 
	if(shellOutput.indexOf("Database has been created") == -1)
		error "Unable to create database for dvwa application"
	else
		echo "dvwa application initialized"
}
 
pipeline {
    agent any
    stages {
        stage('Baseline Scan') {
            steps {
				//deleting all files in the current workspace so we start clean
				cleanWs()
 
				//we checkout ourt files from the github repository into the current workspace
				checkout([$class: 'GitSCM', 
					branches: [[name: "origin/master"]], 
					userRemoteConfigs: [[
					url: 'https://github.com/augmentonesecurity/dvwa_owasp_zap_config.git']],
				])			
 
				//we create the user defined network here.
				sh "docker network create ${owaspZapNet}"
 
                script{
					//we start up the dvwa container
					def dvwaContainerPortNumber = 8090
					def dvwaContainer = docker.image('vulnerables/web-dvwa').run("-p ${dvwaContainerPortNumber}:80 --name ${dvwaContainerName} --network ${owaspZapNet}");
 
					/*
						we are waiting for 5 seconds before trying to check that the dvwa web application has been initialized and retrying 3 times.
						if not, we fail the build
					*/
                    retry(3) {
                        sleep 5
						shellOutput = sh(script: "curl -v -L http://localhost:${dvwaContainerPortNumber}", returnStdout: true).trim()
						if(!shellOutput.contains("<title>Login :: Damn Vulnerable Web Application (DVWA) v1.10 *Development*</title>"))
							error "${dvwaContainerName} still not started"
						else {
							echo "${dvwaContainerName} has started"	
							startAndInitializeDVWAContainer(dvwaContainerPortNumber)					
						}
                    }
 
					//displaying the output to show that the dvwa container is registered in the user defined network
					sh "docker network inspect ${owaspZapNet}"
 
					/*
						this is where we will be building our custom zap2docker-stable image with the arguments the user/group id of the jenkins user 
						that is executing this build job
					*/
					def customImage = docker.build("modified-zap2docker-stable"," --build-arg USER_ID=\$(id -u jenkins) --build-arg GROUP_ID=\$(id -g jenkins) .")
 
					def jenkinsWorkDir = env.WORKSPACE
					/*
						take note that when we spin up the container using this custom image, all commands in this block of code runs inside the container.
						So when the block of code exits, the container will be stoppped and removed.
						We are also mounting the current workspace to /zap/wrk here where we can transfer files between the container and workspace with ease
					*/
					customImage.inside("-p 8100:8080 --name=${zapContainerName} -v ${jenkinsWorkDir}:/zap/wrk/:rw -w /zap/wrk/  --network ${owaspZapNet}"){
						try{
							// overwriting the files in zap container with ours
							sh "cp /zap/wrk/log4j2.properties /zap/xml/"
							sh "cp /zap/wrk/config.xml /zap/xml/"
 
							//check to see if we are not able to reach the dvwa container through the docker user defined network
							def shellReturnStatus = sh(script: "ping -c 2 ${dvwaContainerName}", returnStatus: true)
							if(shellReturnStatus != 0) 
								error('Not able to reach dvwa container')
 
							def dvwaContainerUrl = "http://${dvwaContainerName}/"											
							//checking that we can reach the dvwa web application through the docker user defined network
							shellReturnStatus = sh(script: "curl -v -L ${dvwaContainerUrl}"	, returnStatus: true)
							if(shellReturnStatus != 0)
								error('Not able to reach dvwa web application')
 
							//once all is good, we run the zap-baseline.py script
							def zapBaselineCommand = "zap-baseline.py -t ${dvwaContainerUrl} -g gen.conf -r testreport.html -n /zap/wrk/Default_Context.context -U admin"							
							sh "${zapBaselineCommand}"
 
							//if you get to this point, your baseline scan passed
							echo "Baseline scan passed"
 
						} catch (Exception e) {
							//when any errors happened we want to catch it and fail the build
							echo "Exception happened: ${e.getMessage()}"
							currentBuild.result = 'FAILURE'
						} finally {
							//copy the zap.log to /zap/wrk so that it will be in workspace for you to download and view it
							sh "cp /home/zap/.ZAP/zap.log /zap/wrk"
 
							//or alternatively, you can display the log in the jenkins build job console output
							//sh "cat /home/zap/.ZAP/zap.log"
						}						
					}
				}
            }
        }
    }
 
	post { 
        always { 
			script{
				//cleaning up the build by stopping and removing the dvwa docker containers and network
				stopAndRemoveDVWAContainer(dvwaContainerName)
				echo "removing network ${owaspZapNet}"
				sh "docker network rm ${owaspZapNet}"
			}
        }
    }
 
}

Running the build job

After running the build job, you can go into the workspace

And you will see all the files that you have checked out from the repository, the zap.log file that you can view to see what is happening inside Zap and also the report generated by Zap which you can view by just clicking on it.

Leave a Comment

Your email address will not be published. Required fields are marked *