Skip to content

Commit 9201d9e

Browse files
committed
ci: test e2e with harbor
1 parent 17097a8 commit 9201d9e

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

.github/workflows/ci-e2e.yaml

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
name: e2e-harbor-integration
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
branches:
7+
- master
8+
9+
jobs:
10+
e2e-test:
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
shell: nix develop --command bash -v {0}
15+
timeout-minutes: 60
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Install nix
22+
uses: DeterminateSystems/nix-installer-action@main
23+
24+
- name: Start Minikube (Docker driver)
25+
run: |
26+
minikube start --driver=docker --cpus=2 --memory=2G --force
27+
28+
- name: Helm repos
29+
run: |
30+
helm repo add bitnami https://charts.bitnami.com/bitnami
31+
helm repo add sysdig https://charts.sysdig.com
32+
helm repo update
33+
34+
- name: Install Harbor (NodePort)
35+
run: |
36+
export MINIKUBE_IP=$(minikube ip)
37+
helm install harbor bitnami/harbor \
38+
--namespace harbor \
39+
--create-namespace \
40+
--set service.type=NodePort \
41+
--set service.nodePorts.http=30002 \
42+
--set service.nodePorts.https=30003 \
43+
--set externalURL=https://$MINIKUBE_IP:30003
44+
45+
- name: Build adapter image with Nix
46+
run: nix build .#harbor-adapter-docker
47+
48+
- name: Load image into Docker & Minikube
49+
id: image_in_docker
50+
run: |
51+
PULL_STRING=$(docker load -i ./result -q | cut -d: -f2- | tr -d ' ')
52+
REPOSITORY=$(echo "${PULL_STRING}" | cut -d: -f1)
53+
TAG=$(echo "${PULL_STRING}" | cut -d: -f2)
54+
echo "pull_string=${PULL_STRING}" >> "$GITHUB_OUTPUT"
55+
echo "repository=${REPOSITORY}" >> "$GITHUB_OUTPUT"
56+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
57+
58+
- name: Load image into Minikube
59+
run: |
60+
minikube image load ${{ steps.image_in_docker.outputs.pull_string }}
61+
62+
- name: Deploy Sysdig Harbor scanner (use local image)
63+
env:
64+
SECURE_API_TOKEN: ${{ secrets.SECURE_API_TOKEN }}
65+
SECURE_URL: ${{ secrets.SECURE_URL }}
66+
run: |
67+
helm install harbor-scanner-sysdig-secure sysdig/harbor-scanner-sysdig-secure \
68+
--wait \
69+
--timeout 300s \
70+
--namespace harbor \
71+
--create-namespace \
72+
--set image.repository=${{ steps.image_in_docker.outputs.repository }} \
73+
--set image.tag=${{ steps.image_in_docker.outputs.tag }} \
74+
--set image.pullPolicy=Never \
75+
--set sysdig.secure.apiToken="$SECURE_API_TOKEN" \
76+
--set sysdig.secure.url="$SECURE_URL" \
77+
--set cliScanning.image="quay.io/sysdig/sysdig-cli-scanner:1.22.6"
78+
79+
- name: Wait for Harbor to be ready
80+
run: |
81+
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=harbor -n harbor --timeout=600s
82+
kubectl get pods -n harbor -o wide
83+
84+
- name: Register scanner via Harbor API and set as default
85+
id: reg
86+
run: |
87+
set -euo pipefail
88+
89+
MINIKUBE_IP=$(minikube ip)
90+
HARBOR_URL="https://${MINIKUBE_IP}:30003"
91+
92+
# Get admin password from Bitnami secret
93+
HARBOR_PASSWORD=$(kubectl get secret -n harbor harbor-core-envvars \
94+
-o jsonpath='{.data.HARBOR_ADMIN_PASSWORD}' | base64 -d)
95+
echo "::add-mask::${HARBOR_PASSWORD}"
96+
97+
# Create scanner registration
98+
curl -sk -u "admin:${HARBOR_PASSWORD}" -H 'Content-Type: application/json' \
99+
-d '{
100+
"name": "Sysdig-Local",
101+
"description": "Scanner registrado por CI",
102+
"url": "http://harbor-scanner-sysdig-secure.harbor.svc.cluster.local:5000",
103+
"skip_cert_verify": true,
104+
"use_internal_addr": true,
105+
"disabled": false
106+
}' \
107+
"${HARBOR_URL}/api/v2.0/scanners"
108+
109+
# Find registration UUID by name
110+
REG_ID=$(curl -sk -u "admin:${HARBOR_PASSWORD}" \
111+
"${HARBOR_URL}/api/v2.0/scanners" \
112+
| jq -r '.[] | select(.name=="Sysdig-Local") | .uuid')
113+
echo "reg_id=${REG_ID}" >> "$GITHUB_OUTPUT"
114+
115+
# Set as default
116+
curl -sk -u "admin:${HARBOR_PASSWORD}" -H 'Content-Type: application/json' \
117+
-X PATCH "${HARBOR_URL}/api/v2.0/scanners/${REG_ID}" \
118+
-d '{"is_default": true}'
119+
120+
- name: Push sample image
121+
run: |
122+
MINIKUBE_IP=$(minikube ip)
123+
HARBOR_URL="https://${MINIKUBE_IP}:30003"
124+
HARBOR_PASSWORD=$(kubectl get secret -n harbor harbor-core-envvars \
125+
-o jsonpath='{.data.HARBOR_ADMIN_PASSWORD}' | base64 -d)
126+
127+
# Push alpine:test sin tocar el daemon de Docker (NodePort con cert autofirmado)
128+
mkdir -p /etc/containers
129+
echo '{"default":[{"type":"insecureAcceptAnything"}]}' > /etc/containers/policy.json
130+
skopeo copy docker://alpine:latest \
131+
"docker://${MINIKUBE_IP}:30003/library/alpine:test" \
132+
--dest-tls-verify=false \
133+
--dest-creds="admin:${HARBOR_PASSWORD}"
134+
135+
- name: Trigger scan
136+
env:
137+
REG_ID: ${{ steps.reg.outputs.reg_id }}
138+
run: |
139+
set -euo pipefail
140+
141+
MINIKUBE_IP=$(minikube ip)
142+
HARBOR_URL="https://${MINIKUBE_IP}:30003"
143+
HARBOR_USER=admin
144+
HARBOR_PASSWORD=$(kubectl get secret -n harbor harbor-core-envvars \
145+
-o jsonpath='{.data.HARBOR_ADMIN_PASSWORD}' | base64 -d)
146+
147+
REPO="alpine"
148+
PROJECT="library"
149+
TAG="test"
150+
151+
# 0) Sanity: el proyecto usa el scanner esperado (hereda el system default si no hay override)
152+
echo "Project-level scanner:"
153+
curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" \
154+
"$HARBOR_URL/api/v2.0/projects/${PROJECT}/scanner" | jq .
155+
156+
# 1) Resuelve el DIGEST del tag que acabas de subir (evita ambigüedades con manifest-lists)
157+
DIGEST=$(
158+
curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" \
159+
"$HARBOR_URL/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts?with_tag=true" \
160+
| jq -r --arg TAG "$TAG" '
161+
.[] | select(any(.tags[]?; .name==$TAG)) | .digest' | head -n1
162+
)
163+
if [ -z "${DIGEST}" ]; then
164+
echo "No encuentro el digest para ${PROJECT}/${REPO}:${TAG} ❌"
165+
exit 1
166+
fi
167+
echo "Digest de ${PROJECT}/${REPO}:${TAG} -> ${DIGEST}"
168+
169+
# 2) Dispara el escaneo (espera HTTP 202)
170+
HTTP=$(curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" -o /dev/null -w "%{http_code}" \
171+
-X POST "$HARBOR_URL/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts/${DIGEST}/scan")
172+
echo "Trigger scan -> HTTP $HTTP"
173+
if [ "$HTTP" -ne 202 ]; then
174+
echo "No se pudo disparar el escaneo (HTTP $HTTP) ❌"
175+
exit 1
176+
fi
177+
178+
# 3) Función helper para imprimir log del job si aparece un report en el overview
179+
show_scan_log() {
180+
# Intenta extraer un report_id del overview si existe
181+
local RID
182+
RID=$(
183+
curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" \
184+
"$HARBOR_URL/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts/${DIGEST}?with_scan_overview=true" \
185+
| jq -r '
186+
(.scan_overview // {}) as $o
187+
| ($o | to_entries | .[0].value.report_id) // empty'
188+
)
189+
if [ -n "${RID}" ] ; then
190+
echo "---- scan log (últimas líneas) ----"
191+
curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" \
192+
"$HARBOR_URL/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts/${DIGEST}/scan/${RID}/log" \
193+
| tail -n 60 || true
194+
echo "-----------------------------------"
195+
fi
196+
}
197+
198+
# 4) Poll del estado:
199+
# - Primero mira scan_overview (ambas MIME keys posibles)
200+
# - Si sigue vacío, intenta el endpoint /additions/vulnerabilities (200 cuando haya reporte)
201+
for i in $(seq 1 30); do
202+
STATUS=$(
203+
curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" \
204+
"$HARBOR_URL/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts/${DIGEST}?with_scan_overview=true" \
205+
| jq -r '
206+
(.scan_overview // {}) as $o
207+
| $o["application/vnd.security.vulnerability.report; version=1.1"].scan_status
208+
// $o["application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"].scan_status
209+
// ([$o[]? | .scan_status] | first)
210+
// empty'
211+
)
212+
213+
echo "Scan status: ${STATUS:-<none>}"
214+
215+
case "${STATUS}" in
216+
Success) echo "Scan completed successfully ✅"; exit 0;;
217+
Error) echo "Scan failed ❌"; show_scan_log; exit 1;;
218+
Running|Pending|"") :
219+
# fallback: ¿ya hay reporte de vulnerabilidades disponible?
220+
VULN_HTTP=$(
221+
curl -sk -u "$HARBOR_USER:$HARBOR_PASSWORD" -o /dev/null -w "%{http_code}" \
222+
-H 'Accept: application/vnd.security.vulnerability.report; version=1.1' \
223+
"$HARBOR_URL/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts/${DIGEST}/additions/vulnerabilities"
224+
)
225+
if [ "$VULN_HTTP" -eq 200 ]; then
226+
echo "Vulnerability report disponible (additions) ✅"
227+
exit 0
228+
fi
229+
;;
230+
esac
231+
232+
# saca logs a mitad de camino para diagnosticar silencios
233+
if [ $i -eq 6 ] || [ $i -eq 12 ]; then show_scan_log; fi
234+
sleep 10
235+
done
236+
237+
echo "Scan did not complete in time ❌"
238+
show_scan_log
239+
exit 1

flake.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
pre-commit
4343
sd
4444
trivy
45+
minikube
46+
kubernetes-helm
47+
kubectl
48+
skopeo
4549
];
4650

4751
inputsFrom = [

0 commit comments

Comments
 (0)