-
Notifications
You must be signed in to change notification settings - Fork 0
419 lines (359 loc) · 19.6 KB
/
Copy pathfsc-harness-overlays.yml
File metadata and controls
419 lines (359 loc) · 19.6 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# Bewaakt dat de compose-overlays van de lokale FSC-harnessen blijven passen op hun basis.
#
# De hostnet-overlay spiegelt elke service uit docker-compose.yaml: `network_mode: host`,
# `extra_hosts` en een botsvrije poort op 127.0.0.1. Drie dingen kunnen daar stil misgaan:
#
# 1. Iemand voegt een service aan de basis toe en vergeet de overlay. Die service vraagt dan het
# default-netwerk terug, en omdat de overlay dat netwerk met `!reset` weghaalt, valt de hele
# stack om — pas bij `up`, op de machine van de volgende gebruiker.
# 2. Iemand voegt een listener toe die niet op loopback bindt. In een gedeelde netns staat die
# daarmee op élke interface van de machine, inclusief de componenten die zonder authenticatie
# draaien (controller met AUTHN_TYPE=none, postgres met harness-credentials).
# 3. Iemand voegt een service toe die `/pki` leest onder een vaste UID, zonder keep-id. Die faalt
# onder rootless podman met `permission denied` op de privékey.
#
# De federatie-opstelling (demo/environment/federatie/) legt daar een VIERDE laag op die alle peers
# in één netns zet. Die laag is de laatste en dus beslissend; wat daar geïntroduceerd wordt, is in
# de drie-bestands-merge onzichtbaar. Vandaar twee jobs die dezelfde checks draaien — via
# .github/scripts/merge-guard.sh, zodat hun dekking niet uiteen kan lopen — plus, in de tweede,
# de eis die alleen in een federatie bestaat: de peers mogen elkaars poorten niet claimen.
#
# Beide jobs renderen alleen de merge (geen containers). `config -q` alleen zou een halve overlay
# zonder morren accepteren.
name: FSC-harness overlays
on:
pull_request:
types: [opened, synchronize, reopened]
# De workflow staat in zijn eigen pad-filter: anders komt een verzwakking van de guard binnen
# zonder dat de guard zelf ooit gedraaid heeft.
paths: ['demo/environment/**', 'compose.yaml', 'compose.podman*.yaml', 'demo/*.sh', 'demo/genereer-magazijnen.py', 'toxiproxy/proxies.json', '.github/workflows/fsc-harness-overlays.yml', '.github/scripts/merge-guard.sh']
push:
branches: [main]
paths: ['demo/environment/**', 'compose.yaml', 'compose.podman*.yaml', 'demo/*.sh', 'demo/genereer-magazijnen.py', 'toxiproxy/proxies.json', '.github/workflows/fsc-harness-overlays.yml', '.github/scripts/merge-guard.sh']
# LET OP: `pull_request`, niet `pull_request_target`, en read-only zonder secrets. Beide jobs
# `source`n demo/environment/federatie/peers.env respectievelijk renderen compose-bestanden uit de
# PR — dat is PR-gestuurde uitvoering, net als de bash-unittests en de docker build/run in
# `harness-scripts`. Dat is alleen aanvaardbaar zolang deze workflow geen schrijfrechten, geen
# secrets en geen cache heeft. Wijzig geen van drieën.
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
hostnet-overlay-past-op-de-basis:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Elke service op de gedeelde netns, elke listener op loopback
run: |
set -euo pipefail
fail=0
peers=0
overlays=0
for peer in demo/environment/*/deploy/local; do
[ -f "$peer/docker-compose.yaml" ] || continue
peers=$((peers + 1))
if [ ! -f "$peer/docker-compose.podman-hostnet.yaml" ]; then
echo "FOUT: $peer heeft geen docker-compose.podman-hostnet.yaml — verwijderd of hernoemd?"
fail=1
continue
fi
overlays=$((overlays + 1))
# PKI_DIR/HOST_UID komen normaal uit .env (gitignored). De waarden doen er niet toe:
# `config` rendert alleen, het mount niets en controleert geen paden.
if ! (cd "$peer" && PKI_DIR=../../pki HOST_UID=1000 HOST_GID=1000 \
docker compose -f docker-compose.yaml \
-f docker-compose.podman.yaml \
-f docker-compose.podman-hostnet.yaml config --format json) \
| "$GITHUB_WORKSPACE/.github/scripts/merge-guard.sh" "$peer (hostnet-merge)" --postgres; then
fail=1
fi
done
if [ "$peers" -eq 0 ] || [ "$overlays" -ne "$peers" ]; then
echo "FOUT: $overlays van $peers harnessen hebben een hostnet-overlay — guard meet niet wat hij hoort te meten."
fail=1
fi
# Servicenamen en paden komen uit de PR en gaan daarom niet in een ::error-annotatie:
# een `%0A` daarin zou een tweede workflow-commando starten. De details staan hierboven.
if [ "$fail" -ne 0 ]; then
echo "::error::De hostnet-overlays passen niet meer op hun basis. Zie de log hierboven."
fi
exit "$fail"
federatie-overlay-past-op-de-peers:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Peer-blokken botsen niet en elke listener staat op loopback
run: |
set -euo pipefail
FED=demo/environment/federatie
GUARD="$GITHUB_WORKSPACE/.github/scripts/merge-guard.sh"
[ -d "$FED/compose" ] || { echo "FOUT: $FED/compose ontbreekt."; exit 1; }
# shellcheck source=/dev/null
. "$FED/peers.env"
fail=0
alle=""
aantal_peers=0
for peer in $GASTHEER $GASTEN; do
lokaal="demo/environment/$peer/deploy/local"
overlay="$FED/compose/$peer.yaml"
aantal_peers=$((aantal_peers + 1))
if [ ! -f "$overlay" ]; then
echo "FOUT: peer '$peer' staat in peers.env maar heeft geen $overlay."
fail=1
continue
fi
merged=$(cd "$lokaal" && PKI_DIR=../../pki HOST_UID=1000 HOST_GID=1000 \
docker compose -f docker-compose.yaml \
-f docker-compose.podman.yaml \
-f docker-compose.podman-hostnet.yaml \
-f "../../../federatie/compose/$peer.yaml" config --format json)
# De gastheer draait de gedeelde postgres; bij de gasten staat die in een inactief
# profiel en rendert hij niet mee, dus daar mag de eis niet gelden.
if [ "$peer" = "$GASTHEER" ]; then
printf '%s' "$merged" | "$GUARD" "$peer (federatie-merge)" --postgres || fail=1
else
printf '%s' "$merged" | "$GUARD" "$peer (federatie-merge)" || fail=1
fi
# De adressen die deze peer in de federatie claimt, uit env én uit `command`
# (stub-upstream draagt zijn adres in een `-listen=`-vlag). Elke component heeft een
# eigen adres binnen het federatie-prefix en houdt zijn standaardpoort; het adres is
# dus wat de peers onderscheidt, niet de poort.
binds=$(jq -r --arg pre "$FED_PREFIX" '.services | to_entries[] | . as $s
| ( ($s.value.environment // {}) | to_entries[] | select(.value != null) | (.value | tostring) ),
( (($s.value.command // []) + ($s.value.entrypoint // []))[]? | select(type == "string") )
| select(test("(^|=)" + ($pre | gsub("\\."; "\\.")) + "\\.[0-9]+\\.[0-9]+:[0-9]+$"))
| sub("^.*="; "")' <<<"$merged" | sort -u)
# Een listener die buiten het federatie-prefix valt, is een service die zijn
# federatie-overlay mist: die valt terug op het adres uit de hostnet-overlay
# (127.0.0.1) en botst daar met de gelijknamige component van elke andere peer. Dat
# moet hier gevangen worden en niet door de smoke: uit de gedeclareerde adressen is
# zo'n service juist VERDWENEN, dus daar valt hij niet op.
buiten_prefix=$(jq -r --arg pre "$FED_PREFIX" '.services | to_entries[] | . as $s
| ($s.value.environment // {}) | to_entries[]
| select(.key | test("^(LISTEN_ADDRESS|MONITORING_ADDRESS)"))
| select(.value != null and (.value | tostring) != "")
| select(((.value | tostring) | startswith($pre + ".")) | not)
| "\($s.key): \(.key)=\(.value)"' <<<"$merged")
if [ -n "$buiten_prefix" ]; then
echo "FOUT: $peer — listener buiten $FED_PREFIX. (mist zijn federatie-overlay):"
printf '%s\n' "$buiten_prefix" | sed 's/^/ /'
fail=1
fi
net_var="NET_$(printf '%s' "$peer" | tr '-' '_')"
net="${!net_var:-}"
if [ -z "$net" ]; then
echo "FOUT: geen $net_var in peers.env."
fail=1
continue
fi
in_net=0
for b in $binds; do
adres="${b%:*}"
# Federatie-infra draait op de GASTHEER namens iedereen, op <prefix>.0.x, en hoort bij
# geen peer-/24. Voor een gast is datzelfde adres per definitie een tweede router of
# postgres, dus daar geldt de uitzondering niet.
case "$adres" in
"$FED_PREFIX".0.*)
if [ "$peer" != "$GASTHEER" ]; then
echo "FOUT: $peer is gast maar claimt infra-adres $adres."
fail=1
fi
continue
;;
esac
case "$adres" in
"$net".*) in_net=$((in_net + 1)) ;;
*) echo "FOUT: $peer — $b valt buiten zijn eigen $net.0/24."; fail=1 ;;
esac
alle="$alle $peer:$adres"
done
# Nul adressen betekent niet "schoon" maar "de extractie matcht niets meer" — een
# opmaakwijziging in de overlay zou de guard anders stilzwijgend uitschakelen.
if [ "$in_net" -eq 0 ]; then
echo "FOUT: $peer — 0 adressen uit de merge; de extractie matcht niets meer."
fail=1
else
echo "$peer: $in_net binds in $net.0/24."
fi
done
# Disjunctheid over de peers heen: het punt van de adresindeling. Twee peers op hetzelfde
# adres botsen alsnog op hun standaardpoort.
#
# `sort -u` eerst: één peer claimt zijn eigen adres meermaals, want een component heeft
# meerdere listeners (manager: extern, intern, intern-unauth, monitoring). Zonder de
# deduplicatie leest elke component met twee poorten als een botsing met zichzelf.
# shellcheck disable=SC2086 # bewuste word splitting: `alle` is een lijst `peer:adres`.
botsingen=$(printf '%s\n' $alle | sort -u | awk -F: 'NF==2 {tel[$2]=tel[$2]" "$1} END {for (p in tel) if (split(tel[p],a," ")>1) print p": "tel[p]}')
if [ -n "$botsingen" ]; then
echo "FOUT: hetzelfde adres wordt door meer dan één peer geclaimd:"
printf '%s\n' "$botsingen" | sed 's/^/ /'
fail=1
fi
# Disjunctheid over één peer is inhoudsloos; de guard hoort te meten wat hij belooft.
if [ "$aantal_peers" -lt 2 ]; then
echo "FOUT: peers.env levert $aantal_peers peer(s) — te weinig om botsingen op te meten."
fail=1
fi
# Spiegeling: een overlay die niet in peers.env staat wordt nergens gerenderd en zou dus
# ongecontroleerd meeliften zodra iemand hem handmatig meegeeft aan `docker compose`.
for overlay in "$FED"/compose/*.yaml; do
naam="$(basename "$overlay" .yaml)"
case " $GASTHEER $GASTEN " in
*" $naam "*) ;;
*) echo "FOUT: $overlay hoort bij geen enkele peer uit peers.env."; fail=1 ;;
esac
done
if [ "$fail" -ne 0 ]; then
echo "::error::De federatie-overlays passen niet meer op hun peers. Zie de log hierboven."
fi
exit "$fail"
# De demo-stack uit compose.yaml draait in dezelfde netns-modus als de harnessen, en dus onder
# dezelfde eis: geen enkele listener buiten loopback. Zonder deze job stond die stack op élke
# interface van de machine — Redis zonder wachtwoord en twee PostgreSQL-instanties met
# demo-credentials — en botste een wildcard-bind bovendien met elke specifieke bind van een
# federatie in dezelfde netns.
demo-stack-op-loopback:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Elke demo-service op de gedeelde netns, elke listener op loopback
run: |
set -euo pipefail
# `--profile demo` erbij: zonder dat profiel rendert compose de vier Quarkus-services en
# toxiproxy niet, en meet deze job precies de helft van de stack.
DEMO_HOST=localhost DEMO_MAGAZIJNEN=98 docker compose --profile demo \
-f compose.yaml \
-f compose.podman.yaml \
-f compose.podman-hostnet.yaml config --format json \
| "$GITHUB_WORKSPACE/.github/scripts/merge-guard.sh" "demo-stack (hostnet-merge)"
- name: De console stuurt Toxiproxy in deze modus ook naar loopback
# De demo-console maakt de proxies zelf aan en zet ze elke reconcile-ronde terug. Staan haar
# adressen niet op de gedeelde netns ingesteld, dan overschrijft ze de geladen definities met
# container-DNS-namen die daar niet resolven — en dat is aan niets te zien: Toxiproxy blijft
# gezond, de zes namen kloppen nog, alleen sterft elk verbinding erdoorheen. Deze guard eist
# daarom een override per proxy uit toxiproxy/proxies.json, allemaal op 127.0.0.1.
run: |
set -euo pipefail
DEMO_HOST=localhost DEMO_MAGAZIJNEN=98 docker compose --profile demo \
-f compose.yaml \
-f compose.podman.yaml \
-f compose.podman-hostnet.yaml config --format json > /tmp/hostnet-merge.json
python3 - /tmp/hostnet-merge.json toxiproxy/proxies.json <<'PY'
import json, sys
merge, bron = (json.load(open(pad)) for pad in sys.argv[1:3])
env = merge["services"]["demo-console"].get("environment") or {}
fouten = []
for proxy in bron:
# `magazijn-a` → MAGAZIJN_A_; de env-namen in application.properties volgen die vorm.
stam = "TOXIPROXY_" + proxy["name"].upper().replace("-", "_")
for soort in ("LISTEN", "UPSTREAM"):
sleutel = f"{stam}_{soort}"
waarde = env.get(sleutel)
if waarde is None:
fouten.append(f"{sleutel} ontbreekt: de console valt terug op haar container-DNS-default")
elif not str(waarde).startswith("127.0.0.1:"):
fouten.append(f"{sleutel}={waarde} staat niet op 127.0.0.1")
if fouten:
print("::error::compose.podman-hostnet.yaml stuurt de console niet volledig naar loopback")
print("\n".join(" " + f for f in fouten))
sys.exit(1)
print(f"{len(bron)} proxies: listen en upstream van de console staan op loopback.")
PY
harness-scripts:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: shellcheck + bash-unittests
# De harness is bash, en de bash-tests die er zijn (demo/environment/lib/test-*.sh) draaiden
# tot nu toe nergens automatisch. Zonder deze stap rot elke test die eraan wordt toegevoegd.
#
# `deploy/zad/*.sh` valt bewust buiten de globs: die scripts dragen bestaande
# SC2034-bevindingen die losstaan van de lokale harness. Ze erbij nemen vergt eerst die
# opruiming; anders zou deze stap rood staan op iets wat hij niet bewaakt.
run: |
set -euo pipefail
fail=0
echo "== shellcheck =="
# -x volgt `source`-directives, zodat de gedeelde lib meegecontroleerd wordt. De
# ci-scripts.yml shellcheckt alles onder .github/scripts/; merge-guard.sh staat hier
# bewust óók, zodat een wijziging aan de guard niet ongecontroleerd blijft wanneer
# alleen deze overlay-workflow draait.
if ! shellcheck -x -S warning \
demo/environment/lib/*.sh \
demo/environment/federatie/*.sh \
demo/environment/federatie/contracts/*.sh \
demo/environment/*/deploy/local/*.sh \
demo/environment/*/pki/*.sh \
demo/environment/zad-demo/*.sh \
demo/*.sh \
.github/scripts/merge-guard.sh; then
fail=1
fi
echo "== bash-unittests =="
gedraaid=0
for t in demo/environment/lib/test-*.sh; do
[ -f "$t" ] || continue
gedraaid=$((gedraaid + 1))
echo "-- $t"
bash "$t" || fail=1
done
if [ "$gedraaid" -eq 0 ]; then
echo "FOUT: geen enkele test-*.sh gevonden — deze stap meet niets."
fail=1
fi
if [ "$fail" -ne 0 ]; then
echo "::error::shellcheck of een bash-unittest faalde. Zie de log hierboven."
fi
exit "$fail"
- name: Contract-bootstrap-image bouwt en draait onder een willekeurige UID
# Het image draait op ZAD als component; zonder deze stap blijkt een verkeerd COPY-pad, een
# ontbrekend pakket of een rechtenprobleem pas daar. OpenShift start containers onder een
# willekeurige UID in groep 0 — dat is de aanname waar de hele opstelling op rust, dus die
# wordt hier expliciet uitgeoefend en niet alleen in de Dockerfile opgeschreven.
run: |
set -euo pipefail
docker build -f demo/environment/federatie/contracts/Dockerfile \
-t fbs-fsc-contract-bootstrap:ci demo/environment
echo "== gereedschap en leesbaarheid onder UID 12345, groep 0 =="
docker run --rm -u 12345:0 --entrypoint sh fbs-fsc-contract-bootstrap:ci -c '
set -eu
command -v bash curl jq openssl >/dev/null
test -r /opt/fsc/lib/fsc-contract.sh
test -r /opt/fsc/lib/fsc-harness.sh
test -x /opt/fsc/contracts/zad-lus.sh
test -x /opt/fsc/contracts/bootstrap-consumer.sh
test -x /opt/fsc/contracts/bootstrap-provider.sh'
# De twee scripts die met béíde managers praten horen niet in het image: op ZAD kan dat
# niet en een operator die ze daar aantreft, gaat ze gebruiken.
echo "== bootstrap.sh en fbs-contracten.sh zitten er NIET in =="
docker run --rm -u 12345:0 --entrypoint sh fbs-fsc-contract-bootstrap:ci -c '
! test -e /opt/fsc/contracts/bootstrap.sh && ! test -e /opt/fsc/contracts/fbs-contracten.sh'
# Exit 2 = "configuratie deugt niet". Daar stopt de lus op, en dat maakt er een zichtbare
# crashloop van in plaats van een pod die draait en niets doet.
echo "== configuratiefouten eindigen op 2 =="
for geval in "" "FSC_ROL=onzin"; do
rc=0
if [ -n "$geval" ]; then
docker run --rm -u 12345:0 -e "$geval" fbs-fsc-contract-bootstrap:ci || rc=$?
else
docker run --rm -u 12345:0 fbs-fsc-contract-bootstrap:ci || rc=$?
fi
if [ "$rc" -ne 2 ]; then
echo "::error::verwachtte exit 2 voor '${geval:-<geen env>}', kreeg ${rc}"
exit 1
fi
done