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
60 changes: 60 additions & 0 deletions .github/workflows/dive-evidence-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Dive Image Analysis with JFrog Evidence
on:
workflow_dispatch:

permissions:
id-token: write
contents: read

jobs:
dive-with-evidence:
runs-on: ubuntu-latest
env:
REGISTRY_DOMAIN: ${{ vars.JF_URL }}
REPO_NAME: 'docker-dive-repo'
IMAGE_NAME: 'docker-dive-image'
VERSION: ${{ github.run_number }}
BUILD_NAME: 'dive-docker-build'
ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE: true

steps:
# Build and publish the packages to JFrog Artifactory
- name: Setup jfrog cli
uses: jfrog/setup-jfrog-cli@v4
env:
JF_URL: ${{ vars.ARTIFACTORY_URL }}
JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}
- name: Checkout repository
uses: actions/checkout@v4
- name: Build and publish Docker Image to Artifactory
run: |
docker build . --file ./examples/dive/Dockerfile --tag $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION
jf rt docker-push $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION $REPO_NAME --build-name=$BUILD_NAME --build-number=${{ github.run_number }}

# Fetch Dive Analysis
- name: Run Dive analysis
run: |
docker run -i --rm -e CI=true \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd):/output docker.io/wagoodman/dive \
$REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION \
--json /output/dive.json

# This is an optional step to generate a custom markdown report
- name: Generate optional custom markdown report
if: env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true'
run: |
python ./examples/dive/dive_json_to_md.py dive.json

