Skip to content

Commit 2abe1fb

Browse files
authored
Merge pull request #32 from VigneshC-Jfrog/jenkins-slsa
Support for Jenkins SLSA Evidence Integration
2 parents a9b841e + cea3d9c commit 2abe1fb

File tree

5 files changed

+321
-0
lines changed

5 files changed

+321
-0
lines changed

examples/jenkins-slsa/Jenkinsfile

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
pipeline {
2+
agent any
3+
tools {
4+
jfrog 'jfrog-cli-latest'
5+
maven 'maven'
6+
}
7+
environment {
8+
PROJECT_WORKING_DIR = 'examples/jenkins-slsa'
9+
PACKAGE_REPO_NAME = 'maven-libs-release-local'
10+
MARKDOWN_FILE_NAME = 'JenkinsSLSA.md'
11+
PREDICATE_FILE_NAME = '${PROJECT_WORKING_DIR}/decoded-payload.json'
12+
PREDICATE_TYPE = 'http://slsa.dev/provenance/v1'
13+
}
14+
stages {
15+
stage('Checkout') {
16+
steps {
17+
git branch: 'jenkins-slsa', url: 'https://github.com/jfrog/Evidence-Examples.git', credentialsId: 'github'
18+
}
19+
}
20+
21+
stage('JFrog CLI Configuration') {
22+
steps {
23+
script {
24+
withCredentials([
25+
string(credentialsId: 'ARTIFACTORY_URL', variable: 'ARTIFACTORY_URL'),
26+
string(credentialsId: 'ACCESS_TOKEN', variable: 'ACCESS_TOKEN')
27+
]) {
28+
jf 'c add jenkins-slsa-evidence --url=${ARTIFACTORY_URL} --access-token=${ACCESS_TOKEN}'
29+
}
30+
}
31+
}
32+
}
33+
34+
stage('Build and Publish') {
35+
steps {
36+
script {
37+
jf 'c use jenkins-slsa-evidence'
38+
jf 'mvn-config \
39+
--repo-resolve-releases=maven-libs-release \
40+
--repo-resolve-snapshots=maven-libs-snapshot \
41+
--repo-deploy-releases=maven-libs-release \
42+
--repo-deploy-snapshots=maven-libs-snapshot'
43+
jf 'mvn clean install -f ${PROJECT_WORKING_DIR}/pom.xml'
44+
// Retrieve Maven project artifactId and version for use in later steps
45+
env.PACKAGE_NAME = sh(script: "mvn help:evaluate -f ${PROJECT_WORKING_DIR}/pom.xml -Dexpression=project.artifactId -q -DforceStdout", returnStdout: true).trim()
46+
env.PACKAGE_VERSION = sh(script: "mvn help:evaluate -f ${PROJECT_WORKING_DIR}/pom.xml -Dexpression=project.version -q -DforceStdout", returnStdout: true).trim()
47+
}
48+
}
49+
}
50+
}
51+
52+
post {
53+
success {
54+
provenanceRecorder artifactFilter: '${PROJECT_WORKING_DIR}/target/*.jar', targetDirectory: '${PROJECT_WORKING_DIR}/build/slsa'
55+
script {
56+
def slsaDir = '${PROJECT_WORKING_DIR}/build/slsa'
57+
def jsonlFiles = sh(script: "ls ${slsaDir}/*.jsonl 2>/dev/null || true", returnStdout: true).trim().split("\\r?\\n")
58+
def jsonlFile = jsonlFiles.find { it }
59+
if (!jsonlFile) {
60+
echo "No .jsonl file found in ${slsaDir}/"
61+
return
62+
}
63+
echo "Found JSONL file: ${jsonlFile}"
64+
def jsonlText = readFile(jsonlFile)
65+
def jsonlMap = new groovy.json.JsonSlurperClassic().parseText(jsonlText)
66+
def decodedPayload = new String(jsonlMap.decodedPayload.decodeBase64(), 'UTF-8')
67+
def prettyJson = groovy.json.JsonOutput.prettyPrint(decodedPayload)
68+
writeFile file: "${PROJECT_WORKING_DIR}/decoded-payload.json", text: prettyJson
69+
echo "Decoded payload saved to examples/jenkins-slsa/decoded-payload.json"
70+
sh 'python3 ${PROJECT_WORKING_DIR}/json-to-md.py'
71+
}
72+
withCredentials([
73+
file(credentialsId: 'PRIVATE_PEM', variable: 'PRIVATE_PEM'),
74+
string(credentialsId: 'KEY_ALIAS', variable: 'KEY_ALIAS')
75+
]) {
76+
jf 'evd create \
77+
--package-name ${PACKAGE_NAME} \
78+
--package-version ${PACKAGE_VERSION} \
79+
--package-repo-name ${PACKAGE_REPO_NAME} \
80+
--key ${PRIVATE_PEM} \
81+
--key-alias ${KEY_ALIAS} \
82+
--predicate ${PREDICATE_FILE_NAME} \
83+
--predicate-type ${PREDICATE_TYPE} \
84+
--markdown ${MARKDOWN_FILE_NAME}'
85+
}
86+
}
87+
}
88+
}

