Skip to content

Commit 0f96de2

Browse files
committed
Build: Add CI workflow for KEK validation
Add a pull request workflow to run KEK validation checks in CI and surface failures early during review. Signed-off-by: Doug Flick dougflick@microsoft.com
1 parent 63c6557 commit 0f96de2

1 file changed

Lines changed: 264 additions & 0 deletions

File tree

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# This workflow validates KEK update files in pull requests to ensure they have
2+
# valid cryptographic signatures and expected payloads before merging.
3+
#
4+
# Copyright (c) Microsoft Corporation.
5+
# SPDX-License-Identifier: BSD-2-Clause-Patent
6+
name: Validate KEK Updates
7+
8+
on:
9+
pull_request:
10+
branches: [ "main" ]
11+
paths:
12+
- 'PostSignedObjects/KEK/**/*.bin'
13+
- 'PreSignedObjects/KEK/**/*.bin'
14+
15+
permissions:
16+
contents: read
17+
issues: write
18+
pull-requests: write
19+
20+
jobs:
21+
validate-kek:
22+
name: Validate KEK Update Files
23+
runs-on: ubuntu-latest
24+
25+
steps:
26+
- name: Checkout PR
27+
uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0 # Need full history to compare with base branch
30+
31+
- name: Set up Python
32+
uses: actions/setup-python@v6
33+
with:
34+
python-version: 3.12
35+
cache: 'pip'
36+
cache-dependency-path: pip-requirements.txt
37+
38+
- name: Install Pip Dependencies
39+
run: |
40+
python -m pip install --upgrade pip
41+
pip install -r pip-requirements.txt
42+
43+
- name: Get Changed KEK Files
44+
id: changed-files
45+
run: |
46+
# Get list of changed .bin files in KEK directories
47+
git fetch origin ${{ github.base_ref }}
48+
CHANGED_FILES=$(git diff --name-only --diff-filter=AM origin/${{ github.base_ref }}...HEAD | grep -E '(PostSignedObjects|PreSignedObjects)/KEK/.*\.bin$' || echo "")
49+
50+
if [ -z "$CHANGED_FILES" ]; then
51+
echo "No KEK files changed"
52+
echo "has_changes=false" >> $GITHUB_OUTPUT
53+
else
54+
echo "Changed KEK files:"
55+
echo "$CHANGED_FILES"
56+
echo "has_changes=true" >> $GITHUB_OUTPUT
57+
# Save changed files to a temporary file
58+
echo "$CHANGED_FILES" > changed_kek_files.txt
59+
fi
60+
61+
- name: Validate Changed KEK Files
62+
id: validate
63+
if: steps.changed-files.outputs.has_changes == 'true'
64+
run: |
65+
VALIDATION_FAILED=0
66+
VALIDATION_RESULTS_DIR="kek_validation_results"
67+
mkdir -p "$VALIDATION_RESULTS_DIR"
68+
69+
# Accumulators for PR comment data
70+
ALL_OUTPUT=""
71+
ALL_JSON=""
72+
ALL_HASHES=""
73+
74+
echo "## KEK Validation Results" >> $GITHUB_STEP_SUMMARY
75+
echo "" >> $GITHUB_STEP_SUMMARY
76+
77+
while IFS= read -r file; do
78+
if [ -f "$file" ]; then
79+
echo "Validating: $file"
80+
BASENAME=$(basename "$file" .bin)
81+
OUTPUT_JSON="$VALIDATION_RESULTS_DIR/${BASENAME}_validation.json"
82+
83+
# Compute SHA-256 of the binary
84+
FILE_HASH=$(sha256sum "$file" | awk '{print $1}')
85+
ALL_HASHES="${ALL_HASHES}${file}: ${FILE_HASH}\n"
86+
87+
# Run validation and capture both exit code and stdout/stderr
88+
CMD="python scripts/validate_kek.py \"$file\" -o \"$OUTPUT_JSON\" -q"
89+
CMD_OUTPUT=$(python scripts/validate_kek.py "$file" -o "$OUTPUT_JSON" -q 2>&1) || true
90+
CMD_EXIT=$?
91+
ALL_OUTPUT="${ALL_OUTPUT}\$ ${CMD}\n${CMD_OUTPUT}\n\n"
92+
93+
if [ -f "$OUTPUT_JSON" ]; then
94+
# Parse JSON to check both signature and payload
95+
SIGNATURE_VALID=$(jq -r '.result.valid' "$OUTPUT_JSON")
96+
PAYLOAD_VALID=$(jq -r '.result.payload_hash_valid' "$OUTPUT_JSON")
97+
JSON_CONTENT=$(cat "$OUTPUT_JSON")
98+
ALL_JSON="${ALL_JSON}### ${file}\n\`\`\`json\n${JSON_CONTENT}\n\`\`\`\n\n"
99+
100+
if [ "$SIGNATURE_VALID" = "true" ] && [ "$PAYLOAD_VALID" = "true" ]; then
101+
echo "✅ **PASS**: \`$file\`" >> $GITHUB_STEP_SUMMARY
102+
echo " - Cryptographic Signature: ✅ VALID" >> $GITHUB_STEP_SUMMARY
103+
echo " - Expected Payload: ✅ True" >> $GITHUB_STEP_SUMMARY
104+
elif [ "$SIGNATURE_VALID" = "true" ] && [ "$PAYLOAD_VALID" = "false" ]; then
105+
echo "⚠️ **WARNING**: \`$file\`" >> $GITHUB_STEP_SUMMARY
106+
echo " - Cryptographic Signature: ✅ VALID" >> $GITHUB_STEP_SUMMARY
107+
echo " - Expected Payload: ⚠️ False (non-standard payload)" >> $GITHUB_STEP_SUMMARY
108+
PAYLOAD_HASH=$(jq -r '.result.payload_hash' "$OUTPUT_JSON")
109+
echo " - Payload Hash: \`$PAYLOAD_HASH\`" >> $GITHUB_STEP_SUMMARY
110+
# Don't fail on payload mismatch, just warn
111+
else
112+
echo "❌ **FAIL**: \`$file\`" >> $GITHUB_STEP_SUMMARY
113+
echo " - Cryptographic Signature: ❌ INVALID" >> $GITHUB_STEP_SUMMARY
114+
echo " - Expected Payload: $([ "$PAYLOAD_VALID" = "true" ] && echo "✅ True" || echo "⚠️ False")" >> $GITHUB_STEP_SUMMARY
115+
VALIDATION_FAILED=1
116+
fi
117+
else
118+
echo "❌ **FAIL**: \`$file\` - Validation script failed" >> $GITHUB_STEP_SUMMARY
119+
ALL_JSON="${ALL_JSON}### ${file}\nNo JSON output produced.\n\n"
120+
VALIDATION_FAILED=1
121+
fi
122+
echo "" >> $GITHUB_STEP_SUMMARY
123+
fi
124+
done < changed_kek_files.txt
125+
126+
# Save comment body to a file for the comment step
127+
COMMENT_FILE="kek_validation_comment.md"
128+
echo '<!-- kek-validation-comment -->' > "$COMMENT_FILE"
129+
echo '❌ **KEK Validation Failed**' >> "$COMMENT_FILE"
130+
echo '' >> "$COMMENT_FILE"
131+
echo '### File Hashes (SHA-256)' >> "$COMMENT_FILE"
132+
echo '```' >> "$COMMENT_FILE"
133+
printf '%b' "$ALL_HASHES" >> "$COMMENT_FILE"
134+
echo '```' >> "$COMMENT_FILE"
135+
echo '' >> "$COMMENT_FILE"
136+
echo '### Command Output' >> "$COMMENT_FILE"
137+
echo '```' >> "$COMMENT_FILE"
138+
printf '%b' "$ALL_OUTPUT" >> "$COMMENT_FILE"
139+
echo '```' >> "$COMMENT_FILE"
140+
echo '' >> "$COMMENT_FILE"
141+
echo '### Validation Results' >> "$COMMENT_FILE"
142+
printf '%b' "$ALL_JSON" >> "$COMMENT_FILE"
143+
echo '' >> "$COMMENT_FILE"
144+
echo '### Reproduce Locally' >> "$COMMENT_FILE"
145+
echo '```' >> "$COMMENT_FILE"
146+
echo 'pip install -r pip-requirements.txt' >> "$COMMENT_FILE"
147+
echo 'python scripts/validate_kek.py <path-to-kek-bin-file> -v' >> "$COMMENT_FILE"
148+
echo '```' >> "$COMMENT_FILE"
149+
150+
# Append detailed results to step summary
151+
echo "" >> $GITHUB_STEP_SUMMARY
152+
echo "### File Hashes (SHA-256)" >> $GITHUB_STEP_SUMMARY
153+
echo '```' >> $GITHUB_STEP_SUMMARY
154+
printf '%b' "$ALL_HASHES" >> $GITHUB_STEP_SUMMARY
155+
echo '```' >> $GITHUB_STEP_SUMMARY
156+
echo "" >> $GITHUB_STEP_SUMMARY
157+
echo "### Command Output" >> $GITHUB_STEP_SUMMARY
158+
echo '```' >> $GITHUB_STEP_SUMMARY
159+
printf '%b' "$ALL_OUTPUT" >> $GITHUB_STEP_SUMMARY
160+
echo '```' >> $GITHUB_STEP_SUMMARY
161+
echo "" >> $GITHUB_STEP_SUMMARY
162+
printf '%b' "$ALL_JSON" >> $GITHUB_STEP_SUMMARY
163+
echo "" >> $GITHUB_STEP_SUMMARY
164+
echo "### Reproduce Locally" >> $GITHUB_STEP_SUMMARY
165+
echo '```' >> $GITHUB_STEP_SUMMARY
166+
echo "pip install -r pip-requirements.txt" >> $GITHUB_STEP_SUMMARY
167+
echo "python scripts/validate_kek.py <path-to-kek-bin-file> -v" >> $GITHUB_STEP_SUMMARY
168+
echo '```' >> $GITHUB_STEP_SUMMARY
169+
170+
# Upload validation results as artifact
171+
if [ -d "$VALIDATION_RESULTS_DIR" ] && [ "$(ls -A $VALIDATION_RESULTS_DIR)" ]; then
172+
echo "Uploading validation results..."
173+
fi
174+
175+
# Exit with error if any validation failed
176+
if [ $VALIDATION_FAILED -eq 1 ]; then
177+
echo "::error::One or more KEK files have invalid cryptographic signatures"
178+
exit 1
179+
fi
180+
181+
- name: Upload Validation Results
182+
if: steps.changed-files.outputs.has_changes == 'true' && always()
183+
uses: actions/upload-artifact@v4
184+
with:
185+
name: kek-validation-results
186+
path: kek_validation_results/
187+
retention-days: 30
188+
189+
- name: Generate Token
190+
id: app-token
191+
if: steps.changed-files.outputs.has_changes == 'true' && always()
192+
continue-on-error: true
193+
uses: actions/create-github-app-token@v3
194+
with:
195+
app-id: ${{ vars.MU_ACCESS_APP_ID }}
196+
private-key: ${{ secrets.MU_ACCESS_APP_PRIVATE_KEY }}
197+
owner: ${{ github.repository_owner }}
198+
199+
- name: Comment on PR
200+
if: steps.changed-files.outputs.has_changes == 'true' && failure() && steps.app-token.outputs.token
201+
continue-on-error: true
202+
uses: actions/github-script@v7
203+
with:
204+
github-token: ${{ steps.app-token.outputs.token }}
205+
script: |
206+
const fs = require('fs');
207+
const marker = '<!-- kek-validation-comment -->';
208+
const body = fs.readFileSync('kek_validation_comment.md', 'utf8')
209+
+ '\n\n_Updated: ' + new Date().toISOString() + '_';
210+
try {
211+
const { data: comments } = await github.rest.issues.listComments({
212+
owner: context.repo.owner,
213+
repo: context.repo.repo,
214+
issue_number: context.issue.number
215+
});
216+
const existing = comments.find(c => c.body.includes(marker));
217+
if (existing) {
218+
await github.rest.issues.updateComment({
219+
owner: context.repo.owner,
220+
repo: context.repo.repo,
221+
comment_id: existing.id,
222+
body
223+
});
224+
} else {
225+
await github.rest.issues.createComment({
226+
owner: context.repo.owner,
227+
repo: context.repo.repo,
228+
issue_number: context.issue.number,
229+
body
230+
});
231+
}
232+
} catch (error) {
233+
core.warning(`Unable to post PR comment: ${error.message}`)
234+
}
235+
236+
- name: Update PR Comment on Success
237+
if: steps.changed-files.outputs.has_changes == 'true' && success() && steps.app-token.outputs.token
238+
continue-on-error: true
239+
uses: actions/github-script@v7
240+
with:
241+
github-token: ${{ steps.app-token.outputs.token }}
242+
script: |
243+
const marker = '<!-- kek-validation-comment -->';
244+
try {
245+
const { data: comments } = await github.rest.issues.listComments({
246+
owner: context.repo.owner,
247+
repo: context.repo.repo,
248+
issue_number: context.issue.number
249+
});
250+
const existing = comments.find(c => c.body.includes(marker));
251+
if (existing) {
252+
const body = marker + '\n\u2705 **KEK Validation Passed**\n\n'
253+
+ 'All KEK update files have valid cryptographic signatures.\n\n'
254+
+ '_Updated: ' + new Date().toISOString() + '_';
255+
await github.rest.issues.updateComment({
256+
owner: context.repo.owner,
257+
repo: context.repo.repo,
258+
comment_id: existing.id,
259+
body
260+
});
261+
}
262+
} catch (error) {
263+
core.warning(`Unable to update PR comment: ${error.message}`)
264+
}

0 commit comments

Comments
 (0)