How to Set Up Jenkins for Security Scanning with Nessus

How to Set Up Jenkins for Security Scanning with Nessus

Nessus was one of the first vulnerability scanners I ever used, long before I got into CI/CD pipelines, and it’s still one of the most trusted tools for infrastructure and network vulnerability assessment. When I started building out DevSecOps pipelines, wiring Nessus into Jenkins felt like a natural fit — it let me catch missing patches, misconfigurations, and known CVEs on freshly provisioned servers automatically, right after infrastructure changes went out, rather than waiting for a quarterly compliance scan to catch them months later.

This guide covers the full setup: from installing and configuring Nessus, to triggering scans from Jenkins, parsing results, and gating builds.

Why Nessus in a CI/CD Pipeline

Nessus, built by Tenable, is primarily a network and host vulnerability scanner. It checks for missing patches, outdated software, weak configurations, and known CVEs across servers, network devices, and even some cloud assets. In a DevOps context, it’s most commonly used to:

Jenkins Architecture Context

Nessus exposes a REST API (available in Nessus Professional, Nessus Manager, and Tenable.io/Tenable.sc) that Jenkins can call to launch scans, check status, and pull results. Unlike DAST tools that scan running web apps, Nessus typically targets IP ranges or hostnames directly, meaning it usually fits into your pipeline right after infrastructure provisioning, rather than after an application-layer deployment.

Prerequisites

Step 1: Generate Nessus API Keys

In the Nessus web UI, go to Settings > My Account > API Keys and generate an access key and secret key. Store both securely.

Step 2: Store Nessus Credentials in Jenkins

Add two Secret text credentials in Jenkins:

Step 3: Create a Scan Policy/Template in Nessus (One-Time Setup)

Before automating via API, create a reusable scan policy in the Nessus UI (e.g., “Basic Network Scan” or a custom compliance policy), and note its template_uuid or policy ID — you’ll reference this when launching scans programmatically.

Step 4: Jenkinsfile for Nessus Scan Integration

pipeline {
    agent any

    environment {
        NESSUS_HOST = 'https://nessus.internal:8834'
        SCAN_POLICY_UUID = 'ab4bacd2-05f6-425c-9d79-6e8cdf5f1b19' // your policy UUID
        TARGET_HOSTS = '10.0.1.10,10.0.1.11,10.0.1.12'
    }

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

        stage('Create Nessus Scan') {
            steps {
                withCredentials([
                    string(credentialsId: 'nessus-access-key', variable: 'ACCESS_KEY'),
                    string(credentialsId: 'nessus-secret-key', variable: 'SECRET_KEY')
                ]) {
                    script {
                        def response = httpRequest(
                            url: "${NESSUS_HOST}/scans",
                            httpMode: 'POST',
                            customHeaders: [
                                [name: 'X-ApiKeys', value: "accessKey=${ACCESS_KEY}; secretKey=${SECRET_KEY}"],
[name: ‘Content-Type’, value: ‘application/json’]

], requestBody: “””{ “uuid”: “${SCAN_POLICY_UUID}”, “settings”: { “name”: “Jenkins-Build-${env.BUILD_NUMBER}”, “text_targets”: “${TARGET_HOSTS}” } }”””, ignoreSslErrors: true ) def json = readJSON text: response.content env.SCAN_ID = json.scan.id echo “Created scan with ID: ${env.SCAN_ID}” } } } } stage(‘Launch Scan’) { steps { withCredentials([ string(credentialsId: ‘nessus-access-key’, variable: ‘ACCESS_KEY’), string(credentialsId: ‘nessus-secret-key’, variable: ‘SECRET_KEY’) ]) { httpRequest( url: “${NESSUS_HOST}/scans/${env.SCAN_ID}/launch”, httpMode: ‘POST’, customHeaders: [[name: ‘X-ApiKeys’, value: “accessKey=${ACCESS_KEY}; secretKey=${SECRET_KEY}”]], ignoreSslErrors: true ) } } } stage(‘Poll Scan Status’) { steps { withCredentials([ string(credentialsId: ‘nessus-access-key’, variable: ‘ACCESS_KEY’), string(credentialsId: ‘nessus-secret-key’, variable: ‘SECRET_KEY’) ]) { script { def status = ‘running’ timeout(time: 60, unit: ‘MINUTES’) { while (status != ‘completed’) { sleep(60) def check = httpRequest( url: “${NESSUS_HOST}/scans/${env.SCAN_ID}”, customHeaders: [[name: ‘X-ApiKeys’, value: “accessKey=${ACCESS_KEY}; secretKey=${SECRET_KEY}”]], ignoreSslErrors: true ) def json = readJSON text: check.content status = json.info.status echo “Scan status: ${status}” } } } } } } stage(‘Evaluate Results’) { steps { withCredentials([ string(credentialsId: ‘nessus-access-key’, variable: ‘ACCESS_KEY’), string(credentialsId: ‘nessus-secret-key’, variable: ‘SECRET_KEY’) ]) { script { def resultResponse = httpRequest( url: “${NESSUS_HOST}/scans/${env.SCAN_ID}”, customHeaders: [[name: ‘X-ApiKeys’, value: “accessKey=${ACCESS_KEY}; secretKey=${SECRET_KEY}”]], ignoreSslErrors: true ) def json = readJSON text: resultResponse.content def critical = json.hosts.findAll { it.severitycount.item.find { s -> s.severitylevel == 4 } } if (critical.size() > 0) { error “Critical vulnerabilities found on ${critical.size()} host(s). Failing build.” } else { echo “No critical vulnerabilities found.” } } } } } } post { failure { mail to: ‘security-team@example.com’, subject: “Nessus Scan Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}”, body: “Critical vulnerabilities detected on scanned infrastructure. Check Nessus dashboard.” } } }