examples/jenkins-slsa/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Jenkins SLSA Evidence Example
2+
3+
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.
4+
5+
## Overview
6+
7+
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.
8+
9+
## Prerequisites
10+
11+
- JFrog CLI
12+
- Artifactory configured as a Maven repository
13+
- The following Jenkins credentials:
14+
- `ARTIFACTORY_URL` (Artifactory URL)
15+
- `ACCESS_TOKEN_ID` (Artifactory access token)
16+
- `PRIVATE_PEM` (Private key for signing evidence)
17+
- `KEY_ALIAS` (Key alias for signing evidence)
18+
- `github` (GitHub credentials for source checkout)
19+
20+
## Environment Variables Used
21+
22+
- `PACKAGE_REPO_NAME` - Maven repository name in Artifactory
23+
- `PACKAGE_NAME` - Maven artifactId (extracted from pom.xml)
24+
- `PACKAGE_VERSION` - Maven version (extracted from pom.xml)
25+
- `PREDICATE_FILE_NAME` - Path to SLSA provenance JSON file
26+
- `PREDICATE_TYPE` - Predicate type URL for SLSA
27+
- `MARKDOWN_FILE_NAME` - Path to the generated Markdown file from provenance
28+
29+
## Pipeline Stages
30+
31+
1. **Checkout**
32+
- Clones the source code from GitHub.
33+
2. **JFrog CLI Configuration**
34+
- Configures JFrog CLI with Artifactory credentials.
35+
3. **Build and Publish**
36+
- Builds the Maven project and publishes artifacts to Artifactory.
37+
- Extracts artifactId and version for evidence attachment.
38+
4. **Provenance Generation and Evidence Attachment**
39+
- Generates SLSA provenance.
40+
- Converts the provenance JSON to Markdown.
41+
- Attaches the signed provenance (JSON and Markdown) as evidence to the Maven package in Artifactory.
42+
43+
## Example Usage
44+
45+
Trigger the pipeline in Jenkins. The pipeline will:
46+
47+
- Build and publish the Maven artifact
48+
- Generate and convert the SLSA provenance
49+
- Attach the provenance as evidence
50+
51+
## Key Commands Used
52+
53+
- **Configure JFrog CLI:**
54+
```bash
55+
jf c add jenkins-slsa-evidence --url=https://evidencetrial.jfrog.io --access-token=$ACCESS_TOKEN
56+
```
57+
- **Build and Publish Maven Artifact:**
58+
```bash
59+
jf mvn clean install
60+
```
61+
- **Extract Maven Coordinates:**
62+
```bash
63+
mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout
64+
mvn help:evaluate -Dexpression=project.version -q -DforceStdout
65+
```
66+
- **Convert Provenance JSON to Markdown:**
67+
```bash
68+
python3 json-to-md.py
69+
```
70+
- **Attach Evidence:**
71+
```bash
72+
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"
73+
```
74+
75+
## Limitation
76+
77+
**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.
78+
79+
## References
80+
81+
- [SLSA Provenance](https://slsa.dev/spec/v1.1/provenance)
82+
- [Jenkins SLSA Plugin](https://plugins.jenkins.io/slsa/)
83+
- [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management)
84+
- [JFrog CLI Documentation](https://jfrog.com/getcli/)
85+
- [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)
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import json
2+
3+
def format_digests(digests):
4+
if not isinstance(digests, dict):
5+
return ""
6+
sha1 = digests.get("sha1")
7+
sha256 = digests.get("sha256")
8+
if sha1 and sha256:
9+
return f"sha1: {sha1}, sha256: {sha256}"
10+
elif sha1:
11+
return f"sha1: {sha1}"
12+
elif sha256:
13+
return f"sha256: {sha256}"
14+
return ""
15+
16+
def main():
17+
with open('examples/jenkins-slsa/decoded-payload.json', 'r') as f:
18+
data = json.load(f)
19+
20+
lines = []
21+
lines.append("# SLSA Provenance Statement")
22+
lines.append(f"- **predicateType**: `{data.get('predicateType', '')}`")
23+
lines.append(f"- **_type**: `{data.get('_type', '')}`\n")
24+
25+
# Subject
26+
lines.append("## Subject")
27+
for subj in data.get("subject", []):
28+
lines.append(f"- **Name**: `{subj.get('name', '')}`")
29+
digests = format_digests(subj.get("digests", {}))
30+
if digests:
31+
lines.append(f"- **Digests**: `{digests}`")
32+
lines.append("")
33+
34+
# Predicate
35+
pred = data.get("predicate", {})
36+
lines.append("## Predicate\n")
37+
lines.append("### Build Type")
38+
lines.append(f"- `{pred.get('buildType', '')}`\n")
39+
40+
lines.append("### Builder")
41+
builder = pred.get("builder", {})
42+
lines.append(f"- **ID**: `{builder.get('id', '')}`\n")
43+
44+
# Invocation
45+
invocation = pred.get("invocation", {})
46+
lines.append("### Invocation\n")
47+
config = invocation.get("configSource", {})
48+
lines.append("#### Config Source")
49+
lines.append(f"- **URI**: `{config.get('uri', '')}`")
50+
lines.append(f"- **Entry Point**: `{config.get('entryPoint', '')}`")
51+
digests = format_digests(config.get("digests", {}))
52+
if digests:
53+
lines.append(f"- **Digests**: `{digests}`")
54+
lines.append("")
55+
56+
env = invocation.get("environment", {})
57+
lines.append("#### Environment")
58+
lines.append(f"- **Build URL**: `{env.get('build_url', '')}`")
59+
lines.append(f"- **Job URL**: `{env.get('job_url', '')}`")
60+
lines.append(f"- **Node Name**: `{env.get('node_name', '')}`\n")
61+
62+
# Metadata
63+
metadata = pred.get("metadata", {})
64+
lines.append("### Metadata")
65+
lines.append(f"- **Build Invocation ID**: `{metadata.get('buildInvocationId', '')}`")
66+
lines.append(f"- **Build Started On**: `{metadata.get('buildStartedOn', '')}`")
67+
lines.append(f"- **Build Finished On**: `{metadata.get('buildFinishedOn', '')}`")
68+
lines.append(f"- **Reproducible**: `{str(metadata.get('reproducible', ''))}`\n")
69+
70+
completeness = metadata.get("completeness", {})
71+
lines.append("#### Completeness")
72+
lines.append(f"- **Parameters Complete**: `{str(completeness.get('parametersComplete', ''))}`")
73+
lines.append(f"- **Environment Complete**: `{str(completeness.get('environmentComplete', ''))}`")
74+
lines.append(f"- **Materials Complete**: `{str(completeness.get('materialsComplete', ''))}`\n")
75+
76+
# Materials
77+
lines.append("### Materials")
78+
for mat in pred.get("materials", []):
79+
lines.append(f"- **URI**: `{mat.get('uri', '')}`")
80+
digests = format_digests(mat.get("digests", {}))
81+
if digests:
82+
lines.append(f"- **Digests**: `{digests}`")
83+
84+
with open('JenkinsSLSA.md', 'w') as f:
85+
f.write('\n'.join(lines))
86+
87+
if __name__ == "__main__":
88+
main()

examples/jenkins-slsa/pom.xml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.jfrog.evidence</groupId>
8+
<artifactId>jenkins-slsa-evidence-integration</artifactId>
9+
<version>1.0</version>
10+
11+
<properties>
12+
<maven.compiler.source>21</maven.compiler.source>
13+
<maven.compiler.target>21</maven.compiler.target>
14+
15+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
17+
18+
<maven.plugins.compiler.version>3.13.0</maven.plugins.compiler.version>
19+
<maven.plugins.shade.version>3.5.2</maven.plugins.shade.version>
20+
<maven.plugins.surefire.version>3.2.5</maven.plugins.surefire.version>
21+
22+
<junit.version>5.10.2</junit.version>
23+
</properties>
24+
25+
<dependencies>
26+
<dependency>
27+
<groupId>org.junit.jupiter</groupId>
28+
<artifactId>junit-jupiter-api</artifactId>
29+
<version>${junit.version}</version>
30+
<scope>test</scope>
31+
</dependency>
32+
</dependencies>
33+
34+
<build>
35+
<plugins>
36+
<plugin>
37+
<groupId>org.apache.maven.plugins</groupId>
38+
<artifactId>maven-compiler-plugin</artifactId>
39+
<version>${maven.plugins.compiler.version}</version>
40+
<configuration>
41+
<source>${maven.compiler.source}</source>
42+
<target>${maven.compiler.target}</target>
43+
</configuration>
44+
</plugin>
45+
<plugin>
46+
<groupId>org.apache.maven.plugins</groupId>
47+
<artifactId>maven-surefire-plugin</artifactId>
48+
<version>${maven.plugins.surefire.version}</version>
49+
</plugin>
50+
</plugins>
51+
</build>
52+
53+
</project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.jfrog.evidence;
2+
3+
public class HelloWorld {
4+
public static void main(String[] args) {
5+
System.out.println("This is a sample Java application for Jenkins SLSA Evidence Integration.");
6+
}
7+
}

0 commit comments

Comments
 (0)