Skip to content

Commit 187e4ce

Browse files
committed
test(e2e): assert the tenant OIDC UID header on the control plane
The render matrix proves the chart emits --requestheader-uid-headers, but nothing checked that the flag survives the path a real tenant takes: aggregated API admission of spec.oidc, the HelmRelease, and the rendered KamajiControlPlane. Assert it in both OIDC helpers, next to the existing --authentication-config check on the same resource. Read the arg list once into a variable and grep the capture twice. The existing check consumed a pipeline, which is single-use, and a second kubectl call would double the management-cluster reads these helpers already make. Only the header flag is asserted. The chart also renders --feature-gates=RemoteRequestHeaderUID=true, but only on v1.32 where the gate is still alpha, while this lane takes the highest version in the map, which is above that. The header flag is the part that holds wherever the flag exists at all. The bats case for the missing flag runs the helper as a standalone subshell under set -e rather than as an if condition. Shells suppress errexit for a command whose status is being tested, and that reaches inside the subshell, so the if spelling reports success and pins nothing. The binding query next to these assertions piped kubectl straight into sort and so returned sort's status. Its callers capture it in a command substitution, where errexit does not reach, so a failed query read as an empty binding list. It now returns kubectl's failure itself. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <[email protected]>
1 parent 97832f3 commit 187e4ce

2 files changed

Lines changed: 118 additions & 12 deletions

File tree

‎hack/e2e-chainsaw/_lib/run-kubernetes.sh‎

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,27 @@ EOF
139139

140140
cozy_oidc_bindings() {
141141
local test_name="$1"
142-
kubectl --kubeconfig "tenantkubeconfig-${test_name}" get clusterrolebindings \
142+
local bindings
143+
bindings=$(kubectl --kubeconfig "tenantkubeconfig-${test_name}" get clusterrolebindings \
143144
--selector="app.kubernetes.io/managed-by=cozystack-oidc,app.kubernetes.io/instance=kubernetes-${test_name}" \
144-
-o 'jsonpath={range .items[*]}{.subjects[0].name}{"\t"}{.roleRef.name}{"\n"}{end}' |
145-
sort
145+
-o 'jsonpath={range .items[*]}{.subjects[0].name}{"\t"}{.roleRef.name}{"\n"}{end}') || return 1
146+
printf '%s' "${bindings}" | sort
147+
}
148+
149+
cozy_assert_oidc_apiserver_flags() {
150+
local release="$1"
151+
local extra_args
152+
153+
# Captured because a pipeline is single-use: a second grep on it reads nothing.
154+
extra_args=$(kubectl -n tenant-test get kamajicontrolplane "${release}" \
155+
-o jsonpath='{.spec.apiServer.extraArgs}')
156+
printf '%s\n' "${extra_args}" |
157+
grep -qF -- '--authentication-config=/etc/kubernetes/authentication-config/config.yaml'
158+
# --feature-gates=RemoteRequestHeaderUID=true is deliberately not asserted:
159+
# the chart renders it only on v1.32, while this lane takes the highest
160+
# version in the map, which is above that.
161+
printf '%s\n' "${extra_args}" |
162+
grep -qF -- '--requestheader-uid-headers=X-Remote-Uid'
146163
}
147164