Exporting and Archiving Reports

Nessus supports exporting scan results in multiple formats (Nessus, HTML, PDF, CSV). Adding an export stage makes results easy to archive and share:

stage('Export Report') {
    steps {
        withCredentials([
            string(credentialsId: 'nessus-access-key', variable: 'ACCESS_KEY'),
            string(credentialsId: 'nessus-secret-key', variable: 'SECRET_KEY')
        ]) {
            script {
                def exportResp = httpRequest(
                    url: "${NESSUS_HOST}/scans/${env.SCAN_ID}/export",
                    httpMode: 'POST',
                    customHeaders: [
                        [name: 'X-ApiKeys', value: "accessKey=${ACCESS_KEY}; secretKey=${SECRET_KEY}"],
[name: ‘Content-Type’, value: ‘application/json’]

], requestBody: ‘{“format”: “pdf”}’, ignoreSslErrors: true ) echo “Export triggered: ${exportResp.content}” } } } }

You’d then poll the export status and download the file token similarly to how the scan itself is polled, then archive it with archiveArtifacts.

Integrating with Terraform and Ansible

Nessus fits particularly well right after terraform apply:

stage('Provision & Scan') {
    steps {
        sh 'terraform apply -auto-approve'
        script {
            env.TARGET_HOSTS = sh(script: "terraform output -raw instance_ips", returnStdout: true).trim()
        }
    }
}

Pulling the target IPs directly from Terraform output means you never have to hardcode IP addresses — new infrastructure gets scanned automatically no matter how it’s provisioned.

With Ansible, a common pattern is triggering a Nessus scan as a post-playbook validation step, confirming that configuration changes haven’t introduced any new vulnerabilities or left default credentials active.

Integrating with Cloud Platforms

Tenable.io (the cloud-hosted version of Nessus) integrates with AWS, Azure, and GCP asset discovery, automatically importing newly launched instances as scan targets. This is especially useful in auto-scaling environments where instances are created and destroyed dynamically — you don’t want to hardcode a static IP list in your Jenkinsfile when your infrastructure is elastic.

Monitoring and Long-Term Tracking

Nessus/Tenable.io dashboards provide historical vulnerability trends across scans. I recommend combining this with Jenkins build history — tagging each scan with the build number (as shown in the example) makes it trivial to trace which infrastructure change introduced a new vulnerability.

Troubleshooting Common Issues

Best Practices

FAQs

Can Nessus scan cloud-native resources like S3 buckets or IAM policies? Standard Nessus is primarily host/network focused; for cloud configuration scanning, Tenable.io’s Cloud Security module or a dedicated CSPM tool is a better fit alongside Nessus.

How is Nessus different from Qualys? Both are vulnerability management platforms with overlapping functionality; the choice often comes down to existing licensing, UI/workflow preference, and specific compliance template support your organization needs.

Do I need a Nessus scanner on every network segment? Yes — Nessus scanners need direct network reachability to targets, so distributed infrastructure across VPCs or on-prem segments typically requires multiple scanner appliances or a properly routed network path.

Can I run authenticated scans for deeper results? Yes, and it’s strongly recommended — providing SSH/Windows credentials to Nessus enables authenticated scans that detect far more issues (like missing OS patches) than unauthenticated network-only scans.

Summary

Integrating Nessus into Jenkins lets you catch infrastructure-level vulnerabilities as part of your normal delivery pipeline rather than during a separate, disconnected security review. By launching scans via the Nessus REST API right after provisioning, polling for completion, and gating builds on severity thresholds, you build continuous vulnerability visibility directly into your CI/CD workflow — closing the gap between “when a vulnerability appears” and “when someone notices.”

References

Exit mobile version