Skip to content

Commit c796d95

Browse files
committed
test cve fix workflow
1 parent 114f101 commit c796d95

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

.github/renovate-cve-config.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// =============================================================================
2+
// Renovate CVE Remediation Configuration - CodeFlare Operator
3+
// =============================================================================
4+
// This config is used by the CVE Remediation Controller to create fix PRs
5+
// for Go module vulnerabilities.
6+
//
7+
// Environment variables from workflow:
8+
// - RENOVATE_TARGET_REPO: Repository to create PR in
9+
// - RENOVATE_BASE_BRANCH: Branch to target (the cve-fix branch)
10+
// - RENOVATE_PACKAGE_NAME: Vulnerable Go module to update
11+
// - RENOVATE_CVE_ID: CVE identifier
12+
// - RENOVATE_ISSUE_NUMBER: Tracking issue number
13+
// - RENOVATE_ISSUE_REPO: Repository with the tracking issue
14+
// =============================================================================
15+
16+
const prBody = `## Security Update
17+
18+
This PR fixes **${process.env.RENOVATE_CVE_ID || 'CVE'}** by updating \`${process.env.RENOVATE_PACKAGE_NAME || 'package'}\`.
19+
20+
**Tracking Issue:** ${process.env.RENOVATE_ISSUE_REPO || ''}#${process.env.RENOVATE_ISSUE_NUMBER || ''}
21+
22+
---
23+
*Created by [CVE Remediation Controller](https://github.com/project-codeflare/codeflare-operator/blob/main/.github/workflows/cve-controller.yml)*
24+
`;
25+
26+
module.exports = {
27+
platform: 'github',
28+
onboarding: false,
29+
requireConfig: 'ignored',
30+
31+
// Target repo and branch
32+
repositories: [process.env.RENOVATE_TARGET_REPO].filter(Boolean),
33+
baseBranches: [process.env.RENOVATE_BASE_BRANCH].filter(Boolean),
34+
35+
// Go module manager
36+
enabledManagers: ['gomod'],
37+
38+
// Only update the vulnerable package
39+
packageRules: [
40+
{
41+
// Enable updates for the vulnerable package
42+
matchPackagePatterns: [process.env.RENOVATE_PACKAGE_NAME].filter(Boolean),
43+
enabled: true,
44+
recreateWhen: 'always',
45+
rebaseWhen: 'behind-base-branch',
46+
prPriority: 99,
47+
labels: ['security', 'cve-fix', process.env.RENOVATE_CVE_ID].filter(Boolean),
48+
},
49+
{
50+
// Disable everything else
51+
matchPackagePatterns: ['*'],
52+
excludePackagePatterns: [process.env.RENOVATE_PACKAGE_NAME].filter(Boolean),
53+
enabled: false,
54+
},
55+
],
56+
57+
// PR configuration
58+
prTitle: `fix(security): Update ${process.env.RENOVATE_PACKAGE_NAME || 'package'} [${process.env.RENOVATE_CVE_ID || 'CVE'}]`,
59+
prBody: prBody,
60+
branchPrefix: 'cve-fix/',
61+
branchName: `cve-fix/${process.env.RENOVATE_CVE_ID || 'cve'}-${(process.env.RENOVATE_PACKAGE_NAME || 'pkg').split('/').pop()}`.toLowerCase().replace(/[^a-z0-9-/]/g, '-'),
62+
63+
// No dashboard needed for targeted CVE fixes
64+
dependencyDashboard: false,
65+
66+
// No rate limits for security fixes
67+
prHourlyLimit: 0,
68+
prConcurrentLimit: 0,
69+
branchConcurrentLimit: 0,
70+
71+
// Commit message format
72+
commitMessagePrefix: 'fix(security):',
73+
74+
// Go-specific settings
75+
postUpdateOptions: ['gomodTidy'],
76+
77+
logLevel: process.env.LOG_LEVEL || 'info',
78+
};
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# =============================================================================
2+
# CVE Remediation Controller - CodeFlare Operator
3+
# =============================================================================
4+
# This workflow scans the codeflare-operator repository for a specific CVE and
5+
# creates a Renovate PR to fix the vulnerable dependency.
6+
#
7+
# Target: red-hat-data-services/codeflare-operator (downstream only)
8+
# Since codeflare-operator is only shipped in older versions, we only need to
9+
# fix downstream release branches, not main/midstream.
10+
#
11+
# Usage:
12+
# 1. Create a GitHub issue to track the CVE
13+
# 2. Trigger this workflow with:
14+
# - cve_id: The CVE to check for (e.g., CVE-2024-12345)
15+
# - issue_number: The tracking issue number
16+
# - release_tag: The release to check (e.g., v1.14.0)
17+
# 3. The workflow will:
18+
# - Create a fix branch: cve-fix/<cve-id>-<release-tag>
19+
# - Scan the release for the CVE using govulncheck
20+
# - If not affected: comment and close the issue
21+
# - If affected: create a Renovate PR to fix the dependency
22+
#
23+
# PAT Requirements:
24+
# The CVE_CONTROLLER_PAT secret needs repo scope for PR creation
25+
# =============================================================================
26+
27+
name: CVE Remediation Controller
28+
29+
on:
30+
workflow_dispatch:
31+
inputs:
32+
cve_id:
33+
description: 'CVE identifier (e.g., CVE-2024-12345)'
34+
required: true
35+
type: string
36+
issue_number:
37+
description: 'GitHub Issue ID to update with status'
38+
required: true
39+
type: string
40+
release_tag:
41+
description: 'Release tag to base the fix branch from (e.g., v1.14.0)'
42+
required: true
43+
type: string
44+
# -------------------------------------------------------------------------
45+
# Override inputs for testing with forks
46+
# -------------------------------------------------------------------------
47+
repo_override:
48+
description: 'Override target repo for testing (e.g., your-username/codeflare-operator)'
49+
required: false
50+
type: string
51+
issue_repo_override:
52+
description: 'Override issue repo for testing (e.g., your-username/codeflare-operator)'
53+
required: false
54+
type: string
55+
56+
# Ensure only one CVE remediation runs at a time per CVE/release combination
57+
concurrency:
58+
group: cve-operator-${{ github.event.inputs.cve_id }}-${{ github.event.inputs.release_tag }}
59+
cancel-in-progress: false
60+
61+
env:
62+
# Default repository - downstream only for codeflare-operator
63+
TARGET_REPO: ${{ inputs.repo_override || 'red-hat-data-services/codeflare-operator' }}
64+
ISSUE_REPO: ${{ inputs.issue_repo_override || inputs.repo_override || 'red-hat-data-services/codeflare-operator' }}
65+
66+
jobs:
67+
# ===========================================================================
68+
# Job 1: Scan for CVE
69+
# ===========================================================================
70+
scan:
71+
name: Scan for CVE
72+
runs-on: ubuntu-latest
73+
outputs:
74+
vulnerable: ${{ steps.scan.outputs.vulnerable }}
75+
package_name: ${{ steps.scan.outputs.package_name }}
76+
fix_branch: ${{ steps.branch.outputs.fix_branch }}
77+
78+
steps:
79+
# -----------------------------------------------------------------------
80+
# Step 1: Generate branch name from CVE and release tag
81+
# Format: cve-fix/<cve-id>-<release-tag>
82+
# -----------------------------------------------------------------------
83+
- name: Generate fix branch name
84+
id: branch
85+
run: |
86+
# Create branch name: cve-fix/CVE-2024-12345-v1.14.0
87+
CVE_ID="${{ inputs.cve_id }}"
88+
RELEASE_TAG="${{ inputs.release_tag }}"
89+
90+
# Sanitize for branch name (lowercase, replace invalid chars)
91+
FIX_BRANCH="cve-fix/${CVE_ID}-${RELEASE_TAG}"
92+
FIX_BRANCH=$(echo "$FIX_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9./-]/-/g')
93+
94+
echo "fix_branch=${FIX_BRANCH}" >> $GITHUB_OUTPUT
95+
echo "Generated fix branch: ${FIX_BRANCH}"
96+
97+
# -----------------------------------------------------------------------
98+
# Step 2: Create branch from release tag
99+
# -----------------------------------------------------------------------
100+
- name: Create fix branch from release tag
101+
run: |
102+
FIX_BRANCH="${{ steps.branch.outputs.fix_branch }}"
103+
RELEASE_TAG="${{ inputs.release_tag }}"
104+
105+
echo "Creating branch ${FIX_BRANCH} from tag ${RELEASE_TAG}"
106+
107+
git clone https://x-access-token:${{ secrets.CVE_CONTROLLER_PAT }}@github.com/${{ env.TARGET_REPO }}.git repo
108+
cd repo
109+
110+
git config user.email "[email protected]"
111+
git config user.name "CVE Controller"
112+
113+
# Check if branch already exists
114+
if git ls-remote --heads origin "${FIX_BRANCH}" | grep -q .; then
115+
echo "Branch ${FIX_BRANCH} already exists, will use existing branch"
116+
else
117+
echo "Creating new branch from tag ${RELEASE_TAG}"
118+
git fetch --tags
119+
git checkout tags/${RELEASE_TAG} -b "${FIX_BRANCH}"
120+
git push origin "${FIX_BRANCH}"
121+
fi
122+
123+
# -----------------------------------------------------------------------
124+
# Step 3: Checkout the fix branch
125+
# -----------------------------------------------------------------------
126+
- name: Checkout fix branch
127+
uses: actions/checkout@v4
128+
with:
129+
repository: ${{ env.TARGET_REPO }}
130+
ref: ${{ steps.branch.outputs.fix_branch }}
131+
token: ${{ secrets.CVE_CONTROLLER_PAT }}
132+
fetch-depth: 0
133+
134+
# -----------------------------------------------------------------------
135+
# Step 4: Setup Go and install govulncheck
136+
# -----------------------------------------------------------------------
137+
- name: Setup Go
138+
uses: actions/setup-go@v5
139+
with:
140+
go-version-file: './go.mod'
141+
142+
- name: Install govulncheck
143+
run: go install golang.org/x/vuln/cmd/govulncheck@latest
144+
145+
# -----------------------------------------------------------------------
146+
# Step 5: Scan for CVE using govulncheck
147+
# -----------------------------------------------------------------------
148+
- name: Scan for CVE
149+
id: scan
150+
run: |
151+
set +e
152+
153+
CVE_ID="${{ inputs.cve_id }}"
154+
VULNERABLE="false"
155+
PACKAGE_NAME=""
156+
157+
echo "=== Scanning for ${CVE_ID} ==="
158+
159+
# Run govulncheck and capture JSON output
160+
# govulncheck automatically respects vendor directory if present
161+
# The output is streaming JSON - one JSON object per line
162+
echo "Running govulncheck..."
163+
govulncheck -json ./... > /tmp/govulncheck-output.json 2>&1 || true
164+
165+
echo "Full output saved to /tmp/govulncheck-output.json"
166+
167+
# Search for the CVE in the output
168+
# govulncheck outputs streaming JSON with 'osv' field containing OSV entries
169+
# The OSV id is typically GO-XXXX-XXXX, while CVE IDs are in aliases
170+
# We need to parse each line as a separate JSON object
171+
MATCH=$(cat /tmp/govulncheck-output.json | \
172+
jq -c "select(.osv != null) | .osv | select(.id == \"${CVE_ID}\" or (.aliases // [] | index(\"${CVE_ID}\") != null))" 2>/dev/null | \
173+
head -1 || echo "")
174+
175+
if [ -n "$MATCH" ] && [ "$MATCH" != "null" ] && [ "$MATCH" != "" ]; then
176+
VULNERABLE="true"
177+
# Extract the affected module name from the OSV record
178+
# For Go, the affected field contains module info under package.name
179+
PACKAGE_NAME=$(echo "$MATCH" | jq -r '.affected[0].package.name // "unknown"' 2>/dev/null | head -1)
180+
181+
# Debug output
182+
echo "Found vulnerability!"
183+
echo "OSV ID: $(echo "$MATCH" | jq -r '.id')"
184+
echo "Aliases: $(echo "$MATCH" | jq -r '.aliases // [] | join(", ")')"
185+
echo "Affected Package: $PACKAGE_NAME"
186+
fi
187+
188+
echo "vulnerable=${VULNERABLE}" >> $GITHUB_OUTPUT
189+
echo "package_name=${PACKAGE_NAME}" >> $GITHUB_OUTPUT
190+
echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_OUTPUT
191+
192+
echo "=== Scan Complete. Vulnerable: ${VULNERABLE} ==="
193+
194+
# -----------------------------------------------------------------------
195+
# Step 6: Report results
196+
# -----------------------------------------------------------------------
197+
- name: Comment - Not Vulnerable
198+
if: steps.scan.outputs.vulnerable == 'false'
199+
run: |
200+
gh issue comment ${{ inputs.issue_number }} \
201+
--repo ${{ env.ISSUE_REPO }} \
202+
--body "## ✅ CVE Scan Result: Not Vulnerable
203+
204+
| Field | Value |
205+
|-------|-------|
206+
| CVE | ${{ inputs.cve_id }} |
207+
| Release Tag | ${{ inputs.release_tag }} |
208+
| Fix Branch | ${{ steps.branch.outputs.fix_branch }} |
209+
| Scan Time | ${{ steps.scan.outputs.timestamp }} |
210+
211+
The CVE was **not detected** in the dependencies for this release.
212+
213+
Closing this issue."
214+
215+
gh issue close ${{ inputs.issue_number }} --repo ${{ env.ISSUE_REPO }} --reason "not planned"
216+
env:
217+
GH_TOKEN: ${{ secrets.CVE_CONTROLLER_PAT }}
218+
219+
- name: Comment - Vulnerable
220+
if: steps.scan.outputs.vulnerable == 'true'
221+
run: |
222+
gh issue comment ${{ inputs.issue_number }} \
223+
--repo ${{ env.ISSUE_REPO }} \
224+
--body "## ⚠️ CVE Scan Result: Vulnerable
225+
226+
| Field | Value |
227+
|-------|-------|
228+
| CVE | ${{ inputs.cve_id }} |
229+
| Release Tag | ${{ inputs.release_tag }} |
230+
| Fix Branch | ${{ steps.branch.outputs.fix_branch }} |
231+
| Affected Package | \`${{ steps.scan.outputs.package_name }}\` |
232+
233+
Triggering Renovate to create a fix PR..."
234+
env:
235+
GH_TOKEN: ${{ secrets.CVE_CONTROLLER_PAT }}
236+
237+
# ===========================================================================
238+
# Job 2: Create Fix PR
239+
# ===========================================================================
240+
fix:
241+
name: Create Fix PR
242+
needs: scan
243+
if: needs.scan.outputs.vulnerable == 'true'
244+
runs-on: ubuntu-latest
245+
246+
steps:
247+
- name: Checkout (for Renovate config)
248+
uses: actions/checkout@v4
249+
250+
- name: Run Renovate
251+
uses: renovatebot/[email protected]
252+
with:
253+
configurationFile: .github/renovate-cve-config.js
254+
token: ${{ secrets.CVE_CONTROLLER_PAT }}
255+
env:
256+
RENOVATE_TARGET_REPO: ${{ env.TARGET_REPO }}
257+
RENOVATE_BASE_BRANCH: ${{ needs.scan.outputs.fix_branch }}
258+
RENOVATE_PACKAGE_NAME: ${{ needs.scan.outputs.package_name }}
259+
RENOVATE_CVE_ID: ${{ inputs.cve_id }}
260+
RENOVATE_ISSUE_NUMBER: ${{ inputs.issue_number }}
261+
RENOVATE_ISSUE_REPO: ${{ env.ISSUE_REPO }}
262+
RENOVATE_COMPONENT: codeflare-operator
263+
RENOVATE_SCAN_TYPE: go
264+
LOG_LEVEL: debug
265+
266+
- name: Comment - PR Created
267+
run: |
268+
gh issue comment ${{ inputs.issue_number }} \
269+
--repo ${{ env.ISSUE_REPO }} \
270+
--body "## 🔧 Fix PR Initiated
271+
272+
| Field | Value |
273+
|-------|-------|
274+
| Package | \`${{ needs.scan.outputs.package_name }}\` |
275+
| Fix Branch | ${{ needs.scan.outputs.fix_branch }} |
276+
| CVE | ${{ inputs.cve_id }} |
277+
278+
Renovate has been triggered. Check the repository for the new PR."
279+
env:
280+
GH_TOKEN: ${{ secrets.CVE_CONTROLLER_PAT }}
281+
282+
- name: Comment on failure
283+
if: failure()
284+
run: |
285+
gh issue comment ${{ inputs.issue_number }} \
286+
--repo ${{ env.ISSUE_REPO }} \
287+
--body "## ❌ Fix PR Failed
288+
289+
Renovate failed to create a PR. Check the workflow logs:
290+
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
291+
env:
292+
GH_TOKEN: ${{ secrets.CVE_CONTROLLER_PAT }}

0 commit comments

Comments
 (0)