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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/examples/dependabot-alerts-example"
schedule:
interval: "daily"
102 changes: 102 additions & 0 deletions .github/workflows/dependabot-evidence-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: dependabot-evidence-example
on:
workflow_dispatch:

permissions:
id-token: write
contents: read

jobs:
dependabot-evidence-example:
runs-on: ubuntu-latest
env:
REPO_NAME: 'dependabot-docker-local'
IMAGE_NAME: 'dependabot-docker-image'
BUILD_NAME: 'dependabot-evidence-eg'
VERSION: ${{ github.run_number }}
REGISTRY_DOMAIN: ${{ vars.REGISTRY_DOMAIN }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup JFrog CLI
uses: jfrog/setup-jfrog-cli@v4
env:
JF_URL: ${{ vars.ARTIFACTORY_URL }}
JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}

- name: Log in to Artifactory Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.ARTIFACTORY_URL }}
username: ${{ secrets.JF_USER }}
password: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and Push Docker Image to Artifactory
run: |
docker build -f ./examples/dependabot-alerts-example/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=$VERSION

- name: Get Artifact Details
run: |
ARTIFACT_NAME="$REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION"
echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV

IMAGE_ID=$(docker images --format "{{.ID}}" "$ARTIFACT_NAME")
echo "IMAGE_ID=$IMAGE_ID" >> $GITHUB_ENV

IMAGE_SIZE=$(docker images --format "{{.Size}}" "$ARTIFACT_NAME" | sed 's/MB//' | awk '{print $1 * 1024 * 1024}')
echo "IMAGE_SIZE=$IMAGE_SIZE" >> $GITHUB_ENV

echo "SCAN_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"" >> $GITHUB_ENV

