Skip to content

Commit 05e05b4

Browse files
Merge pull request #13 from aparnatk53/main
Support for Dependabot Evidence Integration
2 parents 8e797f8 + 20cbbbf commit 05e05b4

6 files changed

Lines changed: 335 additions & 0 deletions

File tree

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "pip"
4+
directory: "/examples/dependabot-alerts-example"
5+
schedule:
6+
interval: "daily"
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: dependabot-evidence-example
2+
on:
3+
workflow_dispatch:
4+
5+
permissions:
6+
id-token: write
7+
contents: read
8+
9+
jobs:
10+
dependabot-evidence-example:
11+
runs-on: ubuntu-latest
12+
env:
13+
REPO_NAME: 'dependabot-docker-local'
14+
IMAGE_NAME: 'dependabot-docker-image'
15+
BUILD_NAME: 'dependabot-evidence-eg'
16+
VERSION: ${{ github.run_number }}
17+
REGISTRY_DOMAIN: ${{ vars.REGISTRY_DOMAIN }}
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v4
22+
23+
- name: Setup JFrog CLI
24+
uses: jfrog/setup-jfrog-cli@v4
25+
env:
26+
JF_URL: ${{ vars.ARTIFACTORY_URL }}
27+
JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}
28+
29+
- name: Log in to Artifactory Docker Registry
30+
uses: docker/login-action@v3
31+
with:
32+
registry: ${{ vars.ARTIFACTORY_URL }}
33+
username: ${{ secrets.JF_USER }}
34+
password: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }}
35+
36+
- name: Set up Docker Buildx
37+
uses: docker/setup-buildx-action@v3
38+
39+
- name: Build and Push Docker Image to Artifactory
40+
run: |
41+
docker build -f ./examples/dependabot-alerts-example/Dockerfile . --tag $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION
42+
jf rt docker-push $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION $REPO_NAME --build-name=$BUILD_NAME --build-number=$VERSION
43+
44+
- name: Get Artifact Details
45+
run: |
46+
ARTIFACT_NAME="$REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION"
47+
echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV
48+
49+
IMAGE_ID=$(docker images --format "{{.ID}}" "$ARTIFACT_NAME")
50+
echo "IMAGE_ID=$IMAGE_ID" >> $GITHUB_ENV
51+
52+
IMAGE_SIZE=$(docker images --format "{{.Size}}" "$ARTIFACT_NAME" | sed 's/MB//' | awk '{print $1 * 1024 * 1024}')
53+
echo "IMAGE_SIZE=$IMAGE_SIZE" >> $GITHUB_ENV
54+
55+
echo "SCAN_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"" >> $GITHUB_ENV
56+
57+
- name: Fetch Dependabot Vulnerability Snapshot
58+
id: dependabot_snapshot
59+
env:
60+
GH_TOKEN: ${{ secrets.TOKEN_GIT }} # GitHub Token with 'security_events: read' permission is required
61+
OWNER: ${{ github.repository_owner }}
62+
REPO: ${{ github.event.repository.name }}
63+
run: |
64+
gh api "repos/${OWNER}/${REPO}/dependabot/alerts?state=open" \
65+
--jq '[.[] |
66+
{
67+
packageName: .dependency.package.name,
68+
ecosystem: .dependency.package.ecosystem,
69+
vulnerableVersionRange: .security_vulnerability.vulnerable_version_range,
70+
patchedVersion: (try .security_vulnerability.first_patched_version.identifier // "N/A"),
71+
severity: .security_vulnerability.severity,
72+
ghsaId: .security_advisory.ghsa_id,
73+
cveId: (.security_advisory.cve_id // "N/A"),
74+
advisoryUrl: .html_url,
75+
summary: .security_advisory.summary,
76+
detectedAt: .created_at
77+
}
78+
]' > result.json
79+
80+
jq -n --argjson data "$(cat result.json)" '{ data: $data }' > dependabot.json
81+
82+
- name: Generate and Save Dependabot Markdown Report
83+
run: |
84+
python ./examples/dependabot-alerts-example/markdown_helper.py \
85+
"dependabot.json" \
86+
"dependabot_report.md" \
87+
"$ARTIFACT_NAME" \
88+
"$SCAN_DATE" \
89+
"$IMAGE_ID" \
90+
"$IMAGE_SIZE"
91+
92+
- name: Create Dependabot Evidence
93+
run: |
94+
jf evd create \
95+
--package-name $IMAGE_NAME \
96+
--package-version $VERSION \
97+
--package-repo-name $REPO_NAME \
98+
--key "${{ secrets.TEST_PRVT_KEY }}" \
99+
--key-alias ${{ vars.TEST_PUB_KEY_ALIAS }} \
100+
--predicate ./dependabot.json \
101+
--predicate-type http://Github.com/Dependabot/static-analysis \
102+
--markdown dependabot_report.md
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM python:3.7-slim-buster
2+
3+
WORKDIR /app
4+
5+
COPY ./examples/dependabot-alerts-example/requirements.txt .
6+
7+
RUN pip install --no-cache-dir -r requirements.txt
8+
9+
CMD ["ansible", "--version"]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Dependabot Vulnerability Alerts Evidence Example Workflow
2+
3+
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.
4+
5+
## Overview
6+
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.
7+
8+
## Prerequisites
9+
- JFrog CLI 2.65.0 or above (installed automatically in the workflow)
10+
- Artifactory configured as a Docker registry
11+
- GitHub repository variables: Configure the following variables in your GitHub repository settings
12+
(Settings > Secrets and variables > Actions > Variables)
13+
- `REGISTRY_DOMAIN` (Artifactory Docker registry domain, e.g. `mycompany.jfrog.io`)
14+
- `ARTIFACTORY_URL` (Artifactory base URL)
15+
- `TEST_PUB_KEY_ALIAS` (Key alias for verifying evidence)
16+
- GitHub repository secrets: Configure the following secrets in your GitHub repository settings
17+
(Settings > Secrets and variables > Actions > Repository secrets)
18+
- `ARTIFACTORY_ACCESS_TOKEN` (Artifactory access token)
19+
- `JF_USER` (Artifactory username)
20+
- `TEST_PRVT_KEY` (Private key for signing evidence)
21+
- `TOKEN_GIT` (A GitHub Token with "security_events: read" permission to access Dependabot alerts via the GitHub API)
22+
23+
## Environment Variables Used
24+
- `REGISTRY_DOMAIN` - Docker registry domain
25+
- `REPO_NAME` - Docker repository name
26+
- `IMAGE_NAME` - Docker image name
27+
- `VERSION` - Image version
28+
- `BUILD_NAME` - Name for the build info
29+
30+
## Workflow Steps
31+
1. **Checkout Repository**
32+
- Checks out the source code for the build context.
33+
2. **Setup JFrog CLI**
34+
- Install and Setup the JFrog CLI using the official GitHub Action.
35+
3. **Log in to Artifactory Docker Registry**
36+
- Authenticates Docker with Artifactory for pushing the image.
37+
4. **Set up Docker Buildx**
38+
- Prepares Docker Buildx for advanced build and push operations.
39+
5. **Build and Push Docker Image to Artifactory**
40+
- Builds the Docker image using the provided Dockerfile and tags it for the Artifactory registry.
41+
- Pushes the tagged Docker image to the Artifactory Docker registry using JFrog CLI.
42+
8. **Fetch Dependabot Vulnerability Snapshot**
43+
- Fetchs the snapshot of open Dependabot vulnerability alerts for the repository and outputs the results in JSON format.
44+
9. **Create Dependabot Evidence Using JFrog CLI**
45+
- Attaches the Dependabot vulnerability snapshot as signed evidence to the Docker image package in Artifactory.
46+
47+
## Example Dependabot Vulnerability Alert Data
48+
49+
The Fetch Dependabot Vulnerability Snapshot step retrieves Dependabot alerts and transforms them into a structured JSON format.
50+
- advisoryUrl: Link to the security advisory.
51+
- cveId: Common Vulnerabilities and Exposures identifier (e.g., CVE-2020-1734).
52+
- detectedAt: Timestamp when the vulnerability was detected.
53+
- ecosystem: The package ecosystem (e.g., pip).
54+
- ghsaId: GitHub Security Advisory ID (e.g., GHSA-h39q-95q5-9jfp).
55+
- packageName: The name of the vulnerable package (e.g., ansible).
56+
- patchedVersion: The version where the vulnerability is patched (e.g., 2.9.11, or N/A if not specified).
57+
- severity: The severity level (e.g., high, medium, low).
58+
- summary: A brief summary of the vulnerability.
59+
- vulnerableVersionRange: The version range affected by the vulnerability.
60+
61+
## Key Commands Used
62+
63+
- **Build and Push Docker Image to Artifactory**
64+
```bash
65+
docker build -f ./examples/dependabot-alerts-example/Dockerfile . --tag $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION
66+
jf rt docker-push $REGISTRY_DOMAIN/$REPO_NAME/$IMAGE_NAME:$VERSION $REPO_NAME --build-name=$BUILD_NAME --build-number=$VERSION
67+
```
68+
- **Fetch Dependabot Vulnerability Snapshot**
69+
```bash
70+
gh api "repos/${OWNER}/${REPO}/dependabot/alerts?state=open" \
71+
--jq '[.[] |
72+
{
73+
packageName: .dependency.package.name,
74+
ecosystem: .dependency.package.ecosystem,
75+
vulnerableVersionRange: .security_vulnerability.vulnerable_version_range,
76+
patchedVersion: (try .security_vulnerability.first_patched_version.identifier // "N/A"),
77+
severity: .security_vulnerability.severity,
78+
ghsaId: .security_advisory.ghsa_id,
79+
cveId: (.security_advisory.cve_id // "N/A"),
80+
advisoryUrl: .html_url,
81+
summary: .security_advisory.summary,
82+
detectedAt: .created_at
83+
}
84+
]' > result.json
85+
86+
jq -n --argjson data "$(cat result.json)" '{ data: $data }' > dependabot.json
87+
```
88+
- **Attach Evidence:**
89+
```bash
90+
jf evd create \
91+
--package-name $IMAGE_NAME \
92+
--package-version $VERSION \
93+
--package-repo-name $REPO_NAME \
94+
--key "${{ secrets.TEST_PRVT_KEY }}" \
95+
--key-alias ${{ vars.TEST_PUB_KEY_ALIAS }} \
96+
--predicate ./dependabot.json \
97+
--predicate-type http://Github.com/Dependabot/static-analysis
98+
```
99+
100+
## References
101+
- [Dependabot Documentation](https://docs.github.com/en/rest/dependabot)
102+
- [JFrog Evidence Management](https://jfrog.com/help/r/jfrog-artifactory-documentation/evidence-management)
103+
- [JFrog CLI Documentation](https://jfrog.com/getcli/)
104+
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import json
2+
import sys
3+
4+
def generate_dependabot_markdown_report(json_file_path, artifact_name, scan_date, image_id, image_size):
5+
try:
6+
with open(json_file_path, 'r') as f:
7+
data = json.load(f)
8+
except FileNotFoundError:
9+
return f"Error: The file '{json_file_path}' was not found. Please ensure it exists."
10+
except json.JSONDecodeError:
11+
return f"Error: Could not decode JSON from '{json_file_path}'. Please verify the file's content."
12+
13+
markdown_output = f"# Dependabot Vulnerability Report\n\n"
14+
15+
markdown_output += f"""
16+
**Artifact Name:** `{artifact_name}`
17+
18+
**Scan Date:** `{scan_date}`
19+
20+
**Image ID:** `{image_id}`
21+
22+
**Image Size:** `{image_size}`
23+
24+
25+
"""
26+
27+
alerts_data = data.get("data", [])
28+
alerts_found = bool(alerts_data)
29+
30+
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0, "unknown": 0}
31+
32+
for alert in alerts_data:
33+
severity = alert.get("severity", "unknown").lower()
34+
if severity in severity_counts:
35+
severity_counts[severity] += 1
36+
else:
37+
severity_counts["unknown"] += 1
38+
39+
markdown_output += "---\n\n"
40+
markdown_output += "## Overview of Vulnerabilities\n\n"
41+
markdown_output += "| Severity | Count |\n"
42+
markdown_output += "| ------ | ------ |\n"
43+
markdown_output += f"| CRITICAL | {severity_counts['critical']} |\n"
44+
markdown_output += f"| HIGH | {severity_counts['high']} |\n"
45+
markdown_output += f"| MEDIUM | {severity_counts['medium']} |\n"
46+
markdown_output += f"| LOW | {severity_counts['low']} |\n"
47+
markdown_output += f"| UNKNOWN | {severity_counts['unknown']} |\n\n"
48+
49+
markdown_output += "---\n\n"
50+
51+
markdown_output += "## Detected Vulnerabilities by Package\n\n"
52+
53+
if not alerts_found:
54+
markdown_output += "No Dependabot alerts were found in the provided JSON.\n"
55+
else:
56+
for alert in alerts_data:
57+
package_name = alert.get("packageName", "N/A")
58+
summary = alert.get("summary", "No summary provided.")
59+
ecosystem = alert.get("ecosystem", "N/A")
60+
cve_id = alert.get("cveId", "N/A")
61+
ghsa_id = alert.get("ghsaId", "N/A")
62+
severity = alert.get("severity", "N/A").capitalize()
63+
vulnerable_range = alert.get("vulnerableVersionRange", "N/A")
64+
patched_version = alert.get("patchedVersion", "N/A")
65+
advisory_url = alert.get("advisoryUrl", "N/A")
66+
detected_at = alert.get("detectedAt", "N/A")
67+
68+
markdown_output += f"### Vulnerability: **{summary}**\n"
69+
markdown_output += f"- **Package**: `{package_name}` (Ecosystem: `{ecosystem}`)\n"
70+
markdown_output += f"- **Severity**: **{severity}**\n"
71+
72+
if cve_id and cve_id != "N/A":
73+
markdown_output += f"- **CVE ID**: `{cve_id}`\n"
74+
if ghsa_id and ghsa_id != "N/A":
75+
markdown_output += f"- **GHSA ID**: `{ghsa_id}`\n"
76+
77+
markdown_output += f"- **Vulnerable Version Range**: `{vulnerable_range}`\n"
78+
markdown_output += f"- **First Patched Version**: `{patched_version}`\n"
79+
markdown_output += f"- **Detected At**: `{detected_at}`\n"
80+
81+
if advisory_url and advisory_url != "N/A":
82+
markdown_output += f"- **Advisory URL**: <{advisory_url}>\n"
83+
markdown_output += "\n---\n\n"
84+
85+
return markdown_output
86+
87+
if __name__ == "__main__":
88+
if len(sys.argv) != 7:
89+
print("Usage: python markdown_helper.py <path_to_dependabot.json> <output_report.md> <artifact_name> <scan_date> <image_id> <image_size>")
90+
sys.exit(1)
91+
92+
json_file_path = sys.argv[1]
93+
output_markdown_path = sys.argv[2]
94+
artifact_name = sys.argv[3]
95+
scan_date = sys.argv[4]
96+
image_id = sys.argv[5]
97+
image_size = sys.argv[6]
98+
99+
markdown_report = generate_dependabot_markdown_report(
100+
json_file_path,
101+
artifact_name,
102+
scan_date,
103+
image_id,
104+
image_size
105+
)
106+
107+
try:
108+
with open(output_markdown_path, 'w') as outfile:
109+
outfile.write(markdown_report)
110+
print(f"Dependabot vulnerability report successfully generated and saved to '{output_markdown_path}'")
111+
except IOError as e:
112+
print(f"Error: Could not write the report to '{output_markdown_path}'. Reason: {e}")
113+
sys.exit(1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ansible==2.9.9

0 commit comments

Comments
 (0)