diff --git a/examples/jenkins-provenance/Jenkinsfile b/examples/jenkins-provenance/Jenkinsfile new file mode 100644 index 0000000..f1b7686 --- /dev/null +++ b/examples/jenkins-provenance/Jenkinsfile @@ -0,0 +1,72 @@ +pipeline { + agent any + tools { + jfrog 'jfrog-cli-latest' + } + environment { + PROJECT_WORKING_DIR = '.' + PACKAGE_REPO_NAME = 'evidence-npm' + MARKDOWN_FILE_NAME = 'JenkinsSLSA.md' + PREDICATE_FILE_NAME = '${PROJECT_WORKING_DIR}/predicate.json' + PREDICATE_TYPE = 'http://slsa.dev/provenance/v1' + } + stages { + stage('Checkout') { + steps { + git branch: 'main', url: 'https://github.com/jfrog/Evidence-Examples.git', credentialsId: 'github' + } + } + + stage('Build and Publish') { + steps { + script { + jf 'npmc --repo-resolve evidence-npm --repo-deploy evidence-npm' + jf 'npm publish' + jf 'npm pack' + // Retrieve npm project name and version for use in later steps + env.PACKAGE_NAME = sh(script: "node -p \"require('./package.json').name\"", returnStdout: true).trim() + env.PACKAGE_VERSION = sh(script: "node -p \"require('./package.json').version\"", returnStdout: true).trim() + } + } + } + } + + post { + success { + provenanceRecorder artifactFilter: '**.tgz', targetDirectory: '${PROJECT_WORKING_DIR}/build/slsa' + script { + def slsaDir = '${PROJECT_WORKING_DIR}/build/slsa' + def jsonlFiles = sh(script: "ls ${slsaDir}/*.jsonl 2>/dev/null || true", returnStdout: true).trim().split("\\r?\\n") + def jsonlFile = jsonlFiles.find { it } + if (!jsonlFile) { + echo "No .jsonl file found in ${slsaDir}/" + } + echo "Found JSONL file: ${jsonlFile}" + def jsonlText = readFile(jsonlFile) + def jsonlMap = new groovy.json.JsonSlurperClassic().parseText(jsonlText) + def decodedPayload = new String(jsonlMap.decodedPayload.decodeBase64(), 'UTF-8') + // Extract predicate object from the JSON + def jsonObject = new groovy.json.JsonSlurperClassic().parseText(decodedPayload) + def predicateObject = jsonObject.predicate + def predicateJson = groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(predicateObject)) + writeFile file: "${PROJECT_WORKING_DIR}/predicate.json", text: predicateJson + echo "Predicate object extracted and saved to examples/jenkins-provenance/predicate.json" + sh 'python3 ${PROJECT_WORKING_DIR}/json-to-md.py' + } + withCredentials([ + file(credentialsId: 'PRIVATE_PEM', variable: 'PRIVATE_PEM'), + string(credentialsId: 'KEY_ALIAS', variable: 'KEY_ALIAS') + ]) { + jf 'evd create \ + --package-name ${PACKAGE_NAME} \ + --package-version ${PACKAGE_VERSION} \ + --package-repo-name ${PACKAGE_REPO_NAME} \ + --key ${PRIVATE_PEM} \ + --key-alias ${KEY_ALIAS} \ + --predicate ${PREDICATE_FILE_NAME} \ + --predicate-type ${PREDICATE_TYPE} \ + --markdown ${MARKDOWN_FILE_NAME}' + } + } + } +} \ No newline at end of file diff --git a/examples/jenkins-provenance/README.md b/examples/jenkins-provenance/README.md new file mode 100644 index 0000000..b62bb8b --- /dev/null +++ b/examples/jenkins-provenance/README.md @@ -0,0 +1,115 @@ +# Jenkins SLSA Evidence Example + +This project demonstrates how to automate npm builds, generate SLSA provenance, convert it to Markdown, and attach the signed provenance evidence to the npm package in JFrog Artifactory using Jenkins Pipeline and JFrog CLI. + +## Overview + +The pipeline builds an npm project, generates SLSA provenance, converts the provenance JSON to Markdown, publishes the artifact to Artifactory, and attaches the signed provenance as evidence to the npm package. This enables traceability and compliance for your Node.js artifacts in CI/CD. + +### **Workflow** + +The following diagram illustrates the sequence of operations performed by the Jenkins pipeline. + +```mermaid +flowchart TD + subgraph subGraph0["
"] + D["Configure npm Repositories"] + E["Build npm Project"] + F["Extract Package Name & Version"] + end + subgraph subGraph1["
"] + H["Generate SLSA Provenance"] + I["Convert Provenance JSON to Markdown"] + J["Attach Evidence to npm Package"] + end + A["Manual Pipeline Trigger"] --> B["Checkout Stage"] + B --> C["Build and Publish Stage"] + C --> D + D --> E + E --> F + F --> G["Post Success Actions"] + G --> H + H --> I + I --> J +``` + +--- + +## Prerequisites + +- JFrog CLI (installed via Jenkins tools) +- Artifactory configured as an npm repository +- JFrog CLI configured in Jenkins system settings with Artifactory access token +- The following Jenkins credentials: + - `PRIVATE_PEM` (Private key for signing evidence) + - `KEY_ALIAS` (Key alias for signing evidence) + - `github` (GitHub credentials for source checkout if required) + +**Note:** The Artifactory access token should be configured in Jenkins system settings for the JFrog CLI tool. This follows the recommended approach for using JFrog CLI in Jenkins as documented in the [JFrog Plugin documentation](https://jfrog.com/help/r/artifactory-how-to-use-jfrog-cli-in-jenkins-using-jfrog-plugin/artifactory-how-to-use-jfrog-cli-in-jenkins-using-jfrog-plugin). + +## Environment Variables Used + +- `PACKAGE_REPO_NAME` - npm repository name in Artifactory +- `PACKAGE_NAME` - npm package name (extracted from package.json) +- `PACKAGE_VERSION` - npm version (extracted from package.json) +- `PREDICATE_FILE_NAME` - Path to SLSA provenance JSON file +- `PREDICATE_TYPE` - Predicate type URL for SLSA +- `MARKDOWN_FILE_NAME` - Path to the generated Markdown file from provenance + +## Pipeline Stages + +1. **Checkout** + - Clones the source code from GitHub. +2. **Build and Publish** + - Configures npm repositories using JFrog Plugin. + - Builds the npm project and publishes artifacts to Artifactory. + - Extracts package name and version for evidence attachment. +3. **Provenance Generation and Evidence Attachment** + - Generates SLSA provenance. + - Converts the provenance JSON to Markdown. + - Attaches the signed provenance (JSON and Markdown) as evidence to the npm package in Artifactory. + +## Example Usage + +Trigger the pipeline in Jenkins. The pipeline will: + +- Build and publish the npm artifact +- Generate and convert the SLSA provenance +- Attach the provenance as evidence + +## Key Commands Used + +- **Configure npm Repositories:** + ```bash + jf npmc --repo-resolve evidence-npm --repo-deploy evidence-npm + ``` +- **Build and Publish npm Artifact:** + ```bash + jf npm publish + jf npm pack + ``` +- **Extract npm Coordinates:** + ```bash + node -p "require('./package.json').name" + node -p "require('./package.json').version" + ``` +- **Convert Provenance JSON to Markdown:** + ```bash + python3 json-to-md.py + ``` +- **Attach Evidence:** + ```bash + jf evd create --package-name="$PACKAGE_NAME" --package-version="$PACKAGE_VERSION" --package-repo-name="$PACKAGE_REPO_NAME" --key="$PRIVATE_PEM" --key-alias="$KEY_ALIAS" --predicate="$PREDICATE_FILE_NAME" --predicate-type="$PREDICATE_TYPE" --markdown="$MARKDOWN_FILE_NAME" + ``` + +## Limitation + +**Note:** The current pipeline and evidence attachment process expects a single npm artifact (tgz) is produced per build. It does **not** support multiple subjects or multiple packages in a single pipeline execution. This is a known limitation and should be considered when working with this example. + +## References + +- [SLSA Provenance](https://slsa.dev/spec/v1.1/provenance) +- [Jenkins SLSA Plugin](https://plugins.jenkins.io/slsa/) +- [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management) +- [JFrog CLI Documentation](https://jfrog.com/getcli/) +- [How to use JFrog CLI in Jenkins using JFrog Plugin](https://jfrog.com/help/r/artifactory-how-to-use-jfrog-cli-in-jenkins-using-jfrog-plugin/artifactory-how-to-use-jfrog-cli-in-jenkins-using-jfrog-plugin) diff --git a/examples/jenkins-provenance/index.js b/examples/jenkins-provenance/index.js new file mode 100644 index 0000000..ae9e3a9 --- /dev/null +++ b/examples/jenkins-provenance/index.js @@ -0,0 +1 @@ +console.log("Hello, World!"); \ No newline at end of file diff --git a/examples/jenkins-slsa/json-to-md.py b/examples/jenkins-provenance/json-to-md.py similarity index 80% rename from examples/jenkins-slsa/json-to-md.py rename to examples/jenkins-provenance/json-to-md.py index 7124506..19ebf9d 100644 --- a/examples/jenkins-slsa/json-to-md.py +++ b/examples/jenkins-provenance/json-to-md.py @@ -14,25 +14,12 @@ def format_digests(digests): return "" def main(): - with open('examples/jenkins-slsa/decoded-payload.json', 'r') as f: - data = json.load(f) + with open('./predicate.json', 'r') as f: + pred = json.load(f) lines = [] - lines.append("# SLSA Provenance Statement") - lines.append(f"- **predicateType**: `{data.get('predicateType', '')}`") - lines.append(f"- **_type**: `{data.get('_type', '')}`\n") - - # Subject - lines.append("## Subject") - for subj in data.get("subject", []): - lines.append(f"- **Name**: `{subj.get('name', '')}`") - digests = format_digests(subj.get("digests", {})) - if digests: - lines.append(f"- **Digests**: `{digests}`") + lines.append("# SLSA Provenance Predicate") lines.append("") - - # Predicate - pred = data.get("predicate", {}) lines.append("## Predicate\n") lines.append("### Build Type") lines.append(f"- `{pred.get('buildType', '')}`\n") diff --git a/examples/jenkins-provenance/package.json b/examples/jenkins-provenance/package.json new file mode 100644 index 0000000..aa596de --- /dev/null +++ b/examples/jenkins-provenance/package.json @@ -0,0 +1,12 @@ +{ + "name": "jenkins-provenance-evidence-integration", + "version": "1.0.0", + "description": "Jenkins CI/CD pipeline with SLSA provenance evidence integration", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "keywords": ["jenkins", "provenance", "slsa", "evidence", "ci-cd"], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/examples/jenkins-slsa/Jenkinsfile b/examples/jenkins-slsa/Jenkinsfile deleted file mode 100644 index 151fe05..0000000 --- a/examples/jenkins-slsa/Jenkinsfile +++ /dev/null @@ -1,88 +0,0 @@ -pipeline { - agent any - tools { - jfrog 'jfrog-cli-latest' - maven 'maven' - } - environment { - PROJECT_WORKING_DIR = 'examples/jenkins-slsa' - PACKAGE_REPO_NAME = 'maven-libs-release-local' - MARKDOWN_FILE_NAME = 'JenkinsSLSA.md' - PREDICATE_FILE_NAME = '${PROJECT_WORKING_DIR}/decoded-payload.json' - PREDICATE_TYPE = 'http://slsa.dev/provenance/v1' - } - stages { - stage('Checkout') { - steps { - git branch: 'jenkins-slsa', url: 'https://github.com/jfrog/Evidence-Examples.git', credentialsId: 'github' - } - } - - stage('JFrog CLI Configuration') { - steps { - script { - withCredentials([ - string(credentialsId: 'ARTIFACTORY_URL', variable: 'ARTIFACTORY_URL'), - string(credentialsId: 'ACCESS_TOKEN', variable: 'ACCESS_TOKEN') - ]) { - jf 'c add jenkins-slsa-evidence --url=${ARTIFACTORY_URL} --access-token=${ACCESS_TOKEN}' - } - } - } - } - - stage('Build and Publish') { - steps { - script { - jf 'c use jenkins-slsa-evidence' - jf 'mvn-config \ - --repo-resolve-releases=maven-libs-release \ - --repo-resolve-snapshots=maven-libs-snapshot \ - --repo-deploy-releases=maven-libs-release \ - --repo-deploy-snapshots=maven-libs-snapshot' - jf 'mvn clean install -f ${PROJECT_WORKING_DIR}/pom.xml' - // Retrieve Maven project artifactId and version for use in later steps - env.PACKAGE_NAME = sh(script: "mvn help:evaluate -f ${PROJECT_WORKING_DIR}/pom.xml -Dexpression=project.artifactId -q -DforceStdout", returnStdout: true).trim() - env.PACKAGE_VERSION = sh(script: "mvn help:evaluate -f ${PROJECT_WORKING_DIR}/pom.xml -Dexpression=project.version -q -DforceStdout", returnStdout: true).trim() - } - } - } - } - - post { - success { - provenanceRecorder artifactFilter: '${PROJECT_WORKING_DIR}/target/*.jar', targetDirectory: '${PROJECT_WORKING_DIR}/build/slsa' - script { - def slsaDir = '${PROJECT_WORKING_DIR}/build/slsa' - def jsonlFiles = sh(script: "ls ${slsaDir}/*.jsonl 2>/dev/null || true", returnStdout: true).trim().split("\\r?\\n") - def jsonlFile = jsonlFiles.find { it } - if (!jsonlFile) { - echo "No .jsonl file found in ${slsaDir}/" - return - } - echo "Found JSONL file: ${jsonlFile}" - def jsonlText = readFile(jsonlFile) - def jsonlMap = new groovy.json.JsonSlurperClassic().parseText(jsonlText) - def decodedPayload = new String(jsonlMap.decodedPayload.decodeBase64(), 'UTF-8') - def prettyJson = groovy.json.JsonOutput.prettyPrint(decodedPayload) - writeFile file: "${PROJECT_WORKING_DIR}/decoded-payload.json", text: prettyJson - echo "Decoded payload saved to examples/jenkins-slsa/decoded-payload.json" - sh 'python3 ${PROJECT_WORKING_DIR}/json-to-md.py' - } - withCredentials([ - file(credentialsId: 'PRIVATE_PEM', variable: 'PRIVATE_PEM'), - string(credentialsId: 'KEY_ALIAS', variable: 'KEY_ALIAS') - ]) { - jf 'evd create \ - --package-name ${PACKAGE_NAME} \ - --package-version ${PACKAGE_VERSION} \ - --package-repo-name ${PACKAGE_REPO_NAME} \ - --key ${PRIVATE_PEM} \ - --key-alias ${KEY_ALIAS} \ - --predicate ${PREDICATE_FILE_NAME} \ - --predicate-type ${PREDICATE_TYPE} \ - --markdown ${MARKDOWN_FILE_NAME}' - } - } - } -} \ No newline at end of file diff --git a/examples/jenkins-slsa/README.md b/examples/jenkins-slsa/README.md deleted file mode 100644 index 2b9b67b..0000000 --- a/examples/jenkins-slsa/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Jenkins SLSA Evidence Example - -This project demonstrates how to automate Maven builds, generate SLSA provenance, convert it to Markdown, and attach the signed provenance evidence to the Maven package in JFrog Artifactory using Jenkins Pipeline and JFrog CLI. - -## Overview - -The pipeline builds a Maven project, generates SLSA provenance, converts the provenance JSON to Markdown, publishes the artifact to Artifactory, and attaches the signed provenance as evidence to the Maven package. This enables traceability and compliance for your Java artifacts in CI/CD. - -## Prerequisites - -- JFrog CLI -- Artifactory configured as a Maven repository -- The following Jenkins credentials: - - `ARTIFACTORY_URL` (Artifactory URL) - - `ACCESS_TOKEN_ID` (Artifactory access token) - - `PRIVATE_PEM` (Private key for signing evidence) - - `KEY_ALIAS` (Key alias for signing evidence) - - `github` (GitHub credentials for source checkout) - -## Environment Variables Used - -- `PACKAGE_REPO_NAME` - Maven repository name in Artifactory -- `PACKAGE_NAME` - Maven artifactId (extracted from pom.xml) -- `PACKAGE_VERSION` - Maven version (extracted from pom.xml) -- `PREDICATE_FILE_NAME` - Path to SLSA provenance JSON file -- `PREDICATE_TYPE` - Predicate type URL for SLSA -- `MARKDOWN_FILE_NAME` - Path to the generated Markdown file from provenance - -## Pipeline Stages - -1. **Checkout** - - Clones the source code from GitHub. -2. **JFrog CLI Configuration** - - Configures JFrog CLI with Artifactory credentials. -3. **Build and Publish** - - Builds the Maven project and publishes artifacts to Artifactory. - - Extracts artifactId and version for evidence attachment. -4. **Provenance Generation and Evidence Attachment** - - Generates SLSA provenance. - - Converts the provenance JSON to Markdown. - - Attaches the signed provenance (JSON and Markdown) as evidence to the Maven package in Artifactory. - -## Example Usage - -Trigger the pipeline in Jenkins. The pipeline will: - -- Build and publish the Maven artifact -- Generate and convert the SLSA provenance -- Attach the provenance as evidence - -## Key Commands Used - -- **Configure JFrog CLI:** - ```bash - jf c add jenkins-slsa-evidence --url=https://evidencetrial.jfrog.io --access-token=$ACCESS_TOKEN - ``` -- **Build and Publish Maven Artifact:** - ```bash - jf mvn clean install - ``` -- **Extract Maven Coordinates:** - ```bash - mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout - mvn help:evaluate -Dexpression=project.version -q -DforceStdout - ``` -- **Convert Provenance JSON to Markdown:** - ```bash - python3 json-to-md.py - ``` -- **Attach Evidence:** - ```bash - jf evd create --package-name="$PACKAGE_NAME" --package-version="$PACKAGE_VERSION" --package-repo-name="$PACKAGE_REPO_NAME" --key="$PRIVATE_PEM" --key-alias="$KEY_ALIAS" --predicate="$PREDICATE_FILE_NAME" --predicate-type="$PREDICATE_TYPE" --markdown="$MARKDOWN_FILE_NAME" - ``` - -## Limitation - -**Note:** The current pipeline and evidence attachment process expects a single Maven artifact (JAR) is produced per build. It does **not** support multiple subjects or multiple JARs in a single pipeline execution. This is a known limitation and should be considered when working with this example. - -## References - -- [SLSA Provenance](https://slsa.dev/spec/v1.1/provenance) -- [Jenkins SLSA Plugin](https://plugins.jenkins.io/slsa/) -- [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management) -- [JFrog CLI Documentation](https://jfrog.com/getcli/) -- [How to use JFrog CLI in Jenkins using JFrog Plugin](https://jfrog.com/help/r/artifactory-how-to-use-jfrog-cli-in-jenkins-using-jfrog-plugin/artifactory-how-to-use-jfrog-cli-in-jenkins-using-jfrog-plugin) \ No newline at end of file diff --git a/examples/jenkins-slsa/pom.xml b/examples/jenkins-slsa/pom.xml deleted file mode 100644 index e2e9338..0000000 --- a/examples/jenkins-slsa/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - com.jfrog.evidence - jenkins-slsa-evidence-integration - 1.0 - - - 21 - 21 - - UTF-8 - UTF-8 - - 3.13.0 - 3.5.2 - 3.2.5 - - 5.10.2 - - - - - org.junit.jupiter - junit-jupiter-api - ${junit.version} - test - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven.plugins.compiler.version} - - ${maven.compiler.source} - ${maven.compiler.target} - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven.plugins.surefire.version} - - - - - diff --git a/examples/jenkins-slsa/src/main/java/com/jfrog/evidence/HelloWorld.java b/examples/jenkins-slsa/src/main/java/com/jfrog/evidence/HelloWorld.java deleted file mode 100644 index 5d3657d..0000000 --- a/examples/jenkins-slsa/src/main/java/com/jfrog/evidence/HelloWorld.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.jfrog.evidence; - -public class HelloWorld { - public static void main(String[] args) { - System.out.println("This is a sample Java application for Jenkins SLSA Evidence Integration."); - } -}