Skip to main content
Integrate Binarly security scanning into your Jenkins pipeline.

Jenkinsfile

pipeline {
    agent any

    environment {
        // Replace {slug} with your organization's tenant identifier
        BINARLY_AUTH_URL = 'https://auth-{slug}.binarly.cloud/realms/BinarlyRealm/protocol/openid-connect/token'
        BINARLY_API_URL = 'https://dashboard-{slug}.binarly.cloud'
        BINARLY_CLIENT_ID = credentials('binarly-client-id')
        BINARLY_CLIENT_SECRET = credentials('binarly-client-secret')
        BINARLY_PRODUCT_ID = credentials('binarly-product-id')
    }

    stages {
        stage('Build Firmware') {
            steps {
                sh 'make firmware'
            }
        }

        stage('Authenticate') {
            steps {
                script {
                    def tokenResponse = sh(
                        script: """
                            curl -s --fail-with-body \\
                              -H "Content-Type: application/x-www-form-urlencoded" \\
                              --data-urlencode "grant_type=client_credentials" \\
                              --data-urlencode "client_id=${BINARLY_CLIENT_ID}" \\
                              --data-urlencode "client_secret=${BINARLY_CLIENT_SECRET}" \\
                              "${BINARLY_AUTH_URL}"
                        """,
                        returnStdout: true
                    )
                    env.BINARLY_TOKEN = readJSON(text: tokenResponse).access_token
                }
            }
        }

        stage('Upload Firmware') {
            steps {
                script {
                    // Generate upload URL
                    def uploadRes = sh(
                        script: """
                            curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                              "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/tempFiles:generateUploadUrl"
                        """,
                        returnStdout: true
                    )
                    def uploadData = readJSON(text: uploadRes)

                    // Upload binary
                    sh "curl -s -X PUT --data-binary @./build/firmware.bin '${uploadData.uploadUrl}'"

                    // Finalize
                    def imageRes = sh(
                        script: """
                            curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                              -F "tempFileId=${uploadData.id}" \\
                              -F "imageName=Jenkins Build ${BUILD_NUMBER}" \\
                              -F "version=${BUILD_NUMBER}" \\
                              -F "file=@./build/firmware.bin" \\
                              "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images:upload"
                        """,
                        returnStdout: true
                    )
                    env.IMAGE_ID = readJSON(text: imageRes).id
                    echo "Image uploaded: ${env.IMAGE_ID}"
                }
            }
        }

        stage('Wait for Scan') {
            steps {
                script {
                    def status = 'running'
                    while (status != 'completed') {
                        sleep 30
                        def scanRes = sh(
                            script: """
                                curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                                  "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images/${IMAGE_ID}/scans?status=true"
                            """,
                            returnStdout: true
                        )
                        status = readJSON(text: scanRes).scans[0].latestScanState.type
                        echo "Scan status: ${status}"
                        if (status == 'failed') {
                            error 'Binarly scan failed'
                        }
                    }
                    echo 'Scan completed successfully'
                }
            }
        }

        stage('Verify Results') {
            steps {
                script {
                    echo 'Binarly Security Verification'

                    def failOnStatus = env.FAIL_ON_STATUS ?: 'new,inProgress'
                    echo "Failing on statuses: ${failOnStatus}"

                    // Get all images
                    def imagesRes = sh(
                        script: """
                            curl -s -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                              "${BINARLY_API_URL}/api/v4/products/${BINARLY_PRODUCT_ID}/images"
                        """,
                        returnStdout: true
                    )
                    def imagesData = readJSON(text: imagesRes)
                    def imageCount = imagesData.images.size()
                    echo "Product has ${imageCount} image(s)"

                    // Compare if multiple images exist
                    if (imageCount > 1) {
                        // Sort images by createTime descending
                        def sortedImages = imagesData.images.sort { a, b -> b.createTime <=> a.createTime }
                        def latestId = sortedImages[0].id
                        def previousId = sortedImages[1].id
                        echo "Comparing: ${previousId} → ${latestId}"

                        def resolved = sh(
                            script: """
                                curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                                  -H "Content-Type: application/json" \\
                                  -d '{"filters":[{"field":"compareLeftImageId","value":"${previousId}","comparator":"equals"},{"field":"compareRightImageId","value":"${latestId}","comparator":"equals"},{"field":"compareSide","value":"left","comparator":"equals"}]}' \\
                                  "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
                            """,
                            returnStdout: true
                        ).trim()

                        def unchanged = sh(
                            script: """
                                curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                                  -H "Content-Type: application/json" \\
                                  -d '{"filters":[{"field":"compareLeftImageId","value":"${previousId}","comparator":"equals"},{"field":"compareRightImageId","value":"${latestId}","comparator":"equals"},{"field":"compareSide","value":"both","comparator":"equals"}]}' \\
                                  "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
                            """,
                            returnStdout: true
                        ).trim()

                        def newIssues = sh(
                            script: """
                                curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                                  -H "Content-Type: application/json" \\
                                  -d '{"filters":[{"field":"compareLeftImageId","value":"${previousId}","comparator":"equals"},{"field":"compareRightImageId","value":"${latestId}","comparator":"equals"},{"field":"compareSide","value":"right","comparator":"equals"}]}' \\
                                  "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
                            """,
                            returnStdout: true
                        ).trim()

                        echo "Resolved: ${resolved} | Unchanged: ${unchanged} | New: ${newIssues}"
                    }

                    // Check for findings matching configured statuses (using 'in' comparator with array)
                    def statusesArray = failOnStatus.split(',').collect { '"' + it.trim() + '"' }.join(',')
                    
                    // Use latestId if available (from comparison), otherwise use IMAGE_ID from upload step
                    def checkImageId = latestId ?: env.IMAGE_ID
                    
                    def totalActionable = sh(
                        script: """
                            curl -s -X POST -H "Authorization: Bearer ${BINARLY_TOKEN}" \\
                              -H "Content-Type: application/json" \\
                              -d '{"filters":[{"field":"imageId","value":"${checkImageId}","comparator":"equals"},{"field":"issueStatus","value":[${statusesArray}],"comparator":"in"}]}' \\
                              "${BINARLY_API_URL}/api/v4/grids/findings:gridList" | jq '.total // 0'
                        """,
                        returnStdout: true
                    ).trim().toInteger()

                    echo "   Statuses checked: ${failOnStatus}"

                    echo "Total actionable findings: ${totalActionable}"

                    if (totalActionable > 0) {
                        error "BUILD FAILED: ${totalActionable} actionable finding(s)"
                    }
                    echo 'BUILD PASSED: No actionable findings'
                }
            }
        }
    }
}

Required Credentials

Configure in Manage Jenkins → Credentials:
Credential IDTypeDescription
binarly-client-idSecret textKeycloak API Client ID
binarly-client-secretSecret textKeycloak API Client Secret
binarly-product-idSecret textTarget product ID

Pipeline Configuration

  1. Create a new Pipeline job
  2. Configure Pipeline script from SCM or paste the Jenkinsfile
  3. Ensure credentials are created in Jenkins
For detailed explanation of the verification logic, see Verify Results.