-
Notifications
You must be signed in to change notification settings - Fork 0
208 lines (188 loc) · 9.18 KB
/
Copy pathagent-host-maintenance.yml
File metadata and controls
208 lines (188 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
name: Agent Host Image Maintenance
# Rebuilds the agentweaver-agent-host image weekly to pick up OS-level patches,
# runs a Trivy CVE scan (HIGH/CRITICAL gate), and pushes to GHCR.
# Also triggered on any Dockerfile change so security posture is validated on every PR.
on:
schedule:
# Every Monday at 03:00 UTC
- cron: "0 3 * * 1"
push:
branches: [dev, main]
paths:
- "apps/Agentweaver.AgentHost/Dockerfile"
pull_request:
paths:
- "apps/Agentweaver.AgentHost/Dockerfile"
workflow_dispatch:
inputs:
push_to_acr:
description: "Push rebuilt image to GHCR"
type: boolean
default: true
permissions:
contents: read
security-events: write # for uploading Trivy SARIF results
id-token: write # for actions/attest-build-provenance (Sigstore/OIDC)
attestations: write # for actions/attest-build-provenance
packages: write # for pushing images to GHCR
env:
GHCR: ghcr.io
IMAGE: agentweaver-agent-host
jobs:
build-and-scan:
name: Build, Scan & Push
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.tag.outputs.tag }}
trivy_gate_failed: ${{ steps.trivy_gate_status.outputs.failed }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Determine image tag
id: tag
run: |
# Weekly maintenance builds get a date-stamped tag; push/PR builds get the SHA.
if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="maintenance-$(date -u +%Y%m%d)"
else
TAG="sha-$(git rev-parse --short HEAD)"
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "Building image tag: ${TAG}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build image (local, no push yet)
uses: docker/build-push-action@v6
with:
context: .
file: apps/Agentweaver.AgentHost/Dockerfile
push: false
load: true
tags: ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
IMAGE_TAG=${{ steps.tag.outputs.tag }}
GIT_SHA=${{ github.sha }}
# ── Trivy CVE scan ────────────────────────────────────────────────────────
# Pinned to the full commit SHA for v0.36.0 (not the mutable 'master' branch)
# so a compromised/altered upstream action can't silently change scan
# behavior. v0.36.0 is the first clean release tag after the March 2026
# trivy-action supply-chain incident (CVE-2026-33634); update this pin
# deliberately via a reviewed PR, not Dependabot auto-merge.
#
# Trivy's SARIF format + exit-code combination has a known bug where
# .trivyignore entries are not reliably honored before the exit-code
# check runs (https://github.com/aquasecurity/trivy/discussions/9487),
# so the actual HIGH/CRITICAL gate is enforced here via `format: table`
# (exit-code 1). The SARIF step below is upload-only (exit-code 0) and
# never gates the build — it exists purely to populate the Security tab.
- name: Run Trivy vulnerability scan (gate)
id: trivy_gate
continue-on-error: true
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }}
format: table
severity: HIGH,CRITICAL
exit-code: "1" # Fail the build on HIGH/CRITICAL CVEs
ignore-unfixed: true # Only flag CVEs that have a fix available
trivyignores: apps/Agentweaver.AgentHost/.trivyignore
- name: Record Trivy gate outcome
if: always()
id: trivy_gate_status
run: echo "failed=${{ steps.trivy_gate.outcome == 'failure' }}" >> "$GITHUB_OUTPUT"
- name: Run Trivy vulnerability scan (SARIF for Security tab)
if: always()
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }}
format: sarif
output: trivy-results.sarif
severity: HIGH,CRITICAL
exit-code: "0" # Non-gating - the table step above is the real gate
ignore-unfixed: true
trivyignores: apps/Agentweaver.AgentHost/.trivyignore
- name: Upload Trivy SARIF to GitHub Security tab
if: always() # Upload even on scan failure so results are visible
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: trivy-results.sarif
# ── Push to GHCR (schedule, dev/main push, or manual dispatch with push=true) ──
- name: Log in to GHCR
if: >
steps.trivy_gate.outcome == 'success' &&
(
github.event_name == 'schedule' ||
(github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')) ||
(github.event_name == 'workflow_dispatch' && inputs.push_to_acr == true)
)
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push to GHCR
id: push
if: >
steps.trivy_gate.outcome == 'success' &&
(
github.event_name == 'schedule' ||
(github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')) ||
(github.event_name == 'workflow_dispatch' && inputs.push_to_acr == true)
)
run: |
FULL_IMAGE="ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE }}"
docker tag ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }} "${FULL_IMAGE}:${{ steps.tag.outputs.tag }}"
PUSH_OUTPUT=$(docker push "${FULL_IMAGE}:${{ steps.tag.outputs.tag }}")
echo "${PUSH_OUTPUT}"
DIGEST=$(echo "${PUSH_OUTPUT}" | grep -oP 'digest:\s*\Ksha256:[a-f0-9]+')
if [ -z "${DIGEST}" ]; then
echo "::error::could not parse pushed image digest from docker push output"
exit 1
fi
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
# Also update the 'patched' rolling tag so clusters can reference a known-good base
docker tag ${{ env.IMAGE }}:${{ steps.tag.outputs.tag }} "${FULL_IMAGE}:patched"
docker push "${FULL_IMAGE}:patched"
echo "✅ Pushed ${FULL_IMAGE}:${{ steps.tag.outputs.tag }} (${DIGEST})"
echo "✅ Updated rolling tag: ${FULL_IMAGE}:patched"
# ── Build provenance attestation ─────────────────────────────────────────
# 'prov-<sha>' (and 'patched') are ordinary, mutable GHCR tags on their own:
# anyone with registry write access could re-point them at a different
# digest later, with no cryptographic binding back to *this* verified
# GitHub Actions run. This generates a Sigstore-backed SLSA provenance
# attestation for the exact digest just pushed, and stores it alongside
# the image in GHCR (OCI referrers) so it can be verified independently
# of tag mutability (e.g. `gh attestation verify oci://... --owner ...`).
# Pinned to the full commit SHA for v4.1.1, not a floating major-version tag.
- name: Attest build provenance
if: steps.push.outcome == 'success'
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-name: ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
- name: Summary
if: always()
run: |
echo "## Agent Host Maintenance" >> "$GITHUB_STEP_SUMMARY"
echo "| Field | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|-------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Image | \`${{ env.IMAGE }}:${{ steps.tag.outputs.tag }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| Trigger | ${{ github.event_name }} |" >> "$GITHUB_STEP_SUMMARY"
echo "| Commit | \`${{ github.sha }}\` |" >> "$GITHUB_STEP_SUMMARY"
echo "| Trivy severity gate | HIGH, CRITICAL (unfixed only) |" >> "$GITHUB_STEP_SUMMARY"
enforce-trivy-gate:
name: Enforce Trivy HIGH/CRITICAL gate
needs: build-and-scan
runs-on: ubuntu-latest
if: always() && needs.build-and-scan.result == 'success'
steps:
- name: Fail when the gate scan found fixable vulnerabilities
run: |
if [ "${{ needs.build-and-scan.outputs.trivy_gate_failed }}" = "true" ]; then
echo "::error::Trivy found HIGH/CRITICAL vulnerabilities in ${{ env.IMAGE }}:${{ needs.build-and-scan.outputs.image_tag }}. SARIF was uploaded successfully; review the GitHub Security tab for details."
exit 1
fi
echo "Trivy gate passed for ${{ env.IMAGE }}:${{ needs.build-and-scan.outputs.image_tag }}."