148165
cozy_assert_oidc_system() {
@@ -154,9 +171,7 @@ cozy_assert_oidc_system() {
154171
kubectl -n tenant-test wait job "${release}-oidc-bootstrap" \
155172
--for=condition=complete --timeout=1m
156173

157-
kubectl -n tenant-test get kamajicontrolplane "${release}" \
158-
-o jsonpath='{.spec.apiServer.extraArgs}' |
159-
grep -qF -- '--authentication-config=/etc/kubernetes/authentication-config/config.yaml'
174+
cozy_assert_oidc_apiserver_flags "${release}"
160175

161176
authn_config=$(kubectl -n tenant-test get secret "${release}-oidc-authn-config" \
162177
-o jsonpath='{.data.config\.yaml}' | base64 -d)
@@ -206,9 +221,7 @@ cozy_switch_and_assert_oidc_custom_config() {
206221
kubectl -n tenant-test wait job "${release}-oidc-bootstrap" \
207222
--for=condition=complete --timeout=1m
208223

209-
kubectl -n tenant-test get kamajicontrolplane "${release}" \
210-
-o jsonpath='{.spec.apiServer.extraArgs}' |
211-
grep -qF -- '--authentication-config=/etc/kubernetes/authentication-config/config.yaml'
224+
cozy_assert_oidc_apiserver_flags "${release}"
212225

213226
authn_config=$(kubectl -n tenant-test get secret "${release}-oidc-authn-config" \
214227
-o jsonpath='{.data.config\.yaml}' | base64 -d)

‎hack/run-kubernetes-oidc_test.bats‎

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,26 @@
5050
}
5151
}
5252

53+
@test "OIDC binding query fails when kubectl cannot list the bindings" {
54+
# Piped straight into sort, the query took its status from sort, and a
55+
# failed kubectl read as an empty binding list. The status has to hold
56+
# without errexit too, because callers capture it in a substitution.
57+
. hack/e2e-chainsaw/_lib/run-kubernetes.sh
58+
kubectl() { echo "the server is currently unable to handle the request" >&2; return 1; }
59+
60+
if cozy_oidc_bindings demo >/dev/null 2>&1; then
61+
echo "a failed binding query was reported as success" >&2
62+
return 1
63+
fi
64+
}
65+
5366
@test "System OIDC assertion proves rendered objects bootstrap kubeconfig and tenant RBAC" {
5467
. hack/e2e-chainsaw/_lib/run-kubernetes.sh
5568
kubectl() {
5669
case "$*" in
5770
*" wait job kubernetes-demo-oidc-bootstrap "*) return 0 ;;
5871
*" get kamajicontrolplane kubernetes-demo "*)
59-
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml]'
72+
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml --requestheader-uid-headers=X-Remote-Uid]'
6073
;;
6174
*" get secret kubernetes-demo-oidc-authn-config "*)
6275
printf 'url: https://keycloak.example.test/realms/cozy\naudiences:\n- tenant-test-kubernetes-demo\n' | base64 | tr -d '\n'
@@ -76,6 +89,51 @@
7689
cozy_assert_oidc_system demo
7790
}
7891

92+
@test "System OIDC assertion fails when the aggregation-layer UID header is missing" {
93+
# Same mock as the passing case, minus --requestheader-uid-headers, so that
94+
# flag is the only thing left to fail on. Drop the grep for it from the helper
95+
# and this case goes red, which is what stops it passing for the wrong reason.
96+
. hack/e2e-chainsaw/_lib/run-kubernetes.sh
97+
kubectl() {
98+
case "$*" in
99+
*" wait job kubernetes-demo-oidc-bootstrap "*) return 0 ;;
100+
*" get kamajicontrolplane kubernetes-demo "*)
101+
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml]'
102+
;;
103+
*" get secret kubernetes-demo-oidc-authn-config "*)
104+
printf 'url: https://keycloak.example.test/realms/cozy\naudiences:\n- tenant-test-kubernetes-demo\n' | base64 | tr -d '\n'
105+
;;
106+
*" get keycloakclient.v1.edp.epam.com tenant-test-kubernetes-demo "*) printf 'true' ;;
107+
*" get keycloakclientscope.v1.edp.epam.com tenant-test-kubernetes-demo-audience "*) printf 'oidc-audience-mapper' ;;
108+
*" get secret kubernetes-demo-oidc-kubeconfig "*)
109+
printf 'args:\n- oidc-login\n- --oidc-client-id=tenant-test-kubernetes-demo\n' | base64 | tr -d '\n'
110+
;;
111+
*) echo "unexpected kubectl call: $*" >&2; return 1 ;;
112+
esac
113+
}
114+
cozy_oidc_bindings() {
115+
printf '[email protected]\tcluster-admin\[email protected]\tview\n'
116+
}
117+
118+
# The subshell restores `set -e`, which the chainsaw step runs the helper
119+
# under: without it a failing `grep -q` mid-function does not end the
120+
# function, and the exit status comes from the last assertion instead.
121+
#
122+
# It has to stand as its own command, not as an `if` condition. Shells
123+
# suppress errexit for a command whose status is being tested, and that
124+
# suppression reaches inside the subshell too, which is why the plain
125+
# `if ( set -e; ... )` spelling reports success here.
126+
local rc=0
127+
set +e
128+
( set -e; cozy_assert_oidc_system demo )
129+
rc=$?
130+
set -e
131+
if [ "${rc}" -eq 0 ]; then
132+
echo "the System assertion passed without --requestheader-uid-headers" >&2
133+
return 1
134+
fi
135+
}
136+
79137
@test "CustomConfig transition fails when it cannot ask whether System leftovers are gone" {
80138
# The teardown probes used `if kubectl get ... >/dev/null 2>&1`, which reads
81139
# every non-zero exit as "the object is gone" -- including an RBAC denial, a
@@ -101,7 +159,7 @@
101159
*" patch kuberneteses.apps.cozystack.io demo "*) : ;;
102160
*" wait job kubernetes-demo-oidc-bootstrap "*) return 0 ;;
103161
*" get kamajicontrolplane kubernetes-demo "*)
104-
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml]'
162+
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml --requestheader-uid-headers=X-Remote-Uid]'
105163
;;
106164
*" get secret kubernetes-demo-oidc-authn-config "*)
107165
printf 'url: https://idp.byo.example.test\naudiences:\n- cozystack-byo-demo\n' | base64 | tr -d '\n'
@@ -123,6 +181,41 @@
123181
done
124182
}
125183

