Skip to content

Commit 1053958

Browse files
authored
ci: test e2e with harbor (#31)
1 parent 17097a8 commit 1053958

File tree

7 files changed

+239
-8
lines changed

7 files changed

+239
-8
lines changed

.github/workflows/ci-e2e.yaml

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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+
id: minikube
26+
run: |
27+
minikube start --driver=docker --cpus=2 --memory=2G --force
28+
echo "ip=$(minikube ip)" >> "$GITHUB_OUTPUT"
29+
30+
- name: Helm repos
31+
run: |
32+
helm repo add bitnami https://charts.bitnami.com/bitnami
33+
helm repo add sysdig https://charts.sysdig.com
34+
helm repo update
35+
36+
- name: Install Harbor (NodePort)
37+
id: harbor
38+
env:
39+
MINIKUBE_IP: ${{ steps.minikube.outputs.ip }}
40+
run: |
41+
HARBOR_URL="https://${MINIKUBE_IP}:30003"
42+
43+
helm install harbor bitnami/harbor \
44+
--namespace harbor \
45+
--create-namespace \
46+
--set trivy.enabled=false \
47+
--set service.type=NodePort \
48+
--set service.nodePorts.http=30002 \
49+
--set service.nodePorts.https=30003 \
50+
--set externalURL="${HARBOR_URL}"
51+
52+
HARBOR_USERNAME=admin
53+
HARBOR_PASSWORD=$(kubectl get secret -n harbor harbor-core-envvars -o jsonpath='{.data.HARBOR_ADMIN_PASSWORD}' | base64 -d)
54+
echo "::add-mask::${HARBOR_PASSWORD}"
55+
56+
echo "url=${HARBOR_URL}" >> "$GITHUB_OUTPUT"
57+
echo "username=${HARBOR_USERNAME}" >> "$GITHUB_OUTPUT"
58+
echo "password=${HARBOR_PASSWORD}" >> "$GITHUB_OUTPUT"
59+
60+
- name: Build adapter image with Nix
61+
run: nix build .#harbor-adapter-docker
62+
63+
- name: Load image into Docker & Minikube
64+
id: image_in_docker
65+
run: |
66+
PULL_STRING=$(docker load -i ./result -q | tail -n1 | cut -d: -f2- | tr -d ' ')
67+
REPOSITORY=$(echo "${PULL_STRING}" | cut -d: -f1)
68+
TAG=$(echo "${PULL_STRING}" | cut -d: -f2)
69+
70+
echo "pull_string=${PULL_STRING}" >> "$GITHUB_OUTPUT"
71+
echo "repository=${REPOSITORY}" >> "$GITHUB_OUTPUT"
72+
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
73+
74+
- name: Load image into Minikube
75+
env:
76+
PULL_STRING: ${{ steps.image_in_docker.outputs.pull_string }}
77+
run: |
78+
minikube image load "${PULL_STRING}"
79+
80+
- name: Deploy Sysdig Harbor scanner (use local image)
81+
env:
82+
REPOSITORY: ${{ steps.image_in_docker.outputs.repository }}
83+
TAG: ${{ steps.image_in_docker.outputs.tag }}
84+
SECURE_API_TOKEN: ${{ secrets.KUBELAB_SECURE_API_TOKEN }}
85+
SECURE_URL: ${{ secrets.SECURE_URL || 'https://secure.sysdig.com' }}
86+
run: |
87+
helm install harbor-scanner-sysdig-secure sysdig/harbor-scanner-sysdig-secure \
88+
--wait \
89+
--timeout 300s \
90+
--namespace harbor \
91+
--create-namespace \
92+
--set image.repository=$REPOSITORY \
93+
--set image.tag=$TAG \
94+
--set image.pullPolicy=Never \
95+
--set sysdig.secure.apiToken="$SECURE_API_TOKEN" \
96+
--set sysdig.secure.url="$SECURE_URL" \
97+
--set cliScanning.image="quay.io/sysdig/sysdig-cli-scanner:1.22.6"
98+
99+
- name: Wait for Harbor to be ready
100+
run: |
101+
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=harbor -n harbor --timeout=600s
102+
kubectl get pods -n harbor -o wide
103+
104+
- name: Log in with harbor-cli
105+
env:
106+
HARBOR_URL: ${{ steps.harbor.outputs.url }}
107+
HARBOR_USERNAME: ${{ steps.harbor.outputs.username }}
108+
HARBOR_PASSWORD: ${{ steps.harbor.outputs.password }}
109+
run: |
110+
harbor login "$HARBOR_URL" --username "$HARBOR_USERNAME" --password "$HARBOR_PASSWORD"
111+
112+
- name: Register scanner via Harbor API and set as default
113+
run: |
114+
harbor scanner create \
115+
--name "Sysdig-Local" \
116+
--description "Sysdig Scanner" \
117+
--url "http://harbor-scanner-sysdig-secure.harbor.svc.cluster.local:5000" \
118+
--skip-cert-verification \
119+
--auth None
120+
121+
harbor scanner set-default "Sysdig-Local"
122+
123+
- name: Push sample image
124+
id: image_in_harbor
125+
env:
126+
MINIKUBE_IP: ${{ steps.minikube.outputs.ip }}
127+
HARBOR_URL: ${{ steps.harbor.outputs.url }}
128+
HARBOR_USERNAME: ${{ steps.harbor.outputs.username }}
129+
HARBOR_PASSWORD: ${{ steps.harbor.outputs.password }}
130+
run: |
131+
REPO="alpine"
132+
PROJECT="library"
133+
NEW_TAG="test"
134+
135+
skopeo \
136+
--policy <(echo '{"default":[{"type":"insecureAcceptAnything"}]}') \
137+
copy "docker://${REPO}:latest" \
138+
"docker://${MINIKUBE_IP}:30003/${PROJECT}/${REPO}:${NEW_TAG}" \
139+
--dest-tls-verify=false \
140+
--dest-creds="${HARBOR_USERNAME}:${HARBOR_PASSWORD}"
141+
142+
DIGEST=$(harbor artifact list "${PROJECT}"/"${REPO}" -o json | jq -r .Payload[].digest)
143+
144+
echo "repo=${REPO}" >> "$GITHUB_OUTPUT"
145+
echo "project=${PROJECT}" >> "$GITHUB_OUTPUT"
146+
echo "tag=${NEW_TAG}" >> "$GITHUB_OUTPUT"
147+
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
148+
149+
- name: Trigger scan
150+
env:
151+
PROJECT: ${{ steps.image_in_harbor.outputs.project }}
152+
REPO: ${{ steps.image_in_harbor.outputs.repo }}
153+
DIGEST: ${{ steps.image_in_harbor.outputs.digest }}
154+
run: |
155+
harbor artifact scan start "${PROJECT}/${REPO}@${DIGEST}" -v
156+
157+
- name: Fetch logs from CLI Scanner
158+
run: |
159+
for i in {1..6}; do kubectl get pods -n harbor -l created-by=harbor-scanner-sysdig-secure -o name | grep -q . && break; sleep 5; done
160+
kubectl wait -n harbor --for=condition=ContainersReady pod -l created-by=harbor-scanner-sysdig-secure --timeout=300s
161+
kubectl logs -n harbor -l created-by=harbor-scanner-sysdig-secure --follow
162+
163+
- name: Check if Vulnerability report is generated in Harbor
164+
env:
165+
PROJECT: ${{ steps.image_in_harbor.outputs.project }}
166+
REPO: ${{ steps.image_in_harbor.outputs.repo }}
167+
DIGEST: ${{ steps.image_in_harbor.outputs.digest }}
168+
HARBOR_USERNAME: ${{ steps.harbor.outputs.username }}
169+
HARBOR_PASSWORD: ${{ steps.harbor.outputs.password }}
170+
HARBOR_URL: ${{ steps.harbor.outputs.url }}
171+
run: |
172+
for i in $(seq 1 30); do
173+
REPORT=$(
174+
curl -sk -u "$HARBOR_USERNAME:$HARBOR_PASSWORD" \
175+
-H 'Accept: application/json' \
176+
"${HARBOR_URL}/api/v2.0/projects/${PROJECT}/repositories/${REPO}/artifacts/${DIGEST}/additions/vulnerabilities"
177+
)
178+
179+
GENERATED_AT=$(echo "$REPORT" | jq -r '."application/vnd.security.vulnerability.report; version=1.1".generated_at')
180+
181+
if [ -n "$GENERATED_AT" ] && [ "$GENERATED_AT" != "null" ]; then
182+
echo "Scan completed successfully, vulnerability report is available ✅"
183+
echo "Report details: $REPORT"
184+
exit 0
185+
else
186+
echo "Polling attempt ${i}/30: Vulnerability report not yet available."
187+
fi
188+
189+
sleep 10
190+
done
191+
192+
echo "Scan did not complete in time ❌"
193+
exit 1
194+
- name: Delete cluster
195+
if: always()
196+
run: |
197+
minikube delete || true

docker.nix

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
{ dockerTools, harbor-adapter }:
1+
{
2+
dockerTools,
3+
harbor-adapter,
4+
cacert,
5+
bash,
6+
curl,
7+
coreutils,
8+
}:
29
dockerTools.buildLayeredImage {
310
name = "sysdiglabs/harbor-scanner-sysdig-secure";
411
tag = harbor-adapter.version;
5-
contents = [ harbor-adapter ];
12+
contents = [
13+
harbor-adapter
14+
cacert
15+
];
616

717
# https://github.com/moby/moby/blob/46f7ab808b9504d735d600e259ca0723f76fb164/image/spec/spec.md#image-json-field-descriptions
818
config = {
@@ -11,5 +21,9 @@ dockerTools.buildLayeredImage {
1121
ExposedPorts = {
1222
"5000" = { };
1323
};
24+
Env = [
25+
"SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt"
26+
"NIX_SSL_CERT_FILE=${cacert}/etc/ssl/certs/ca-bundle.crt"
27+
];
1428
};
1529
}

flake.nix

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242
pre-commit
4343
sd
4444
trivy
45+
minikube
46+
kubernetes-helm
47+
kubectl
48+
skopeo
49+
harbor-cli
4550
];
4651

4752
inputsFrom = [

package.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{ buildGoModule }:
22
buildGoModule {
33
pname = "harbor-scanner-sysdig-secure";
4-
version = "0.8.1";
4+
version = "0.8.2";
55
vendorHash = "sha256-NF1GsthdOJCiAorBPRRXtfOzDlSfmXCJYQxPbnf3rBw=";
66
src = ./.;
77
subPackages = [

pkg/scanner/inline_adapter.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (i *inlineAdapter) buildJob(name string, req harbor.ScanRequest) *batchv1.J
129129
ValueFrom: nil,
130130
})
131131
envVars = appendLocalEnvVar(envVars, "NO_PROXY")
132-
cmdString := fmt.Sprintf("/home/nonroot/sysdig-cli-scanner -a %s --skiptlsverify --output-json=output.json ", i.secureURL)
132+
cmdString := fmt.Sprintf("/home/nonroot/sysdig-cli-scanner -a %s --console-log --skiptlsverify --output-json=output.json ", i.secureURL)
133133
// Add skiptlsverify if insecure
134134
if !i.verifySSL {
135135
cmdString += "--skiptlsverify "
@@ -169,11 +169,19 @@ func (i *inlineAdapter) buildJob(name string, req harbor.ScanRequest) *batchv1.J
169169
return &batchv1.Job{
170170
ObjectMeta: metav1.ObjectMeta{
171171
Name: name,
172+
Labels: map[string]string{
173+
"created-by": "harbor-scanner-sysdig-secure",
174+
},
172175
},
173176
Spec: batchv1.JobSpec{
174177
TTLSecondsAfterFinished: &i.jobTTL,
175178
BackoffLimit: &backoffLimit,
176179
Template: corev1.PodTemplateSpec{
180+
ObjectMeta: metav1.ObjectMeta{
181+
Labels: map[string]string{
182+
"created-by": "harbor-scanner-sysdig-secure",
183+
},
184+
},
177185
Spec: corev1.PodSpec{
178186
RestartPolicy: corev1.RestartPolicyNever,
179187
SecurityContext: podSecurityContext,

pkg/scanner/inline_adapter_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,19 @@ func job() *batchv1.Job {
211211
ObjectMeta: metav1.ObjectMeta{
212212
Name: resourceName,
213213
Namespace: namespace,
214+
Labels: map[string]string{
215+
"created-by": "harbor-scanner-sysdig-secure",
216+
},
214217
},
215218
Spec: batchv1.JobSpec{
216219
TTLSecondsAfterFinished: &jobTTL,
217220
BackoffLimit: &backoffLimit,
218221
Template: corev1.PodTemplateSpec{
222+
ObjectMeta: metav1.ObjectMeta{
223+
Labels: map[string]string{
224+
"created-by": "harbor-scanner-sysdig-secure",
225+
},
226+
},
219227
Spec: corev1.PodSpec{
220228
RestartPolicy: corev1.RestartPolicyNever,
221229
Containers: []corev1.Container{
@@ -225,7 +233,7 @@ func job() *batchv1.Job {
225233
Command: []string{"/busybox/sh"},
226234
Args: []string{
227235
"-c",
228-
"/home/nonroot/sysdig-cli-scanner -a https://secure.sysdig.com --skiptlsverify --output-json=output.json pull://harbor.sysdig-demo.zone/sysdig/agent:9.7.0@an image digest; RC=$?; if [ $RC -eq 1 ]; then exit 0; else exit $RC; fi",
236+
"/home/nonroot/sysdig-cli-scanner -a https://secure.sysdig.com --console-log --skiptlsverify --output-json=output.json pull://harbor.sysdig-demo.zone/sysdig/agent:9.7.0@an image digest; RC=$?; if [ $RC -eq 1 ]; then exit 0; else exit $RC; fi",
229237
},
230238
Env: []corev1.EnvVar{
231239
{

pkg/secure/client.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package secure
22

33
import (
4-
"crypto/tls"
54
"encoding/json"
65
"errors"
76
"fmt"
@@ -37,7 +36,7 @@ func NewClient(apiToken string, secureURL string, verifySSL bool) Client {
3736
transport := http.DefaultTransport.(*http.Transport).Clone()
3837

3938
if !verifySSL {
40-
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
39+
transport.TLSClientConfig.InsecureSkipVerify = true
4140
}
4241

4342
return &client{
@@ -224,7 +223,7 @@ func (s *client) GetVulnerabilities(shaDigest string) (VulnerabilityReport, erro
224223
return result, err
225224
}
226225
if err = json.Unmarshal(body, &checkScanResultResponse); err != nil {
227-
return result, err
226+
return result, fmt.Errorf("error unmarshalling body response %s: %w", string(body), err)
228227
}
229228

230229
statusMap := map[string]bool{

0 commit comments

Comments
 (0)