mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-06 20:13:43 +09:00
CI E2E showed the users-Job assertion returning `DESIRED_USERS_JSON:
[]` — but the CR body had `users: [{email: e2e-admin@example.com,
role: Admin}]`. Root cause: `hook-delete-policy: before-hook-creation`
keeps the previous hook Job around across helm upgrades, so `until
kubectl get job` matches a stale Job from the previous customconfig-
mode test where users may have been unset (the secretRef swap CR had
no `users:` field, so the schema default `[]` propagated to the
prior Job's DESIRED_USERS_JSON). The System test then grabbed that
stale Job before the helm upgrade for the new CR completed and swapped
in the fresh Job.
Fix: poll on the target env value itself — the new Job with the
correct users list is the first Job whose DESIRED_USERS_JSON string
contains `e2e-admin@example.com`. 600s window matches the inner HR's
15m timeout with room for a slow reconcile.
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
231 lines
10 KiB
Bash
Executable File
231 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
|
|
# Phase-1 OIDC System-mode e2e for the Grafana instance, render-side
|
|
# only.
|
|
#
|
|
# Pairs with monitoring-oidc-customconfig.bats (BYO issuer path).
|
|
#
|
|
# Does NOT drive a full browser login through Keycloak: that path is
|
|
# deferred to a follow-up integration suite (same posture as the
|
|
# tenant kube-apiserver's kubernetes-oidc-system.bats).
|
|
#
|
|
# What is exercised here on a live cluster: that the new
|
|
# `spec.oidc.*` fields are admitted by the Monitoring CR; that
|
|
# cozystack-api accepts the updated schema; that the HelmRelease
|
|
# renders the OIDC templates; and that the resulting KeycloakClient /
|
|
# KeycloakClientScope / persistent client-secret Secret / Grafana CR /
|
|
# users-reconcile Job carry the expected shape. Chart-owned
|
|
# KeycloakRealmGroups + role_attribute_path are gone (see the
|
|
# design note in docs/oidc-grafana.md); authorization is driven by
|
|
# spec.oidc.users reconciled into Grafana orgs by the users-Job.
|
|
|
|
# cozytest.sh (the e2e runner) is not real bats: it never invokes
|
|
# setup()/teardown(). Cleanup belongs in cozy_cleanup(), which runs at
|
|
# suite exit and on the first failing test. Per-test isolation is done
|
|
# inline at the top of each @test.
|
|
#
|
|
# Naming: the extra/monitoring chart is a singleton per tenant
|
|
# namespace and enforces `.Release.Name == "monitoring"` in
|
|
# templates/check-release-name.yaml. So the Monitoring CR (and its
|
|
# operator-produced outer HelmRelease) MUST be named `monitoring`;
|
|
# the inner packages/system/monitoring HelmRelease that carries the
|
|
# OIDC templates is then named `${outer}-system`, i.e. `monitoring-system`,
|
|
# and every OIDC identifier derived by the helpers (clientId, audience
|
|
# scope, client-secret Secret) is built off that inner name.
|
|
CR_NAME="monitoring"
|
|
INNER_REL="${CR_NAME}-system"
|
|
CID="tenant-test-${INNER_REL}"
|
|
|
|
cleanup_mon() {
|
|
kubectl -n tenant-test delete monitoring.apps.cozystack.io "${CR_NAME}" \
|
|
--ignore-not-found --wait=false 2>/dev/null || true
|
|
kubectl -n tenant-test wait monitoring.apps.cozystack.io "${CR_NAME}" \
|
|
--for=delete --timeout=2m 2>/dev/null || true
|
|
}
|
|
|
|
cozy_cleanup() { cleanup_mon; }
|
|
|
|
@test "Monitoring CR accepts spec.oidc.mode=System" {
|
|
cleanup_mon
|
|
kubectl apply -f - <<EOF
|
|
apiVersion: apps.cozystack.io/v1alpha1
|
|
kind: Monitoring
|
|
metadata:
|
|
name: ${CR_NAME}
|
|
namespace: tenant-test
|
|
spec:
|
|
host: ""
|
|
metricsStorages:
|
|
- name: shortterm
|
|
retentionPeriod: "3d"
|
|
deduplicationInterval: "15s"
|
|
storage: 10Gi
|
|
storageClassName: replicated
|
|
logsStorages:
|
|
- name: generic
|
|
retentionPeriod: "1"
|
|
storage: 10Gi
|
|
storageClassName: replicated
|
|
oidc:
|
|
mode: System
|
|
users:
|
|
- email: e2e-admin@example.com
|
|
role: Admin
|
|
EOF
|
|
|
|
# Outer HR shares the CR name; inner HR (chart target) is ${outer}-system.
|
|
timeout 60 sh -ec 'until kubectl -n tenant-test get hr "'"${CR_NAME}"'" >/dev/null 2>&1; do sleep 2; done'
|
|
timeout 120 sh -ec 'until kubectl -n tenant-test get hr "'"${INNER_REL}"'" >/dev/null 2>&1; do sleep 2; done'
|
|
|
|
# Wait for the chart to render its Grafana CR. We do not wait on
|
|
# HR Ready: that requires a full VictoriaMetrics + Postgres bringup
|
|
# which the render-side test deliberately avoids.
|
|
timeout 180 sh -ec 'until kubectl -n tenant-test get grafana grafana >/dev/null 2>&1; do sleep 5; done'
|
|
}
|
|
|
|
@test "Grafana CR carries auth.generic_oauth pointing at the cozy realm" {
|
|
# Wait for the Grafana CR to actually reflect mode=System values —
|
|
# `until kubectl get grafana` only proves the CR EXISTS (it may still
|
|
# be a stale copy from a previous test or from before the inner HR
|
|
# picked up the new values). Poll the specific field the test is
|
|
# about to assert on so the race window closes.
|
|
timeout 180 sh -ec '
|
|
until [ "$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath="{.spec.config.auth\.generic_oauth.enabled}" 2>/dev/null)" = "true" ]; do
|
|
sleep 5
|
|
done
|
|
'
|
|
|
|
enabled=$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath='{.spec.config.auth\.generic_oauth.enabled}')
|
|
echo "generic_oauth.enabled: ${enabled}"
|
|
[ "${enabled}" = "true" ]
|
|
|
|
client_id=$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath='{.spec.config.auth\.generic_oauth.client_id}')
|
|
echo "client_id: ${client_id}"
|
|
[ "${client_id}" = "${CID}" ]
|
|
|
|
auth_url=$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath='{.spec.config.auth\.generic_oauth.auth_url}')
|
|
echo "auth_url: ${auth_url}"
|
|
echo "${auth_url}" | grep -qE '/realms/cozy/protocol/openid-connect/auth$'
|
|
}
|
|
|
|
@test "Persistent client-secret Secret is created and 32-char random" {
|
|
# clientSecretName helper: printf "%s-oidc-client" .Release.Name -> ${INNER_REL}-oidc-client.
|
|
SEC="${INNER_REL}-oidc-client"
|
|
timeout 60 sh -ec 'until kubectl -n tenant-test get secret "'"${SEC}"'" >/dev/null 2>&1; do sleep 2; done'
|
|
value=$(kubectl -n tenant-test get secret "${SEC}" \
|
|
-o jsonpath='{.data.client-secret}' | base64 -d)
|
|
echo "client-secret length: ${#value}"
|
|
[ "${#value}" = "32" ]
|
|
# No unexpected characters.
|
|
echo "${value}" | grep -qE '^[A-Za-z0-9]+$'
|
|
}
|
|
|
|
@test "Per-instance KeycloakClient + KeycloakClientScope land in cozy (no groups)" {
|
|
# The EDP Keycloak operator API group is only present when the
|
|
# platform-level OIDC feature is on. Skip gracefully otherwise so the
|
|
# test suite works on runners without Keycloak installed.
|
|
if ! kubectl api-resources --api-group=v1.edp.epam.com >/dev/null 2>&1; then
|
|
skip "EDP Keycloak operator CRDs not present on this cluster"
|
|
fi
|
|
|
|
timeout 60 sh -ec 'until kubectl -n tenant-test get keycloakclient.v1.edp.epam.com "'"${CID}"'" >/dev/null 2>&1; do sleep 2; done'
|
|
# The EDP Keycloak operator's CRD strips `spec.public: false` on write
|
|
# (schema default is false, so a false value never materialises in the
|
|
# applied object). Assert confidentiality via clientAuthenticatorType,
|
|
# which the operator DOES persist.
|
|
auth_type=$(kubectl -n tenant-test get keycloakclient.v1.edp.epam.com "${CID}" \
|
|
-o jsonpath='{.spec.clientAuthenticatorType}')
|
|
echo "client authenticator type: ${auth_type}"
|
|
[ "${auth_type}" = "client-secret" ]
|
|
|
|
timeout 30 sh -ec 'until kubectl -n tenant-test get keycloakclientscope.v1.edp.epam.com "'"${CID}"'-audience" >/dev/null 2>&1; do sleep 2; done'
|
|
mapper=$(kubectl -n tenant-test get keycloakclientscope.v1.edp.epam.com "${CID}-audience" \
|
|
-o jsonpath='{.spec.protocolMappers[0].protocolMapper}')
|
|
echo "audience mapper: ${mapper}"
|
|
[ "${mapper}" = "oidc-audience-mapper" ]
|
|
|
|
# No chart-owned KeycloakRealmGroups — directory objects stay owned
|
|
# by whoever runs the cozy realm; authorization is app-side (see the
|
|
# users-Job assertion below).
|
|
for role in admin editor viewer; do
|
|
if kubectl -n tenant-test get keycloakrealmgroup.v1.edp.epam.com "${CID}-${role}" >/dev/null 2>&1; then
|
|
echo "unexpected chart-owned realm group ${CID}-${role} present" >&2
|
|
false
|
|
fi
|
|
done
|
|
}
|
|
|
|
@test "Grafana config has no role_attribute_path and enables skip_org_role_sync" {
|
|
# Same wait as the first Grafana-CR test — poll the specific field
|
|
# rather than merely the CR's existence so we don't hit a stale copy.
|
|
timeout 180 sh -ec '
|
|
until [ "$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath="{.spec.config.auth\.generic_oauth.skip_org_role_sync}" 2>/dev/null)" = "true" ]; do
|
|
sleep 5
|
|
done
|
|
'
|
|
|
|
role_attr=$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath='{.spec.config.auth\.generic_oauth.role_attribute_path}')
|
|
echo "role_attribute_path: ${role_attr}"
|
|
[ -z "${role_attr}" ]
|
|
|
|
skip_sync=$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath='{.spec.config.auth\.generic_oauth.skip_org_role_sync}')
|
|
echo "skip_org_role_sync: ${skip_sync}"
|
|
[ "${skip_sync}" = "true" ]
|
|
|
|
email_lookup=$(kubectl -n tenant-test get grafana grafana \
|
|
-o jsonpath='{.spec.config.auth\.generic_oauth.oauth_allow_insecure_email_lookup}')
|
|
echo "oauth_allow_insecure_email_lookup: ${email_lookup}"
|
|
[ "${email_lookup}" = "true" ]
|
|
}
|
|
|
|
@test "users-Job runs and carries the desired list from spec.oidc.users" {
|
|
# helm.sh/hook: post-install,post-upgrade — the Job appears after the
|
|
# inner HR reconciles. `hook-delete-policy: before-hook-creation`
|
|
# keeps the previous Job around across upgrades, so `until kubectl
|
|
# get job` may return a stale Job from the previous test (or from
|
|
# the customconfig round where users was set differently or absent).
|
|
# Poll on the target env value directly — the new Job with our
|
|
# desired list is the first Job whose DESIRED_USERS_JSON contains
|
|
# `e2e-admin@example.com`.
|
|
timeout 600 sh -ec '
|
|
until kubectl -n tenant-test get job "'"${INNER_REL}"'-oidc-users" \
|
|
-o jsonpath="{.spec.template.spec.containers[?(@.name==\"reconcile\")].env[?(@.name==\"DESIRED_USERS_JSON\")].value}" 2>/dev/null \
|
|
| grep -q "e2e-admin@example.com"; do
|
|
sleep 5
|
|
done
|
|
'
|
|
|
|
desired=$(kubectl -n tenant-test get job "${INNER_REL}-oidc-users" \
|
|
-o jsonpath='{.spec.template.spec.containers[?(@.name=="reconcile")].env[?(@.name=="DESIRED_USERS_JSON")].value}')
|
|
echo "DESIRED_USERS_JSON: ${desired}"
|
|
echo "${desired}" | grep -q 'e2e-admin@example.com'
|
|
echo "${desired}" | grep -q '"role":"Admin"'
|
|
|
|
mode=$(kubectl -n tenant-test get job "${INNER_REL}-oidc-users" \
|
|
-o jsonpath='{.spec.template.spec.containers[?(@.name=="reconcile")].env[?(@.name=="OIDC_MODE")].value}')
|
|
echo "OIDC_MODE: ${mode}"
|
|
[ "${mode}" = "System" ]
|
|
}
|
|
|
|
@test "Grafana Deployment injects GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET from the Secret" {
|
|
# Grafana operator materialises a Deployment named `grafana-deployment`
|
|
# from the Grafana CR. Poll for it before asserting on the env.
|
|
timeout 120 sh -ec 'until kubectl -n tenant-test get deploy grafana-deployment >/dev/null 2>&1; do sleep 5; done'
|
|
envs=$(kubectl -n tenant-test get deploy grafana-deployment \
|
|
-o jsonpath='{.spec.template.spec.containers[?(@.name=="grafana")].env[*].name}')
|
|
echo "grafana env: ${envs}"
|
|
echo "${envs}" | grep -qw "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET"
|
|
|
|
ref=$(kubectl -n tenant-test get deploy grafana-deployment \
|
|
-o jsonpath='{.spec.template.spec.containers[?(@.name=="grafana")].env[?(@.name=="GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET")].valueFrom.secretKeyRef.name}')
|
|
echo "client-secret env source: ${ref}"
|
|
[ "${ref}" = "${INNER_REL}-oidc-client" ]
|
|
}
|