- name: Fetch Dependabot Vulnerability Snapshot
id: dependabot_snapshot
env:
GH_TOKEN: ${{ secrets.TOKEN_GIT }} # GitHub Token with 'security_events: read' permission is required
OWNER: ${{ github.repository_owner }}
REPO: ${{ github.event.repository.name }}
run: |
gh api "repos/${OWNER}/${REPO}/dependabot/alerts?state=open" \
--jq '[.[] |
{
packageName: .dependency.package.name,
ecosystem: .dependency.package.ecosystem,
vulnerableVersionRange: .security_vulnerability.vulnerable_version_range,
patchedVersion: (try .security_vulnerability.first_patched_version.identifier // "N/A"),
severity: .security_vulnerability.severity,
ghsaId: .security_advisory.ghsa_id,
cveId: (.security_advisory.cve_id // "N/A"),
advisoryUrl: .html_url,
summary: .security_advisory.summary,
detectedAt: .created_at
}
]' > result.json

jq -n --argjson data "$(cat result.json)" '{ data: $data }' > dependabot.json

- name: Generate and Save Dependabot Markdown Report
run: |
python ./examples/dependabot-alerts-example/markdown_helper.py \
"dependabot.json" \
"dependabot_report.md" \
"$ARTIFACT_NAME" \
"$SCAN_DATE" \
"$IMAGE_ID" \
"$IMAGE_SIZE"

- name: Create Dependabot Evidence
run: |
jf evd create \
--package-name $IMAGE_NAME \
--package-version $VERSION \
--package-repo-name $REPO_NAME \
--key "${{ secrets.TEST_PRVT_KEY }}" \
--key-alias ${{ vars.TEST_PUB_KEY_ALIAS }} \
--predicate ./dependabot.json \
--predicate-type http://Github.com/Dependabot/static-analysis \
--markdown dependabot_report.md
9 changes: 9 additions & 0 deletions examples/dependabot-alerts-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.7-slim-buster

WORKDIR /app

COPY ./examples/dependabot-alerts-example/requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

CMD ["ansible", "--version"]
104 changes: 104 additions & 0 deletions examples/dependabot-alerts-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Dependabot Vulnerability Alerts Evidence Example Workflow

The GitHub Actions workflow, named dependabot-evidence-example.yml, demonstrates how to automate the collection of Dependabot vulnerability alerts and attach them as signed evidence to a Docker image within JFrog Artifactory.

## Overview
The workflow builds a Docker image, fetches open Dependabot vulnerability alerts for the repository, pushes the Docker image to JFrog Artifactory, and attaches the Dependabot alerts as signed evidence to the Docker image package. This workflow's primary goal is to automate the collection of security scan results from Dependabot and associate them directly with the deployed artifact in Artifactory, enhancing traceability and compliance for security posture in your CI/CD pipeline.

## Prerequisites
- JFrog CLI 2.65.0 or above (installed automatically in the workflow)
- Artifactory configured as a Docker registry
- GitHub repository variables: Configure the following variables in your GitHub repository settings
(Settings > Secrets and variables > Actions > Variables)
- `REGISTRY_DOMAIN` (Artifactory Docker registry domain, e.g. `mycompany.jfrog.io`)
- `ARTIFACTORY_URL` (Artifactory base URL)
- `TEST_PUB_KEY_ALIAS` (Key alias for verifying evidence)
- GitHub repository secrets: Configure the following secrets in your GitHub repository settings
(Settings > Secrets and variables > Actions > Repository secrets)
- `ARTIFACTORY_ACCESS_TOKEN` (Artifactory access token)
- `JF_USER` (Artifactory username)
- `TEST_PRVT_KEY` (Private key for signing evidence)
- `TOKEN_GIT` (A GitHub Token with "security_events: read" permission to access Dependabot alerts via the GitHub API)

## Environment Variables Used
- `REGISTRY_DOMAIN` - Docker registry domain
- `REPO_NAME` - Docker repository name
- `IMAGE_NAME` - Docker image name
- `VERSION` - Image version
- `BUILD_NAME` - Name for the build info

## Workflow Steps
1. **Checkout Repository**
- Checks out the source code for the build context.
2. **Setup JFrog CLI**
- Install and Setup the JFrog CLI using the official GitHub Action.
3. **Log in to Artifactory Docker Registry**
- Authenticates Docker with Artifactory for pushing the image.
4. **Set up Docker Buildx**
- Prepares Docker Buildx for advanced build and push operations.
5. **Build and Push Docker Image to Artifactory**
- Builds the Docker image using the provided Dockerfile and tags it for the Artifactory registry.
- Pushes the tagged Docker image to the Artifactory Docker registry using JFrog CLI.
8. **Fetch Dependabot Vulnerability Snapshot**
- Fetchs the snapshot of open Dependabot vulnerability alerts for the repository and outputs the results in JSON format.
9. **Create Dependabot Evidence Using JFrog CLI**
- Attaches the Dependabot vulnerability snapshot as signed evidence to the Docker image package in Artifactory.

## Example Dependabot Vulnerability Alert Data

The Fetch Dependabot Vulnerability Snapshot step retrieves Dependabot alerts and transforms them into a structured JSON format.
- advisoryUrl: Link to the security advisory.
- cveId: Common Vulnerabilities and Exposures identifier (e.g., CVE-2020-1734).
- detectedAt: Timestamp when the vulnerability was detected.
- ecosystem: The package ecosystem (e.g., pip).
- ghsaId: GitHub Security Advisory ID (e.g., GHSA-h39q-95q5-9jfp).
- packageName: The name of the vulnerable package (e.g., ansible).
- patchedVersion: The version where the vulnerability is patched (e.g., 2.9.11, or N/A if not specified).
- severity: The severity level (e.g., high, medium, low).
- summary: A brief summary of the vulnerability.
- vulnerableVersionRange: The version range affected by the vulnerability.

## Key Commands Used

- **Build and Push Docker Image to Artifactory**
```bash
docker build -f ./examples/dependabot-alerts-example/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=$VERSION
```
- **Fetch Dependabot Vulnerability Snapshot**
```bash
gh api "repos/${OWNER}/${REPO}/dependabot/alerts?state=open" \
--jq '[.[] |
{
packageName: .dependency.package.name,
ecosystem: .dependency.package.ecosystem,
vulnerableVersionRange: .security_vulnerability.vulnerable_version_range,
patchedVersion: (try .security_vulnerability.first_patched_version.identifier // "N/A"),
severity: .security_vulnerability.severity,
ghsaId: .security_advisory.ghsa_id,
cveId: (.security_advisory.cve_id // "N/A"),
advisoryUrl: .html_url,
summary: .security_advisory.summary,
detectedAt: .created_at
}
]' > result.json

jq -n --argjson data "$(cat result.json)" '{ data: $data }' > dependabot.json
```
- **Attach Evidence:**
```bash
jf evd create \
--package-name $IMAGE_NAME \
--package-version $VERSION \
--package-repo-name $REPO_NAME \
--key "${{ secrets.TEST_PRVT_KEY }}" \
--key-alias ${{ vars.TEST_PUB_KEY_ALIAS }} \
--predicate ./dependabot.json \
--predicate-type http://Github.com/Dependabot/static-analysis
```

## References
- [Dependabot Documentation](https://docs.github.com/en/rest/dependabot)
- [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management)
- [JFrog CLI Documentation](https://jfrog.com/getcli/)

113 changes: 113 additions & 0 deletions examples/dependabot-alerts-example/markdown_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import json
import sys

def generate_dependabot_markdown_report(json_file_path, artifact_name, scan_date, image_id, image_size):
try:
with open(json_file_path, 'r') as f:
data = json.load(f)
except FileNotFoundError:
return f"Error: The file '{json_file_path}' was not found. Please ensure it exists."
except json.JSONDecodeError:
return f"Error: Could not decode JSON from '{json_file_path}'. Please verify the file's content."

markdown_output = f"# Dependabot Vulnerability Report\n\n"

markdown_output += f"""
**Artifact Name:** `{artifact_name}`

**Scan Date:** `{scan_date}`

**Image ID:** `{image_id}`

**Image Size:** `{image_size}`


"""

alerts_data = data.get("data", [])
alerts_found = bool(alerts_data)

severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "unknown": 0}

for alert in alerts_data:
severity = alert.get("severity", "unknown").lower()
if severity in severity_counts:
severity_counts[severity] += 1
else:
severity_counts["unknown"] += 1

markdown_output += "---\n\n"
markdown_output += "## Overview of Vulnerabilities\n\n"
markdown_output += "| Severity | Count |\n"
markdown_output += "| ------ | ------ |\n"
markdown_output += f"| CRITICAL | {severity_counts['critical']} |\n"
markdown_output += f"| HIGH | {severity_counts['high']} |\n"
markdown_output += f"| MEDIUM | {severity_counts['medium']} |\n"
markdown_output += f"| LOW | {severity_counts['low']} |\n"
markdown_output += f"| UNKNOWN | {severity_counts['unknown']} |\n\n"

markdown_output += "---\n\n"

markdown_output += "## Detected Vulnerabilities by Package\n\n"

if not alerts_found:
markdown_output += "No Dependabot alerts were found in the provided JSON.\n"
else:
for alert in alerts_data:
package_name = alert.get("packageName", "N/A")
summary = alert.get("summary", "No summary provided.")
ecosystem = alert.get("ecosystem", "N/A")
cve_id = alert.get("cveId", "N/A")
ghsa_id = alert.get("ghsaId", "N/A")
severity = alert.get("severity", "N/A").capitalize()
vulnerable_range = alert.get("vulnerableVersionRange", "N/A")
patched_version = alert.get("patchedVersion", "N/A")
advisory_url = alert.get("advisoryUrl", "N/A")
detected_at = alert.get("detectedAt", "N/A")

markdown_output += f"### Vulnerability: **{summary}**\n"
markdown_output += f"- **Package**: `{package_name}` (Ecosystem: `{ecosystem}`)\n"
markdown_output += f"- **Severity**: **{severity}**\n"

if cve_id and cve_id != "N/A":
markdown_output += f"- **CVE ID**: `{cve_id}`\n"
if ghsa_id and ghsa_id != "N/A":
markdown_output += f"- **GHSA ID**: `{ghsa_id}`\n"

markdown_output += f"- **Vulnerable Version Range**: `{vulnerable_range}`\n"
markdown_output += f"- **First Patched Version**: `{patched_version}`\n"
markdown_output += f"- **Detected At**: `{detected_at}`\n"

if advisory_url and advisory_url != "N/A":
markdown_output += f"- **Advisory URL**: <{advisory_url}>\n"
markdown_output += "\n---\n\n"

return markdown_output

if __name__ == "__main__":
if len(sys.argv) != 7:
print("Usage: python markdown_helper.py <path_to_dependabot.json> <output_report.md> <artifact_name> <scan_date> <image_id> <image_size>")
sys.exit(1)

json_file_path = sys.argv[1]
output_markdown_path = sys.argv[2]
artifact_name = sys.argv[3]
scan_date = sys.argv[4]
image_id = sys.argv[5]
image_size = sys.argv[6]

markdown_report = generate_dependabot_markdown_report(
json_file_path,
artifact_name,
scan_date,
image_id,
image_size
)

try:
with open(output_markdown_path, 'w') as outfile:
outfile.write(markdown_report)
print(f"Dependabot vulnerability report successfully generated and saved to '{output_markdown_path}'")
except IOError as e:
print(f"Error: Could not write the report to '{output_markdown_path}'. Reason: {e}")
sys.exit(1)
1 change: 1 addition & 0 deletions examples/dependabot-alerts-example/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ansible==2.9.9