Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions examples/jenkins-provenance/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -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}'
}
}
}
}
115 changes: 115 additions & 0 deletions examples/jenkins-provenance/README.md
Original file line number Diff line number Diff line change
@@ -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["<br>"]
D["Configure npm Repositories"]
E["Build npm Project"]
F["Extract Package Name & Version"]
end
subgraph subGraph1["</br>"]
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)
1 change: 1 addition & 0 deletions examples/jenkins-provenance/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hello, World!");
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
12 changes: 12 additions & 0 deletions examples/jenkins-provenance/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
88 changes: 0 additions & 88 deletions examples/jenkins-slsa/Jenkinsfile

This file was deleted.

Loading