How to Set Up Jenkins for Security Scanning with Qualys

How to Set Up Jenkins for Security Scanning with Qualys

Qualys was the tool I first encountered on the infrastructure side of security — used mostly by ops teams to keep tabs on server patch levels and network-level vulnerabilities. It took me a while to realize how well it fits into a CI/CD pipeline too, especially for scanning container images and cloud infrastructure before they ever reach production. Once I hooked Qualys into Jenkins, vulnerability management stopped being a quarterly compliance checkbox and became part of the everyday build process.

This guide walks through integrating Qualys with Jenkins for both infrastructure/network scanning and container image scanning use cases.

Why Qualys in CI/CD

Qualys Cloud Platform offers several modules relevant to DevSecOps pipelines, most notably:

Integrating Qualys into Jenkins means every build can trigger a scan of the relevant asset type — whether that’s a freshly built container image, a newly provisioned VM, or a deployed web application — before it’s promoted further down the pipeline.

Jenkins Architecture Context

Qualys operates primarily through its Cloud Platform API, authenticated using a Qualys username/password or API token depending on the module. Jenkins calls these APIs to launch scans, poll status, and retrieve results, very similar in pattern to how Acunetix and Burp Suite integrations work, but Qualys’s API structure (XML-based for VM/WAS, REST/JSON for Container Security) differs enough to be worth walking through separately.

Prerequisites

Step 1: Store Qualys Credentials in Jenkins

Under Manage Jenkins > Credentials, add a Username with password credential (ID: qualys-api-creds) containing your Qualys API username and password.

Step 2: Qualys Container Security Scanning in a Jenkinsfile

This is one of the most common Qualys + Jenkins integrations — scanning a Docker image immediately after it’s built, before it’s pushed to a registry.

pipeline {
    agent any

    environment {
        IMAGE_NAME = 'myapp'
        IMAGE_TAG = "${env.BUILD_NUMBER}"
        QUALYS_CS_URL = 'https://qualysapi.qualys.com/csapi/v1.3'
    }

    stages {
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ."
            }
        }

        stage('Qualys Container Scan') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'qualys-api-creds', usernameVariable: 'QUALYS_USER', passwordVariable: 'QUALYS_PASS')]) {
                    sh """
                        # Save image as tarball for sensor-based scanning
                        docker save -o image.tar ${IMAGE_NAME}:${IMAGE_TAG}

                        # Trigger scan via Qualys Container Sensor (assumes sensor is deployed as a container on the build host)
                        docker run --rm \
                          -e QUALYS_USER=${QUALYS_USER} \
                          -e QUALYS_PASS=${QUALYS_PASS} \
                          -v \$(pwd)/image.tar:/scan/image.tar \
                          qualys/cs-sensor:latest scan --file /scan/image.tar --output /scan/results.json
                    """
                }
            }
        }

        stage('Evaluate Scan Results') {
            steps {
                script {
                    def results = readJSON file: 'results.json'
                    def critical = results.vulnerabilities.findAll { it.severity == '5' || it.severity == '4' }
                    if (critical.size() > 0) {
                        error "Found ${critical.size()} critical/high vulnerabilities in image ${IMAGE_NAME}:${IMAGE_TAG}"
                    } else {
                        echo "No critical vulnerabilities found. Proceeding to push image."
                    }
                }
            }
        }

        stage('Push Image to Registry') {
            steps {
                sh "docker push ${IMAGE_NAME}:${IMAGE_TAG}"
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'results.json', allowEmptyArchive: true
        }
        failure {
            mail to: 'security-team@example.com',
                 subject: "Qualys Scan Failed for ${IMAGE_NAME}:${IMAGE_TAG}",
                 body: "Critical vulnerabilities detected. See archived results.json for details."
        }
    }
}

Step 3: Qualys Web Application Scanning (WAS) Integration

For DAST-style scanning of a deployed staging application:

pipeline {
    agent any

    environment {
        QUALYS_API = 'https://qualysapi.qualys.com/qps/rest/3.0'
        WEBAPP_ID = '123456'
    }

    stages {
        stage('Deploy to Staging') {
            steps {
                sh './deploy.sh staging'
            }
        }

        stage('Trigger Qualys WAS Scan') {
            steps {
                withCredentials([usernamePassword(credentialsId: 'qualys-api-creds', usernameVariable: 'QUALYS_USER', passwordVariable: 'QUALYS_PASS')]) {
                    script {
                        def response = httpRequest(
                            url: "${QUALYS_API}/launch/was/wasscan/",
                            httpMode: 'POST',
                            authentication: 'qualys-api-creds',
                            customHeaders: [[name: 'Content-Type', value: 'application/xml']],
                            requestBody: """<ServiceRequest>
                                <data>
                                    <WasScan>
                                        <name>Jenkins-Triggered-Scan-${env.BUILD_NUMBER}</name>
                                        <webApp><id>${WEBAPP_ID}</id></webApp>
                                        <profile><id>111</id></profile>
                                    </WasScan>
                                </data>
                            </ServiceRequest>"""
                        )
                        echo "Scan launched: ${response.content}"
                    }
                }
            }
        }
    }
}

Qualys WAS uses XML-based requests, which is a notable difference from the JSON-first APIs of Acunetix and Burp Suite — worth remembering when you’re debugging request formatting issues.

Step 4: Qualys Vulnerability Management (Infrastructure Scanning)

If you’re provisioning infrastructure with Terraform and want to scan newly created VMs before they join production traffic:

stage('Provision Infrastructure') {
    steps {
        sh 'terraform apply -auto-approve'
    }
}

stage('Qualys VM Scan') {
    steps {
        withCredentials([usernamePassword(credentialsId: 'qualys-api-creds', usernameVariable: 'QUALYS_USER', passwordVariable: 'QUALYS_PASS')]) {
            sh """
                curl -u "\$QUALYS_USER:\$QUALYS_PASS" \
                  -X POST "https://qualysapi.qualys.com/api/2.0/fo/scan/" \
                  -d "action=launch&scan_title=Jenkins-${env.BUILD_NUMBER}&ip=10.0.1.0/24&option_title=Initial+Options"
            """
        }
    }
}

Integrating with Terraform and Ansible

Qualys pairs naturally with infrastructure-as-code workflows:

Integrating with Docker and Kubernetes

For Kubernetes environments, Qualys Container Security also supports scanning images already stored in a registry or running in a cluster via the Qualys Kubernetes sensor, which can be deployed as a DaemonSet, giving continuous vulnerability visibility beyond just the CI build step.

Monitoring and Reporting

Qualys Cloud Platform maintains a centralized dashboard across VM, WAS, and Container Security modules. I recommend archiving raw JSON/XML scan results as Jenkins build artifacts in addition to relying on the Qualys dashboard, since build-linked artifacts make it much easier to trace “which commit introduced this vulnerability” during retrospectives.

Troubleshooting Common Issues

Best Practices

FAQs

Can Qualys scan private container registries? Yes, Qualys Container Security supports scanning images directly in registries like Docker Hub, ECR, GCR, and Harbor via configured registry connectors, in addition to CI-time tarball scanning.

Is Qualys VM scanning safe to run against production without causing outages? Qualys offers non-intrusive scan options, but any active vulnerability scan carries some risk of triggering sensitive systems (like IDS/IPS). Best practice is to schedule production scans during low-traffic windows and coordinate with your infrastructure team.

What’s the difference between Qualys WAS and Container Security modules? WAS is a DAST tool testing running web applications for vulnerabilities like SQLi/XSS; Container Security is more like SCA (Software Composition Analysis) for container images, focused on known CVEs in OS packages and application dependencies within the image layers.

How do I avoid false positives blocking every build? Use Qualys’s vulnerability exception/acceptance workflow to formally document accepted risks, rather than loosening your Jenkins severity gate broadly.

Summary

Qualys brings enterprise-grade vulnerability management into your Jenkins pipeline, whether you’re scanning container images before they’re pushed, testing live web applications, or validating freshly provisioned infrastructure. By wiring Qualys’s APIs into your Jenkinsfile and gating deployments on severity thresholds, you shift vulnerability detection left — catching issues during the build instead of during a security audit months later.

References

Exit mobile version