184+
@test "CustomConfig transition fails when the aggregation-layer UID header is missing" {
185+
# The System helper's twin. Same mock as the passing transition below, minus
186+
# --requestheader-uid-headers, so that flag is the only thing left to fail on.
187+
. hack/e2e-chainsaw/_lib/run-kubernetes.sh
188+
kubectl() {
189+
case "$*" in
190+
*" get helmrelease kubernetes-demo "*) printf '7' ;;
191+
*" patch kuberneteses.apps.cozystack.io demo "*) : ;;
192+
*" wait job kubernetes-demo-oidc-bootstrap "*) return 0 ;;
193+
*" get kamajicontrolplane kubernetes-demo "*)
194+
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml]'
195+
;;
196+
*" get secret kubernetes-demo-oidc-authn-config "*)
197+
printf 'url: https://idp.byo.example.test\naudiences:\n- cozystack-byo-demo\n' | base64 | tr -d '\n'
198+
;;
199+
*--ignore-not-found*) : ;;
200+
*) echo "unexpected kubectl call: $*" >&2; return 1 ;;
201+
esac
202+
}
203+
cozy_wait_helmrelease_upgrade() { :; }
204+
cozy_oidc_bindings() { printf '[email protected]\tcluster-admin\n'; }
205+
206+
# Standalone subshell, not an `if` condition: errexit is suppressed for a
207+
# command whose status is tested, and that reaches inside the subshell.
208+
local rc=0
209+
set +e
210+
( set -e; cozy_switch_and_assert_oidc_custom_config demo )
211+
rc=$?
212+
set -e
213+
if [ "${rc}" -eq 0 ]; then
214+
echo "the transition passed without --requestheader-uid-headers" >&2
215+
return 1
216+
fi
217+
}
218+
126219
@test "CustomConfig transition patches the live cluster waits for the new generation and rejects System leftovers" {
127220
. hack/e2e-chainsaw/_lib/run-kubernetes.sh
128221
calls=$(mktemp)
@@ -144,7 +237,7 @@
144237
;;
145238
*" wait job kubernetes-demo-oidc-bootstrap "*) return 0 ;;
146239
*" get kamajicontrolplane kubernetes-demo "*)
147-
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml]'
240+
printf '[--authentication-config=/etc/kubernetes/authentication-config/config.yaml --requestheader-uid-headers=X-Remote-Uid]'
148241
;;
149242
*" get secret kubernetes-demo-oidc-authn-config "*)
150243
printf 'url: https://idp.byo.example.test\naudiences:\n- cozystack-byo-demo\n' | base64 | tr -d '\n'

0 commit comments

Comments
 (0)