# Attaching the evidence to associated package
- name: Attach evidence using jfrog cli
run: |
jf evd create \
--package-name $IMAGE_NAME \
--package-version $VERSION \
--package-repo-name $REPO_NAME \
--key "${{ secrets.PRIVATE_KEY }}" \
--key-alias "${{ vars.EVIDENCE_KEY_ALIAS }}" \
--predicate ./dive.json \
--predicate-type dive/docker-size-scan/v1 \
${{ env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true' && '--markdown "dive-analysis.md"' || '' }}
68 changes: 68 additions & 0 deletions .github/workflows/trufflehog-evidence-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: "Trufflehog evidence integration example"
on:
workflow_dispatch:

permissions:
id-token: write
contents: read

jobs:
trufflehog-secret-scan-and-evidence:
runs-on: ubuntu-latest
env:
REGISTRY_DOMAIN: ${{ vars.JF_URL }}
REPO_NAME: 'trufflehog-repo'
IMAGE_NAME: 'docker-trufflehog-image'
VERSION: ${{ github.run_number }}
BUILD_NAME: 'trufflehog-build'
ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE: true

steps:
# Setup JFrog CLI and checkout repository
- name: Setup jfrog cli
uses: jfrog/setup-jfrog-cli@v4
env:
JF_URL: ${{ vars.ARTIFACTORY_URL }}
JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}
- name: Checkout repository
uses: actions/checkout@v4
with:
sparse-checkout: |
examples/trufflehog/**
sparse-checkout-cone-mode: false
- name: Build and publish Docker Image to Artifactory
run: |
cd examples/trufflehog
docker build . --file Dockerfile --tag $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION
jf rt docker-push $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION $REPO_NAME --build-name=$BUILD_NAME --build-number=${{ github.run_number }}
cd -

# Run Trufflehog secret scan
- name: Run Trufflehog secret scan
run: |
echo "Running Trufflehog secret scan..."
docker run --rm -v "$PWD:/pwd" trufflesecurity/trufflehog:latest filesystem /pwd --json > trufflehog-results.jsonl || true
echo "Trufflehog scan completed"

# This is an optional step to generate a custom markdown report
- name: Generate optional custom markdown report
if: env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true'
run: |
python ./examples/trufflehog/process_trufflehog_results.py trufflehog-results.jsonl

# Attaching the evidence to associated package
- name: Attach evidence using jfrog cli
run: |
ls
python ./examples/trufflehog/jsonl_to_json_converted.py
jf evd create \
--package-name $IMAGE_NAME \
--package-version $VERSION \
--package-repo-name $REPO_NAME \
--key "${{ secrets.PRIVATE_KEY }}" \
--key-alias "${{ vars.EVIDENCE_KEY_ALIAS }}" \
--predicate ./trufflehog.json \
--predicate-type https://trufflesecurity.com/TruffleHog \
${{ env.ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE == 'true' && '--markdown "report_readme.md"' || '' }}
echo "Evidence attachment completed successfully"

4 changes: 4 additions & 0 deletions examples/dive/.dive-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
rules:
lowestEfficiency: 0.95
highestWastedBytes: 20Mb
highestUserWastedPercent: 0.5
10 changes: 10 additions & 0 deletions examples/dive/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Use the official lightweight Python image
FROM python:3.9-slim

# Set the working directory
WORKDIR /app

# Add a simple script that prints a message
RUN echo 'print("Hello from Docker!")' > hello.py

CMD ["python", "hello.py"]
152 changes: 152 additions & 0 deletions examples/dive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# **Dive Image Analysis Evidence Example**

This repository provides a working example of a GitHub Actions workflow that automates Docker image analysis using **Dive**. It then attaches the resulting analysis report as signed, verifiable evidence to the package in **JFrog Artifactory**.

This workflow is an essential pattern for DevSecOps, creating a traceable, compliant, and efficient software supply chain.

### **Key Features**

* **Automated Build & Push**: Builds a Docker image from a Dockerfile.
* **Image Analysis**: Uses Dive to analyze the image for inefficiencies.
* **Evidence Generation**: Creates a dive.json predicate file.
* **Optional Markdown Report**: Includes a helper script to generate a human-readable Markdown summary from the Dive JSON results.
* **Signed Evidence Attachment**: Attaches the analysis results to the corresponding package version in Artifactory using jf evd create, cryptographically signing it for integrity.

### **Workflow**

The following diagram illustrates the sequence of operations performed by the GitHub Actions workflow.

```mermaid
graph TD
A[Workflow Dispatch Trigger] --> B[Setup JFrog CLI]
B --> C[Checkout Repository]
C --> D[Build and Push Docker Image to Artifactory]
D --> E[Run Dive Analysis]
E --> F{Generate Markdown?}
F -->|Yes| G[Generate Markdown Report]
F -->|No| H[Skip Markdown Generation]
G --> I[Attach Evidence to Package]
H --> I
```

---

### **1\. Prerequisites**

Before running this workflow, you must have:

* JFrog CLI 2.65.0 or above (installed automatically in the workflow)
* An Artifactory repository of type docker (e.g., docker-dive-repo).
* A private key and a corresponding key alias configured in your JFrog Platform for signing evidence.
* The following GitHub repository variables:
* `REGISTRY_DOMAIN` (Artifactory Docker registry domain, e.g. `mycompany.jfrog.io`)
* `EVIDENCE_KEY_ALIAS` (Key alias for signing evidence)
* The following GitHub repository secrets:
* `ARTIFACTORY_ACCESS_TOKEN` (Artifactory access token)
* `PRIVATE_KEY` (Private key for signing evidence)

### Environment Variables Used

* `REGISTRY_DOMAIN` \- Docker registry domain

### **2\. Configuration**

To use this workflow, you must configure the following GitHub Repository Secrets and Variables.

#### **GitHub Secrets**

Navigate to Settings \> Secrets and variables \> Actions and create the following secrets:

| Secret Name | Description |
| :---- | :---- |
| ARTIFACTORY_ACCESS_TOKEN | A valid JFrog Access Token with permissions to read, write, and annotate in your target repository. |
| PRIVATE_KEY | The private key used to sign the evidence. This key corresponds to the alias configured in JFrog Platform. |

#### **GitHub Variables**

Navigate to Settings \> Secrets and variables \> Actions and create the following variables:

| Variable Name | Description | Example Value |
| :---- | :---- | :---- |
| JF_URL | The base URL of your JFrog Platform instance. | https://mycompany.jfrog.io |
| EVIDENCE_KEY_ALIAS | The alias for the public key in JFrog Platform used to verify the evidence signature. | my-signing-key-alias |

#### **Workflow Environment Variables**

You can also customize the workflow's behavior by modifying the env block in the .github/workflows/dive-evidence-example.yml file:

| Variable Name | Description | Default Value |
| :---- | :---- | :---- |
| DOCKERFILE_PATH | The path to the Dockerfile in your repository. | ./Dockerfile |
| IMAGE_NAME | The name of the Docker image to be built. | my-dive-image |
| IMAGE_TAG | The tag for the Docker image. | latest |
| REGISTRY_REPO | The Artifactory repository to which the image will be pushed. | docker-dive-repo |
| DIVE_OPTIONS | Additional options for the Dive command. | --ignore-unpackaged |

---

### **3\. Usage**

This workflow is triggered manually.

1. Navigate to the **Actions** tab of your forked repository.
2. In the left sidebar, click on the **Dive evidence integration example** workflow.
3. Click the **Run workflow** dropdown button. You can leave the default branch selected.
4. Click the green **Run workflow** button.

Once the workflow completes successfully, you can navigate to your repository in Artifactory (docker-dive-repo) and view the docker-dive-image. Under the **Evidence** tab for the latest version, you will find the signed Dive analysis results.

---

### **How It Works: A Step-by-Step Breakdown**

1. **Setup and Checkout**: The workflow begins by setting up the JFrog CLI and checking out the repository code.
2. **Build and Publish Docker Image**: It uses standard docker commands to build an image. The `jf rt docker-push` command then pushes this image to your Artifactory instance and associates it with build information using `jf rt build-publish`.
3. **Run Dive Analysis**: The Dive tool is executed against the newly pushed image. It analyzes the image for inefficiencies and outputs the findings into a structured `dive.json` file.
4. **Generate Optional Markdown Report**: If `ATTACH_OPTIONAL_CUSTOM_MARKDOWN_TO_EVIDENCE` is true, a Python helper script is run to parse the JSON output and create a more human-readable `dive-analysis.md` file.
5. **Attach Signed Evidence**: The final step uses the `jf evd create` command. It takes the `dive.json` file as the official "predicate" and attaches it as evidence to the specific package version in Artifactory. The evidence is signed using the provided `PRIVATE_KEY`, ensuring its authenticity and integrity.

---

### **Key Commands Used**

* **Build Docker Image:**

```
docker build . --file ./examples/dive/Dockerfile --tag $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION
```

* **Run Dive Analysis:**

```
docker run -it --rm -e CI=true \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $(pwd):/output docker.io/wagoodman/dive \
$REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION \
--json /output/dive.json
```

* **Push Docker Image:**

```
jf rt docker-push $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION $REPO_NAME --build-name=$BUILD_NAME --build-number=${{ github.run_number }}
```

* **Attach Evidence:**

```
jf evd create \
--package-name $IMAGE_NAME \
--package-version $VERSION \
--package-repo-name $REPO_NAME \
--key "${{ secrets.PRIVATE_KEY }}" \
--key-alias "${{ vars.EVIDENCE_KEY_ALIAS }}" \
--predicate ./dive.json \
--predicate-type dive/docker-size-scan/v1
```

### **References**

* [Dive Documentation](https://github.com/wagoodman/dive)
* [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management)
* [JFrog CLI Documentation](https://jfrog.com/getcli/)
61 changes: 61 additions & 0 deletions examples/dive/dive_json_to_md.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import json
import sys

def generate_markdown_report(dive_output):
image_info = dive_output.get('image', {})

size_bytes = image_info.get('sizeBytes', 'N/A')
inefficient_bytes = image_info.get('inefficientBytes', 'N/A')
efficiency_score = image_info.get('efficiencyScore', 'N/A')

markdown_report = f"""
## Dive Analysis Report

**Image Size:** `{size_bytes} bytes`

**Inefficient Bytes:** `{inefficient_bytes} bytes`

**Efficiency Score:** `{efficiency_score}`

---
### File References
This section lists the files contributing to inefficiencies in the image.

| File Path | Count | Size (Bytes) |
| :-------- | :---- | :----------- |
"""

file_references = image_info.get('fileReference', [])
for file_ref in file_references:
file_path = file_ref.get('file', 'N/A')
count = file_ref.get('count', 'N/A')
size = file_ref.get('sizeBytes', 'N/A')
markdown_report += f"| {file_path} | {count} | {size} |\n"

markdown_report += "\n---"
return markdown_report

def main(input_file):
# Read JSON input from a file
with open(input_file, 'r') as file:
dive_output = json.load(file)

# Generate the Markdown report
markdown_report = generate_markdown_report(dive_output)

# Define the output file path
output_file = 'dive-analysis.md'

# Write the Markdown report to a file
with open(output_file, 'w') as file:
file.write(markdown_report)

print(f"Markdown report generated successfully and saved to {output_file}!")

if __name__ == '__main__':
if len(sys.argv) != 2:
print("Usage: python dive_json_to_md.py <input_file>")
sys.exit(1)

input_file = sys.argv[1]
main(input_file)
8 changes: 8 additions & 0 deletions examples/trufflehog/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM alpine:latest

WORKDIR /app

# Copy the setting file into the Docker image
COPY vulnerable_setting /app/vulnerable_setting

CMD ["cat", "/app/vulnerable_setting"]
Loading