Repository navigation
Expand file tree
/
Copy pathcommon.sh
More file actions
executable file
·3342 lines (3206 loc) · 156 KB
/
Copy pathcommon.sh
File metadata and controls
executable file
·3342 lines (3206 loc) · 156 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
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
set -x
check_return_code()
{
rc=$1
if [ $rc -ne 0 ];
then
echo "Non-zero return code. Exiting."
exit 1
fi
}
# ---------------------------------------------------------------------------
# THE LAST THING A LAUNCHER PRINTS: THE BANNER, AND THE STATUS IT EXITS WITH.
#
# Called from each launcher's EXIT trap, AFTER whatever teardown that trap
# does. Before 2026-09-15 the containerized launcher printed its banner and
# then let the trap run `docker-compose down`, so a passing run ended in a
# screen of container shutdown lines, and a failing one ended in the
# teardown's own `Leaving docker_compose(). rc=0` / `+ return 0` — which reads
# as the script's status and is not: bash exits with the status the script
# had when the trap began unless the trap itself calls `exit`. Both are fixed
# by printing here, last, and naming the status in words.
#
# $1 the status the script is exiting with — the caller's `$?`, captured
# as the trap's FIRST statement, before anything can overwrite it
# $2 the launcher's name, for the line
#
# The banner is printed only when the status is 0 AND the launcher set
# SUITE_PASSED=1, because several launchers also exit 0 without having run
# the suite (`--saml-dev` brings a stack up and runs nothing) and a banner
# there would claim a pass nobody measured.
#
# xtrace is switched off first: every launcher runs under `set -x`, and the
# trace of these echos would otherwise be the real last lines of the log.
# ---------------------------------------------------------------------------
launcherExitStatus()
{
{ set +x; } 2>/dev/null
local status=$1
local name=$2
echo "Entering launcherExitStatus()."
if [ "${status}" -eq 0 ] && [ "${SUITE_PASSED:-0}" = "1" ];
then
cat <<'EOF'
_ _ _ _ _ _
/ \ | | | | |_ ___ ___| |_ ___ _ __ __ _ ___ ___ ___| |
/ _ \| | | | __/ _ \/ __| __/ __| | '_ \ / _` / __/ __|/ _ \ |
/ ___ \ | | | || __/\__ \ |_\__ \ | |_) | (_| \__ \__ \ __/_|
/_/ \_\_|_| \__\___||___/\__|___/ | .__/ \__,_|___/___/\___(_)
|_|
EOF
fi
echo "Leaving launcherExitStatus()."
echo "${name}: exiting with status ${status}"
}
common_setup()
{
echo "Entering common_setup()."
# The tag THIS checkout builds and runs the mock STS under. Overridable, so
# a run can be pointed at an image somebody else built; the default carries
# the compose project (the checkout's directory name unless it is set), so
# two checkouts of this repository do not collide either. See the note above
# COMPOSE_FORWARDED_VARS. Named under IMAGE_REGISTRY like the other images
# the test stack builds (docker-compose-run-tests.yml), and tagged
# IMAGE_TAG when a run sets one — tests.yml does, and pushes it.
local sts_repo="${IMAGE_REGISTRY:-ghcr.io/rcbj/id-proto-debugger}/sts"
local sts_tag="${IMAGE_TAG:-${COMPOSE_PROJECT_NAME:-id-proto-debugger}}"
STS_IMAGE="${STS_IMAGE:-${sts_repo}:${sts_tag}}"
export STS_IMAGE
echo "The mock STS image for this run is ${STS_IMAGE}."
REV=/usr/bin/rev
JQ=/usr/bin/jq
CURL=/usr/bin/curl
for COMMAND in ${REV} ${JQ} ${CURL}
do
if [ ! -x "${COMMAND}" ];
then
echo "Cannot execute ${COMMAND} command."
exit 1
fi
done
echo "Leaving common_setup()."
}
# Variables compose itself needs — the ones the compose FILES substitute, plus
# COMPOSE_PROJECT_NAME, which scopes ps/logs/up to the right project when a
# launcher isolates its stack (remote-run-tests.sh does).
#
# They have to be listed because `sudo` RESETS THE ENVIRONMENT: only assignments
# written on its command line survive. For a long time just CONFIG_FILE was
# forwarded, so every other `${...}` in a compose file silently substituted to
# empty — including GIT_COMMIT and BUILD_NUMBER, which stamp the build.
# The LIVE_* trio that used to be here (LIVE_CONFIG_FILE, LIVE_DEBUGGER_BASE_URL,
# LIVE_WSTRUST_STS_URL) was consumed only by docker-compose-live-tests.yml, which
# is gone: the live-site workflows call remote-run-tests.sh directly now, and it
# runs the suite on the host rather than substituting those into a compose file.
# THE THREE THAT TUNE A CONTAINERIZED RUN are here for exactly the same reason
# and each was silently doing nothing before 2026-08-27. TEST_CONCURRENCY sizes
# run-report.js's job pool and STS_LOG_LEVEL turns the mock's per-request debug
# logging down — the two settings a slow containerized run reaches for — and
# both are read by COMPOSE ITSELF (`${TEST_CONCURRENCY:-}` in the tests service,
# the bare `- STS_LOG_LEVEL` on every sts service), which is the environment
# sudo had just emptied. So `TEST_CONCURRENCY=6 ./docker-run-tests.sh` reached
# compose as empty, the pool sized itself from the container's cores as though
# nothing had been asked for, and the run gave no sign either way.
# TEST_JOB_TIMEOUT_MS is the per-job watchdog and joins them so the whole pool
# is tunable from one place. All three are absent from ./local-run-tests.sh's
# problems: that launcher runs run-report.js on the host, where the variables
# are simply inherited.
COMPOSE_FORWARDED_VARS="CONFIG_FILE COMPOSE_PROJECT_NAME OID4VCI_WALLET_URL"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} BUILD_NUMBER GIT_COMMIT"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} TEST_CONCURRENCY TEST_JOB_TIMEOUT_MS"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} TEST_WAIT_TIME_MS"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STS_LOG_LEVEL"
# THE MOCK STS IMAGE'S TAG, which is not this repository's alone: `sts/` is the
# iya-sts submodule and that repository builds its own stack from the same
# source. A docker tag is machine-wide, so both writing `rcbj/sts` means the
# last build wins and a run here can start a mock built from another
# checkout's tree — which is what happened on 2026-09-15, costing seven jobs
# that failed on defects already fixed in the submodule this checkout points
# at. That repository's launchers already tag `rcbj/sts:<project>` for the
# same reason; these tag one of their own below.
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STS_IMAGE"
# The stack TLS pair. Every compose file here mounts the DIRECTORY and
# names the two files inside it, so all three have to cross sudo — and
# STACK_TLS_CA_FILE is what the mock STS is given as NODE_EXTRA_CA_CERTS
# so that it can PUSH a Security Event Token to the api over TLS.
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STACK_TLS_DIR"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STACK_TLS_CERT_FILE"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STACK_TLS_KEY_FILE"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STACK_TLS_CA_FILE"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STACK_TLS_SPKI_PIN"
# The two per-service CONFIG_FILE knobs docker-compose-run-tests.yml added on
# 2026-08-31. They exist because `CONFIG_FILE` above is EXPORTED by
# docker-run-tests.sh with a default of its own, so a compose-level default for
# the sts and tests services would never be reached — and those two need the
# info-level file where the api and client need their own.
#
# Registering them here is not optional and tests/compose_env_forwarding.js is
# what says so: docker_compose() runs under sudo, which keeps no environment,
# so a variable a compose file reads and this list does not name is seen UNSET.
# `${STS_CONFIG_FILE:-./env/test.js}` would still work by falling back — but
# `STS_CONFIG_FILE=./env/docker-tests.js` on the command line would then do
# NOTHING, silently, which is the whole failure mode that test exists to catch.
# It caught exactly this on the first run after the variables were added.
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} STS_CONFIG_FILE TESTS_CONFIG_FILE"
# THE MOCK STS'S MANAGEMENT API CREDENTIAL (2026-09-09). The submodule bump of
# that date closed /admin-api: it takes an OAuth 2.0 access token now, minted by
# the seeded `sts-management-api` client with client_credentials. That client's
# secret is generated at every start of the mock and is readable only THROUGH
# the API it unlocks — a bootstrap hole — so `adminApi.clientSecret` exists to
# pin it, and this is the value the compose files hand that container as
# ADMIN_API_CLIENT_SECRET and the tests container as
# STS_ADMIN_API_CLIENT_SECRET. See the block below, where it is generated.
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} ADMIN_API_CLIENT_SECRET"
# WHERE THE TEST STACK'S IMAGES COME FROM AND GO (2026-09-27).
# docker-compose-run-tests.yml pulls every third-party image, and every base
# image it builds FROM, out of a private mirror on ghcr.io (IMAGE_MIRROR), and
# names what it builds under IMAGE_REGISTRY with tag IMAGE_TAG. All three have
# defaults in the compose file, so unforwarded they would not fail — they
# would silently fall back to them, which is the failure this list exists for.
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} IMAGE_MIRROR IMAGE_REGISTRY"
COMPOSE_FORWARDED_VARS="${COMPOSE_FORWARDED_VARS} IMAGE_TAG"
# ---------------------------------------------------------------------------
# THE MANAGEMENT API'S CLIENT SECRET, PINNED FOR THIS RUN.
#
# One value per launcher run, generated here because every launcher sources
# this file and both ends of the arrangement need the same string: the mock STS
# is given it as `adminApi.clientSecret` and whoever mints a token — the tests
# container's run-report.js, or mintAdminApiToken() below — is given it as
# STS_ADMIN_API_CLIENT_SECRET.
#
# A FRESH SECRET PER RUN rather than a constant in this file: it lives as long
# as one stack, it never reaches a repository, and two runs on one machine
# cannot lend each other a token. An outer value is respected, which is how a
# caller drives a mock it started itself.
# ---------------------------------------------------------------------------
if [ -z "${ADMIN_API_CLIENT_SECRET:-}" ];
then
ADMIN_API_CLIENT_SECRET="$(head -c 24 /dev/urandom | base64 | tr -d '/+=' \
| head -c 24)"
fi
export ADMIN_API_CLIENT_SECRET
STS_ADMIN_API_CLIENT_SECRET="${ADMIN_API_CLIENT_SECRET}"
export STS_ADMIN_API_CLIENT_SECRET
# Does docker on this machine need sudo? Answered by RUNNING it rather than by
# looking for a group in `id -nG`, which is neither necessary (a rootless
# daemon needs no group) nor sufficient (a group added since this shell logged
# in is not in this shell's credentials). It is the same probe clean.sh and
# infra/terraform-local.sh already make, moved here so the four launchers get
# it too.
#
# THE ANSWER IS "" WHENEVER THE DAEMON IS REACHABLE DIRECTLY, and that case is
# what this file did not have: docker_compose() prefixed `sudo`
# unconditionally, so a member of the `docker` group was asked for a password
# anyway, and a run with no terminal to type it into — CI, or anything driving
# these scripts unattended — died at the FIRST compose call with `sudo: a
# password is required`, before one container had been built. That message
# names sudo and not the suite, which is three steps from the thing to fix.
#
# Only PASSWORDLESS sudo is chosen deliberately. If neither probe succeeds the
# answer stays "yes" and sudo prompts exactly as it always did — this is a
# path that is added, never one that is taken away.
#
# Cached, because a run makes a dozen compose calls and each probe is a round
# trip to the daemon.
DOCKER_SUDO="${DOCKER_SUDO-unset}"
resolveDockerSudo()
{
echo "Entering resolveDockerSudo()."
if [ "${DOCKER_SUDO}" != "unset" ];
then
echo "Leaving resolveDockerSudo(). Cached: '${DOCKER_SUDO}'."
return 0
fi
if docker info > /dev/null 2>&1;
then
DOCKER_SUDO=""
echo "Leaving resolveDockerSudo(). The daemon is reachable directly."
return 0
fi
if sudo -n docker info > /dev/null 2>&1;
then
DOCKER_SUDO="yes"
echo "Leaving resolveDockerSudo(). Using passwordless sudo."
return 0
fi
DOCKER_SUDO="yes"
echo "Leaving resolveDockerSudo(). Neither probe worked; sudo may prompt."
return 0
}
docker_compose() {
echo "Entering docker_compose()."
# Capture the real exit code of the compose command. sudo propagates the
# child's status, but the trailing echo would reset $?, so stash it first and
# return it — otherwise a failed `up --exit-code-from tests` (a failing test)
# is masked and callers (e.g. run-coverage.sh) wrongly see success.
local rc
# Only variables that are actually SET are passed: an empty
# COMPOSE_PROJECT_NAME makes compose refuse to run, and an empty value is not
# the same as absent for compose's own substitution warnings. Deliberately
# unquoted below so the list splits into separate NAME=VALUE arguments — every
# value here is a URL, a path or a name, none of which contain spaces.
local env_args="" _v _val
for _v in ${COMPOSE_FORWARDED_VARS};
do
eval "_val=\${${_v}-}"
if [ -n "${_val}" ];
then
env_args="${env_args} ${_v}=${_val}"
fi
done
resolveDockerSudo
# Which compose. Unquoted where it is used, so that the two words of
# `docker compose` split into a command and its subcommand.
local compose_cmd=""
if [ -x ~/.local/bin/docker-compose ];
then
# By FULL PATH when this runs without sudo. sudo builds its own PATH from
# secure_path and finds the binary there; an ordinary login shell's PATH
# need not carry ~/.local/bin at all, and a bare `docker-compose` would
# then be a `command not found` naming compose rather than the PATH.
if [ -n "${DOCKER_SUDO}" ];
then
compose_cmd="docker-compose"
else
compose_cmd="${HOME}/.local/bin/docker-compose"
fi
elif docker compose version >/dev/null 2>&1;
then
compose_cmd="docker compose"
elif command -v docker-compose >/dev/null 2>&1;
then
compose_cmd="docker-compose"
else
echo "Error: Docker Compose not found." >&2
echo "Leaving docker_compose(). rc=1"
return 1
fi
if [ -n "${DOCKER_SUDO}" ];
then
sudo ${env_args} ${compose_cmd} "$@"
rc=$?
else
# `env` rather than a bare prefix assignment, because compose_cmd may be
# two words and `NAME=v docker compose ...` would then set the variable for
# `docker` only. Same effect as sudo's command-line assignments.
env ${env_args} ${compose_cmd} "$@"
rc=$?
fi
echo "Leaving docker_compose(). rc=${rc}"
return ${rc}
}
# Download the Keycloak SAML IdP descriptor to a local file and export
# SAML_METADATA_FILE, so the SAML tests UPLOAD it into saml_request.html rather
# than having the browser fetch it. Required against a backend-less deployed site
# (e.g. https://test.idptools.com): the HTTPS page can't fetch the local http
# Keycloak descriptor cross-origin (blocked by CORS). Uses
# KEYCLOAK_LOCALHOST_BASE_URL (reachable from THIS shell), not the browser-facing
# KEYCLOAK_BASE_URL. Gated by SAML_METADATA_UPLOAD; called from configureKeycloak
# after the debugger-testing realm exists, so the descriptor resolves.
download_saml_metadata()
{
echo "Entering download_saml_metadata()."
local url="${KEYCLOAK_LOCALHOST_BASE_URL}/realms/debugger-testing/protocol/saml/descriptor"
local dest="${SAML_METADATA_FILE:-${CURRENT_DIR}/saml-idp-metadata.xml}"
echo "Downloading SAML IdP metadata from ${url} to ${dest}"
curl -sf "${url}" -o "${dest}"
check_return_code $?
if [ ! -s "${dest}" ];
then
echo "ERROR: downloaded SAML metadata is empty (${url})." >&2
exit 1
fi
declare -gx SAML_METADATA_FILE="${dest}"
echo "SAML IdP metadata saved to ${SAML_METADATA_FILE}."
echo "Leaving download_saml_metadata()."
}
# ---------------------------------------------------------------------------
# The SAML SP key pair used by the SAML tests.
#
# Generated FRESH on every run and never written to the repository: the private
# key exists only in this shell's environment (and the environment of the test
# processes it spawns) for the life of the run. It is created in a temporary
# directory which is deleted immediately after the PEMs are read.
#
# Exports:
# SAML_SP_PRIVATE_KEY the private key, PEM (PKCS#1) — the tests sign the
# AuthnRequest / LogoutRequest with it, and decrypt an
# encrypted assertion with it
# SAML_SP_CERT the matching self-signed certificate, PEM
# SAML_SP_SIGNING_CERT the same certificate as base64 DER (no PEM armour),
# which is the form Keycloak's saml.signing.certificate
# attribute takes — configureKeycloak registers it on the
# SAML client so it validates the request signature
#
# An outer wrapper may supply SAML_SP_PRIVATE_KEY / SAML_SP_CERT itself; in that
# case they are used as they are and nothing is generated.
# ---------------------------------------------------------------------------
# The browser extension's unpacked builds. Not committed, because they are
# generated (the version is stamped in) and because the CI build needs to know
# which origin the browser will see the mock STS on — http://sts:8081 inside the
# containerized stack, http://localhost:8081 for a host run. Written before
# compose builds, exactly like renderWaltidConfig: tests/Dockerfile COPYs
# extension/dist/ci, so an absent build fails that COPY with a message about a
# missing path rather than anything about extensions.
#
# The test that loads it will not run against BRANDED Google Chrome — that build
# refuses to side-load an unpacked extension and says so only on stderr — but the
# tests image pins Chrome for Testing, which allows it.
buildBrowserExtension()
{
echo "Entering buildBrowserExtension()."
local dir="${1:-.}"
# The origin the CI build auto-arms MUST be the one the BROWSER will use to
# reach the mock STS, which is not the same string in every stack:
#
# containerized http://sts:8081 (the browser is inside the compose network)
# host launchers http://localhost:8081
#
# Get it wrong and nothing fails loudly: the extension installs, reports its
# version, and observes an origin the browser never visits — so the shim is
# never injected and every capture assertion times out with nothing naming the
# cause. That is exactly what happened on 2026-08-09, when this defaulted to
# localhost for the containerized run. Each launcher therefore states it.
local origins="${EXTENSION_AUTOARM_ORIGINS:-}"
if [ -z "${origins}" ];
then
echo "ERROR: buildBrowserExtension needs EXTENSION_AUTOARM_ORIGINS — the origin the browser" >&2
echo " will use for the mock STS (http://sts:8081 containerized, http://localhost:8081" >&2
echo " on a host run). Refusing to guess: a wrong value costs a silent, empty capture." >&2
return 1
fi
if [ ! -f "${dir}/extension/build.js" ];
then
echo "WARNING: ${dir}/extension/build.js is missing; the extension job will fail its own" >&2
echo " precondition rather than this build failing here." >&2
return 0
fi
( cd "${dir}" && EXTENSION_AUTOARM_ORIGINS="${origins}" node extension/build.js )
check_return_code $?
echo "Leaving buildBrowserExtension(). autoarm origins: ${origins}"
return 0
}
# ---------------------------------------------------------------------------
# THE STACK'S TLS CERTIFICATE — ONE PAIR, MADE BEFORE ANYTHING STARTS.
#
# The api and the client both serve https now, and this is where the pair they
# serve comes from. It is generated by common/generate_tls_cert.js, which is a
# caller of THIS PROJECT'S OWN certificate authoring module
# (client/src/x509.js's `tls-server` profile) rather than another encoder — the
# argument is in that file's header and it is the one common/xmldsig.js already
# makes about XML Signature.
#
# ONE PAIR FOR BOTH SERVICES, and the reason is the mock STS. It PUSHES to the
# api (RFC 8935 Shared Signals delivery, POST /ssf/receiver/:id), so that
# container has to be handed the api's anchor in its environment — which is
# impossible for a certificate a service invents at its own startup. One file,
# on disk, before compose. It also makes ONE anchor for node
# (NODE_EXTRA_CA_CERTS names a single file) and ONE SPKI pin for Chrome rather
# than two of each.
#
# IT RUNS ON THE HOST, out of the checkout, for the same reason
# generateSpKeyPair() runs openssl there: client/src/x509.js needs pkijs,
# asn1js and the @noble family, which are the CLIENT's dependencies and are not
# in the api image at all.
#
# Exports STACK_TLS_DIR, STACK_TLS_CERT_FILE, STACK_TLS_KEY_FILE and
# STACK_TLS_SPKI_PIN. The certificate is self-signed, so it is its own anchor:
# STACK_TLS_CA_FILE is the same file under the name a truststore consumer wants
# it by — which still holds now that there is a hierarchy above the leaf,
# because stack-tls-cert.pem is a BUNDLE: leaf, issuing CA and root, in that
# order. A server sends it as the chain; a truststore consumer finds the root
# inside it. What a person imports into a browser is the root ALONE, which
# ./generate-tls-cert.sh writes separately and names on the way out.
#
# STACK_TLS_CA_DIR, if set, is where the CA is kept and REUSED between runs.
# Unset, the generator puts it under ${STACK_TLS_DIR} and it dies with the
# run — which is what every launcher wants, and what ./generate-tls-cert.sh
# deliberately does not.
#
# $1 optional extra subjectAltName DNS name (a deployed hostname, say). The
# stack's own names — localhost, client, api and the loopback literals — are
# always present; see the SANs note in common/generate_tls_cert.js.
# ---------------------------------------------------------------------------
generateStackTlsCertificate()
{
echo "Entering generateStackTlsCertificate()."
# As in generateSpKeyPair(): this runs under `set -x`, and while the key path
# is not the key, the generator prints assignments that are easier to read
# unechoed. Trace restored on the way out.
local xtrace_was_on=""
case "$-" in
*x*) xtrace_was_on="yes"; set +x ;;
esac
if [ -n "${STACK_TLS_CERT_FILE:-}" ] && [ -n "${STACK_TLS_KEY_FILE:-}" ] &&
[ -f "${STACK_TLS_CERT_FILE}" ] && [ -f "${STACK_TLS_KEY_FILE}" ];
then
echo "The stack TLS pair was supplied by the caller; using it as-is."
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateStackTlsCertificate(). Supplied."
return 0
fi
local repo_root="${1:-$(pwd)}"
local extra_name="${2:-}"
# client/node_modules is where client/src/x509.js's own requires resolve.
# Without it the generator dies on `Cannot find module 'pkijs'`, which names
# a package rather than a missing install, so it is handled here by name.
#
# IT IS INSTALLED RATHER THAN ONLY REPORTED, and that is the fix for a red
# CI run rather than a convenience. This function is called by all four
# launchers and none of them installs the CLIENT's dependencies — each does
# `npm install --prefix tests` and no more — so the directory exists only
# because somebody ran an install in client/ at some earlier point. That is
# true on a developer's machine and false on a fresh checkout, which is
# exactly what a GitHub Actions runner is: run 662 of the Selenium Tests
# workflow died here in eight seconds, in both jobs, before compose was
# called, having built nothing and run no test. Reporting a missing install
# is the right thing to do about a missing TOOL; a dependency of this
# repository that this repository knows how to install is one this function
# installs, the way requireMockStsCheckout() initialises a submodule.
#
# PLAIN `npm ci`, NOT `--omit=dev`. Only the runtime dependencies are needed
# here (pkijs, asn1js, the @noble family, node-forge, bunyan — every one of
# them in `dependencies`), so the flag would be correct for this caller and
# would leave the tree wrong for the next one: `cd client && npm run build`
# is what clean-artifacts.sh documents as the restore, and it wants
# browserify and the three minifiers. A half-installed tree fails that with
# `browserify: not found`, which names a package rather than an omitted
# flag. Both images install this way too — see the `comments` block in
# client/package.json. `npm ci` and not `npm install` because the lock is
# committed and reproducing it is the point.
if [ ! -d "${repo_root}/client/node_modules" ];
then
if ! command -v npm >/dev/null 2>&1;
then
echo "ERROR: ${repo_root}/client/node_modules is missing, and" \
"common/generate_tls_cert.js needs it — client/src/x509.js" \
"requires pkijs and asn1js. There is no npm on PATH to install" \
"it with. Install node/npm, or run 'npm ci --prefix client'" \
"elsewhere and bring the tree with you." >&2
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateStackTlsCertificate(). No npm."
return 1
fi
echo "${repo_root}/client/node_modules is missing;" \
"installing the client's dependencies, which is where" \
"common/generate_tls_cert.js resolves pkijs and asn1js."
if ! npm ci --prefix "${repo_root}/client";
then
echo "ERROR: 'npm ci --prefix ${repo_root}/client' failed, so" \
"common/generate_tls_cert.js cannot run and the api and the" \
"client have no certificate to serve." >&2
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateStackTlsCertificate(). Install failed."
return 1
fi
fi
# A throwaway directory, the shape generateSpKeyPair() and
# trustStsCertificate() both use, and for the same reasons: this runs from
# the repository root on a host launcher and from /usr/src/app inside the
# tests container, so a relative path would have to be right in two places.
# STACK_TLS_DIR overrides it for anybody who wants to look at what was
# served. It is 0755 and the key inside it is 0600 — the containers run as
# uid 10001 and have to READ it (see "Containers run as non-root" in the
# repo-root CLAUDE.md), so a 0700 directory would be a permission error
# inside a container naming a path the host can read perfectly well.
STACK_TLS_DIR="${STACK_TLS_DIR:-$(mktemp -d)}"
mkdir -p "${STACK_TLS_DIR}"
chmod 0755 "${STACK_TLS_DIR}"
# THE CERTIFICATE AUTHORITY'S DIRECTORY IS THE ONE THING THAT OUTLIVES A
# RUN, AND ONLY WHEN IT IS ASKED TO. Unset — which is every launcher — the
# generator puts the CA in ${STACK_TLS_DIR}/ca, so it is thrown away with
# the rest of the throwaway directory and each run gets a hierarchy of its
# own. ./generate-tls-cert.sh sets it to a fixed path instead, because that
# is the path a PERSON uses: the root is what they import into a browser,
# and reusing it is what makes that a one-time act rather than a chore
# repeated after every regeneration.
# Always passed, so there is no empty-array expansion to get wrong under
# `set -u` — which the launchers run with and which bash before 4.4 treats
# as an unbound variable.
local ca_dir="${STACK_TLS_CA_DIR:-${STACK_TLS_DIR}/ca}"
local generated=""
if [ -n "${extra_name}" ];
then
generated=$(node "${repo_root}/common/generate_tls_cert.js" \
--out-dir "${STACK_TLS_DIR}" --ca-dir "${ca_dir}" \
--name "${extra_name}")
else
generated=$(node "${repo_root}/common/generate_tls_cert.js" \
--out-dir "${STACK_TLS_DIR}" --ca-dir "${ca_dir}")
fi
if [ -z "${generated}" ];
then
echo "ERROR: common/generate_tls_cert.js produced nothing. The api and" \
"the client are configured for TLS and will refuse to start" \
"without a certificate." >&2
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateStackTlsCertificate(). Not generated."
return 1
fi
# THE GENERATOR'S STDOUT IS PARSED, NOT EVALUATED, AND THAT IS THE SECOND
# HALF OF A FIX. It prints one KEY=value per line and says everything about
# itself on stderr — but the modules it requires are ordinary client
# modules, each with a bunyan logger whose default stream is stdout and
# whose level comes from CONFIG_FILE, which every launcher exports and both
# ./env/*.js it can name set to "debug". So a few hundred Entering/Leaving
# records used to land on the channel this reads, and `eval` applied quote
# removal and brace expansion to the JSON and reported
# `name:pqc: command not found` fourteen times, naming a module that had
# done nothing wrong. common/generate_tls_cert.js now takes stdout away from
# everything but its own three lines; this reads only assignments it asked
# for, so no future call site can corrupt the result by adding a log line.
# Split with parameter expansion rather than `IFS='=' read var value`,
# which looks equivalent and is not: `read` strips a TRAILING delimiter, so
# the base64 SPKI pin arrives without its own `=` padding — and a pin that
# is one character short is not rejected anywhere, it simply never matches,
# which looks exactly like Chrome ignoring the flag.
local line=""
local var=""
local value=""
while IFS= read -r line;
do
var="${line%%=*}"
value="${line#*=}"
case "${var}" in
STACK_TLS_KEY_FILE|STACK_TLS_CERT_FILE|STACK_TLS_SPKI_PIN)
printf -v "${var}" '%s' "${value}"
;;
esac
done <<< "${generated}"
if [ -z "${STACK_TLS_KEY_FILE:-}" ] || [ -z "${STACK_TLS_CERT_FILE:-}" ] ||
[ -z "${STACK_TLS_SPKI_PIN:-}" ];
then
echo "ERROR: common/generate_tls_cert.js printed no usable" \
"STACK_TLS_* assignment. It said:" >&2
echo "${generated}" >&2
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateStackTlsCertificate(). Unparseable output."
return 1
fi
# BOTH 0644, INCLUDING THE KEY, and that is a deliberate trade rather than an
# oversight. Every container here runs as uid 10001 (see "Containers run as
# non-root" in the repo-root CLAUDE.md) and has to READ this key to serve
# with it, and the host uid that generated it is 1000 on a developer's
# machine and 1001 on a GitHub Actions runner — so there is no ownership
# that is right in both places, exactly as there was none for ./tests/report
# and ./coverage. What bounds the exposure instead is what the key IS: a
# throwaway generated per run into a private temp directory, for a
# certificate that names localhost and two compose service names, discarded
# when the run ends. It is not a credential for anything outside this stack.
chmod 0644 "${STACK_TLS_KEY_FILE}" "${STACK_TLS_CERT_FILE}"
# Self-signed, so the leaf IS the anchor. Named separately because a
# truststore consumer asks for a CA file and a server asks for its
# certificate, and one path under two names is clearer at the call sites than
# one name doing both jobs.
STACK_TLS_CA_FILE="${STACK_TLS_CERT_FILE}"
export STACK_TLS_DIR STACK_TLS_CERT_FILE STACK_TLS_KEY_FILE
export STACK_TLS_CA_FILE STACK_TLS_SPKI_PIN
echo "The stack TLS certificate is ${STACK_TLS_CERT_FILE}" \
"(SPKI pin ${STACK_TLS_SPKI_PIN})."
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateStackTlsCertificate(). STACK_TLS_DIR=${STACK_TLS_DIR}"
return 0
}
generateSpKeyPair()
{
echo "Entering generateSpKeyPair()."
# This script runs under `set -x`, which would echo the private key into the
# run log (and a CI build log). Trace off for the duration, restored on the way
# out — only lengths and fingerprints are printed.
local xtrace_was_on=""
case "$-" in
*x*) xtrace_was_on="yes"; set +x ;;
esac
if [ -n "${SAML_SP_PRIVATE_KEY:-}" ] && [ -n "${SAML_SP_CERT:-}" ];
then
echo "SAML SP key pair was supplied by the caller; using it as-is."
SAML_SP_SIGNING_CERT=$(echo "${SAML_SP_CERT}" | grep -v -- '-----' | tr -d '\n\r')
export SAML_SP_PRIVATE_KEY SAML_SP_CERT SAML_SP_SIGNING_CERT
[ -n "${xtrace_was_on}" ] && set -x
echo "Leaving generateSpKeyPair()."
return 0
fi
if ! command -v openssl >/dev/null 2>&1;
then
echo "ERROR: openssl is required to generate the test SAML SP key pair." >&2
exit 1
fi
local dir
dir=$(mktemp -d)
# Two days is plenty for a test run and keeps a stray copy short-lived.
openssl req -x509 -newkey rsa:2048 -sha256 -days 2 -nodes \
-keyout "${dir}/sp-key.pem" -out "${dir}/sp-cert.pem" \
-subj "/CN=OAuth2 OIDC Debugger Test SP" >/dev/null 2>&1
check_return_code $?
# PKCS#1 ("BEGIN RSA PRIVATE KEY"), which is what the debugger's key fields
# have always been given. node-forge reads either form, so this is only for
# consistency with what a user would paste in by hand.
if openssl rsa -in "${dir}/sp-key.pem" -traditional -out "${dir}/sp-key-pkcs1.pem" >/dev/null 2>&1;
then
mv "${dir}/sp-key-pkcs1.pem" "${dir}/sp-key.pem"
fi
SAML_SP_PRIVATE_KEY=$(cat "${dir}/sp-key.pem")
SAML_SP_CERT=$(cat "${dir}/sp-cert.pem")
SAML_SP_SIGNING_CERT=$(grep -v -- '-----' "${dir}/sp-cert.pem" | tr -d '\n\r')
# Off disk immediately — the key lives in the environment only.
rm -rf "${dir}"
export SAML_SP_PRIVATE_KEY SAML_SP_CERT SAML_SP_SIGNING_CERT
if [ -z "${SAML_SP_PRIVATE_KEY}" ] || [ -z "${SAML_SP_SIGNING_CERT}" ];
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: the generated SAML SP key pair is empty." >&2
exit 1
fi
# A fingerprint identifies the pair in the log without revealing anything.
local fingerprint
fingerprint=$(echo "${SAML_SP_CERT}" | openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2)
[ -n "${xtrace_was_on}" ] && set -x
echo "Generated a fresh SAML SP key pair for this run: RSA 2048, SHA-256 fingerprint ${fingerprint}."
echo "Leaving generateSpKeyPair()."
}
# ---------------------------------------------------------------------------
# The walt.id issuer's signing key, generated fresh for each run.
#
# The waltid-issuer container (waltid/config/*.conf) reads its key leaf by leaf
# out of the environment, so that no private key is committed here — the same
# rule generateSpKeyPair() follows for the SAML SP. It signs both the
# credentials it issues and its own access tokens, and its did:jwk — the public
# half of this key, encoded into the identifier — becomes the `iss` of every
# credential it issues.
#
# Exports:
# WALTID_KEY_D / _X / _Y the P-256 key, as JWK members
# WALTID_ISSUER_DID did:jwk of the public half
# WALTID_CI_TOKEN_KEY the same key as the JSON string walt.id's
# ciTokenKey field expects
# Honours values supplied by the caller, so a run can pin a key if it needs to.
# ---------------------------------------------------------------------------
generateWaltidIssuerKey()
{
echo "Entering generateWaltidIssuerKey()."
# As in generateSpKeyPair: this file runs under `set -x`, and a private key
# must not be echoed into a run (or CI) log.
local xtrace_was_on=""
case "$-" in
*x*) xtrace_was_on="yes"; set +x ;;
esac
if [ -n "${WALTID_KEY_D:-}" ] && [ -n "${WALTID_ISSUER_DID:-}" ] && [ -n "${WALTID_CI_TOKEN_KEY:-}" ];
then
export WALTID_KEY_D WALTID_KEY_X WALTID_KEY_Y WALTID_ISSUER_DID WALTID_CI_TOKEN_KEY
[ -n "${xtrace_was_on}" ] && set -x
echo "A walt.id issuer key was supplied by the caller; using it as-is."
echo "Leaving generateWaltidIssuerKey()."
return 0
fi
if ! command -v node >/dev/null 2>&1;
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: node is required to generate the walt.id issuer key." >&2
exit 1
fi
# One line per exported value, so nothing has to be parsed out of JSON here.
local generated
generated=$(node -e '
var crypto = require("crypto");
var kp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
var jwk = kp.privateKey.export({ format: "jwk" });
var pub = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
// did:jwk is base64url of the JSON public key, per the did:jwk method.
var did = "did:jwk:" + Buffer.from(JSON.stringify(pub)).toString("base64url");
console.log(jwk.d);
console.log(jwk.x);
console.log(jwk.y);
console.log(did);
console.log(JSON.stringify({ type: "jwk", jwk: { kty: jwk.kty, d: jwk.d, crv: jwk.crv, x: jwk.x, y: jwk.y } }));
')
if [ -z "${generated}" ];
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: could not generate the walt.id issuer key." >&2
exit 1
fi
WALTID_KEY_D=$(echo "${generated}" | sed -n '1p')
WALTID_KEY_X=$(echo "${generated}" | sed -n '2p')
WALTID_KEY_Y=$(echo "${generated}" | sed -n '3p')
WALTID_ISSUER_DID=$(echo "${generated}" | sed -n '4p')
WALTID_CI_TOKEN_KEY=$(echo "${generated}" | sed -n '5p')
export WALTID_KEY_D WALTID_KEY_X WALTID_KEY_Y WALTID_ISSUER_DID WALTID_CI_TOKEN_KEY
if [ -z "${WALTID_KEY_D}" ] || [ -z "${WALTID_ISSUER_DID}" ];
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: the generated walt.id issuer key is incomplete." >&2
exit 1
fi
[ -n "${xtrace_was_on}" ] && set -x
# The DID is public — it is published in every credential this issuer signs.
echo "Generated a fresh walt.id issuer key for this run: P-256, ${WALTID_ISSUER_DID}."
echo "Leaving generateWaltidIssuerKey()."
}
# ---------------------------------------------------------------------------
# The walt.id VERIFIER's request-signing key.
#
# verifier-api2 signs Request Objects with this when a session asks for
# signed_request. It is separate from the issuer's key on purpose: they are
# different parties, and a test that shared one key between them would prove
# less than it appears to.
#
# Exports WALTID_VERIFIER_KEY — the {"type":"jwk","jwk":{…}} string walt.id's
# configuration expects — and never echoes it, the same rule
# generateWaltidIssuerKey() and generateSpKeyPair() follow.
# ---------------------------------------------------------------------------
generateWaltidVerifierKey()
{
echo "Entering generateWaltidVerifierKey()."
local xtrace_was_on=""
case "$-" in
*x*) xtrace_was_on="yes"; set +x ;;
esac
if [ -n "${WALTID_VERIFIER_KEY:-}" ];
then
export WALTID_VERIFIER_KEY
[ -n "${xtrace_was_on}" ] && set -x
echo "A walt.id verifier key was supplied by the caller; using it as-is."
echo "Leaving generateWaltidVerifierKey()."
return 0
fi
if ! command -v node >/dev/null 2>&1;
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: node is required to generate the walt.id verifier key." >&2
exit 1
fi
WALTID_VERIFIER_KEY=$(node -e '
var crypto = require("crypto");
var kp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
var jwk = kp.privateKey.export({ format: "jwk" });
console.log(JSON.stringify({ type: "jwk",
jwk: { kty: jwk.kty, d: jwk.d, crv: jwk.crv, x: jwk.x, y: jwk.y } }));
')
if [ -z "${WALTID_VERIFIER_KEY}" ];
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: could not generate the walt.id verifier key." >&2
exit 1
fi
export WALTID_VERIFIER_KEY
[ -n "${xtrace_was_on}" ] && set -x
echo "Generated a fresh walt.id verifier request-signing key for this run (P-256)."
echo "Leaving generateWaltidVerifierKey()."
}
# ---------------------------------------------------------------------------
# Render the walt.id configuration with this run's values written in.
#
# waltid/config/*.conf are templates that name their inputs as ${WALTID_...}.
# They could be mounted as they are and left for the config loader to expand —
# walt.id's own files rely on exactly that — but a third party's expansion rules
# are not something to bet a test run on: when it does not happen the service
# dies before it listens, and all you get is a 502 from the proxy in front of it.
#
# So the values are substituted HERE, and the container mounts the rendered
# copies. Nothing is left to interpret, and when something is wrong the effective
# configuration is a file you can read.
#
# The rendered directory is gitignored: it holds this run's private key.
# ---------------------------------------------------------------------------
renderWaltidConfig()
{
echo "Entering renderWaltidConfig()."
local xtrace_was_on=""
case "$-" in
*x*) xtrace_was_on="yes"; set +x ;;
esac
local repo_root="${1:-.}"
# Two services, two configuration trees, rendered into two directories: the
# issuer must not be handed the verifier's files (walt.id's config loader reads
# whatever is in the directory it is given) and the verifier must not be handed
# the issuer's.
local template_dir="${repo_root}/waltid/config"
local out_dir="${repo_root}/waltid/generated-config"
local verifier_template_dir="${repo_root}/waltid/verifier-config"
local verifier_out_dir="${repo_root}/waltid/generated-verifier-config"
if [ ! -d "${template_dir}" ];
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: ${template_dir} does not exist; cannot render the walt.id configuration." >&2
exit 1
fi
if [ -z "${WALTID_KEY_D:-}" ] || [ -z "${WALTID_BASE_URL:-}" ];
then
[ -n "${xtrace_was_on}" ] && set -x
echo "ERROR: renderWaltidConfig needs WALTID_BASE_URL and the issuer key. Call generateWaltidIssuerKey first, and set WALTID_BASE_URL to the address the BROWSER uses." >&2
exit 1
fi
rm -rf "${out_dir}"
mkdir -p "${out_dir}"
check_return_code $?
# Only the names this deployment defines are substituted; anything else in the
# templates — ${defaultIssuerKey} and friends — is HOCON's own referencing and
# must survive untouched.
WALTID_TEMPLATE_DIR="${template_dir}" WALTID_OUT_DIR="${out_dir}" node -e '
var fs = require("fs");
var path = require("path");
var names = ["WALTID_BASE_URL", "WALTID_CI_TOKEN_KEY", "WALTID_ISSUER_DID",
"WALTID_KEY_D", "WALTID_KEY_X", "WALTID_KEY_Y",
"WALTID_KEYCLOAK_AUTHORIZE_URL", "WALTID_KEYCLOAK_TOKEN_URL",
"WALTID_KEYCLOAK_CLIENT_ID", "WALTID_KEYCLOAK_CLIENT_SECRET",
// and the verifier ones
"WALTID_VERIFIER_BASE_URL", "WALTID_VERIFIER_CLIENT_ID",
"WALTID_VERIFIER_KEY"];
var from = process.env.WALTID_TEMPLATE_DIR;
var to = process.env.WALTID_OUT_DIR;
var missing = [];
var rendered = [];
fs.readdirSync(from).filter(function (f) { return /\.conf$/.test(f); }).forEach(function (f) {
var text = fs.readFileSync(path.join(from, f), "utf8");
names.forEach(function (name) {
if (text.indexOf("${" + name + "}") === -1) return;
var value = process.env[name];
if (value === undefined || value === "") {
if (missing.indexOf(name) === -1) missing.push(name);
return;
}
text = text.split("${" + name + "}").join(value);
});
fs.writeFileSync(path.join(to, f), text);
rendered.push(f);
});
if (missing.length) {
console.error("ERROR: the walt.id configuration references " + missing.join(", ") +
", which are not set.");
process.exit(1);
}
console.log("Rendered " + rendered.length + " walt.id configuration file(s): " + rendered.join(", "));
'
local rc=$?
[ -n "${xtrace_was_on}" ] && set -x
check_return_code ${rc}
# The verifier's tree, when this deployment has one. Skipped rather than fatal:
# a checkout that predates the verifier, or a run that only wants the issuer,
# should still work.
if [ -d "${verifier_template_dir}" ] && [ -n "${WALTID_VERIFIER_BASE_URL:-}" ];
then
rm -rf "${verifier_out_dir}"
mkdir -p "${verifier_out_dir}"
check_return_code $?
WALTID_TEMPLATE_DIR="${verifier_template_dir}" WALTID_OUT_DIR="${verifier_out_dir}" \
WALTID_VERIFIER_CLIENT_ID="${WALTID_VERIFIER_CLIENT_ID:-verifier2}" node -e '
var fs = require("fs");
var path = require("path");
var names = ["WALTID_VERIFIER_BASE_URL", "WALTID_VERIFIER_CLIENT_ID", "WALTID_VERIFIER_KEY"];
var from = process.env.WALTID_TEMPLATE_DIR;
var to = process.env.WALTID_OUT_DIR;
var missing = [];
var rendered = [];
fs.readdirSync(from).filter(function (f) { return /\.conf$/.test(f); }).forEach(function (f) {
var text = fs.readFileSync(path.join(from, f), "utf8");
names.forEach(function (name) {
if (text.indexOf("${" + name + "}") === -1) return;
var value = process.env[name];
if (value === undefined || value === "") {
if (missing.indexOf(name) === -1) missing.push(name);
return;
}
text = text.split("${" + name + "}").join(value);
});
fs.writeFileSync(path.join(to, f), text);
rendered.push(f);
});
if (missing.length) {
console.error("ERROR: the walt.id verifier configuration references " + missing.join(", ") +
", which are not set.");
process.exit(1);
}
console.log("Rendered " + rendered.length + " walt.id verifier configuration file(s): " +
rendered.join(", "));
'
local vrc=$?
check_return_code ${vrc}
if grep -l '\${WALTID_' "${verifier_out_dir}"/*.conf >/dev/null 2>&1;
then
echo "ERROR: the rendered walt.id VERIFIER configuration still contains \${WALTID_...} references:" >&2
grep -n '\${WALTID_' "${verifier_out_dir}"/*.conf >&2
exit 1
fi
else
echo "No walt.id verifier configuration to render (WALTID_VERIFIER_BASE_URL unset or ${verifier_template_dir} missing)."
fi
# Anything left unexpanded would be read literally by the service, so say so
# here rather than letting it fail as a connection refused later.
if grep -l '\${WALTID_' "${out_dir}"/*.conf >/dev/null 2>&1;
then
echo "ERROR: the rendered walt.id configuration still contains \${WALTID_...} references:" >&2
grep -n '\${WALTID_' "${out_dir}"/*.conf >&2
exit 1
fi
echo "Leaving renderWaltidConfig()."
}
# ---------------------------------------------------------------------------
# Make sure the mock STS is on disk before anything tries to build it.
#
# sts/ is a SUBMODULE, not code in this repository: it is
# https://github.com/rcbj/iya-sts.git on branch main, and what this repository
# records is a link to it. Two things then depend on the checkout existing, and
# both fail a long way from the cause when it does not:
#
# * four compose files build the image with `context: ./sts`, and compose says
# "failed to read dockerfile" — a message about a path, not about a submodule;
# * tests/Dockerfile copies sts/common/vendored/bbs2023.js into the tests image so that
# tests/bbs2023_cryptosuite.js can check the wallet's cryptosuite against the
# issuer's, and the build says
# "COPY sts/common/vendored/bbs2023.js: not found".
#
# A `git clone` without --recurse-submodules leaves exactly that state: sts/