diff --git a/hack/e2e-chainsaw/kubernetes-oidc-customconfig/chainsaw-test.yaml b/hack/e2e-chainsaw/kubernetes-oidc-customconfig/chainsaw-test.yaml new file mode 100644 index 000000000..0c86ca953 --- /dev/null +++ b/hack/e2e-chainsaw/kubernetes-oidc-customconfig/chainsaw-test.yaml @@ -0,0 +1,117 @@ +# Chainsaw port of hack/e2e-apps/kubernetes-oidc-customconfig.bats. +# +# Phase-1 OIDC CustomConfig selector e2e — render-side only. Pairs with the +# kubernetes-oidc-system suite; see its header for why no Kamaji bringup and +# no HR Ready wait (minReplicas: 0, HR asserted to exist only). +# +# What is exercised on a live cluster: spec.oidc.mode: CustomConfig is +# admitted; the HelmRelease renders; the tenant-supplied +# AuthenticationConfiguration is wired onto the KamajiControlPlane; and — +# crucially — NO Keycloak objects are created in the cozy realm when the +# tenant brings their own issuer, and the placeholder OIDC kubeconfig Secret +# is NOT pre-rendered (System mode pre-renders it; CustomConfig distributes +# out-of-band). +# +# The absence checks run AFTER the positive authn-config Secret assert +# establishes the chart has finished rendering — otherwise a not-yet- +# reconciled HR would false-pass them, a hazard the sequential bats never had. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: kubernetes-oidc-customconfig +spec: + catch: + - events: {} + - describe: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + name: kubernetes-oidc-byo + - describe: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha1 + kind: KamajiControlPlane + name: kubernetes-oidc-byo + steps: + - name: create-kubernetes-oidc-byo + try: + - apply: + file: kubernetes-oidc-byo.yaml + - assert: + timeout: 1m + resource: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + metadata: + name: kubernetes-oidc-byo + - assert: + timeout: 3m + resource: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha1 + kind: KamajiControlPlane + metadata: + name: kubernetes-oidc-byo + + - name: kamajicontrolplane-still-carries-authentication-config + try: + - assert: + resource: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha1 + kind: KamajiControlPlane + metadata: + name: kubernetes-oidc-byo + spec: + apiServer: + (contains(extraArgs, '--authentication-config=/etc/kubernetes/authentication-config/config.yaml')): true + + - name: authn-config-secret-carries-tenant-issuer + try: + - assert: + timeout: 1m + resource: + apiVersion: v1 + kind: Secret + metadata: + name: kubernetes-oidc-byo-oidc-authn-config + - script: + content: | + set -eu + body=$(kubectl -n "$NAMESPACE" get secret kubernetes-oidc-byo-oidc-authn-config \ + -o jsonpath='{.data.config\.yaml}' | base64 -d) + echo "$body" | grep -qF "url: https://idp.byo.example.test" + echo "$body" | grep -qF "cozystack-byo-oidc-byo" + # Fail-loud negation (a bare `! ... | grep` is vacuous under set -e): + # the cozy-realm issuer must NOT appear in a BYO config. + if echo "$body" | grep -qE 'url: https://keycloak\.[^/]+/realms/cozy'; then + echo "FAIL: BYO AuthenticationConfiguration must not carry the cozy realm issuer" >&2 + exit 1 + fi + check: + ($error == null): true + + # Absence proofs — ordered after the positive asserts above (chart has + # rendered), so `error` op polling to its timeout is a real negative, not a + # not-yet-reconciled false pass. + - name: no-keycloak-objects-and-no-prerendered-kubeconfig + try: + - error: + timeout: 30s + resource: + apiVersion: v1.edp.epam.com/v1 + kind: KeycloakClient + metadata: + name: tenant-test-kubernetes-oidc-byo + - error: + timeout: 30s + resource: + apiVersion: v1.edp.epam.com/v1 + kind: KeycloakClientScope + metadata: + name: tenant-test-kubernetes-oidc-byo-audience + # System mode pre-renders -oidc-kubeconfig as a placeholder; + # CustomConfig must not. + - error: + timeout: 30s + resource: + apiVersion: v1 + kind: Secret + metadata: + name: kubernetes-oidc-byo-oidc-kubeconfig diff --git a/hack/e2e-chainsaw/kubernetes-oidc-customconfig/kubernetes-oidc-byo.yaml b/hack/e2e-chainsaw/kubernetes-oidc-customconfig/kubernetes-oidc-byo.yaml new file mode 100644 index 000000000..3a499a938 --- /dev/null +++ b/hack/e2e-chainsaw/kubernetes-oidc-customconfig/kubernetes-oidc-byo.yaml @@ -0,0 +1,48 @@ +apiVersion: apps.cozystack.io/v1alpha1 +kind: Kubernetes +metadata: + name: oidc-byo +spec: + addons: + ingressNginx: + enabled: false + hosts: [] + valuesOverride: {} + controlPlane: + apiServer: + resources: {} + resourcesPreset: small + replicas: 1 + nodeGroups: + md0: + diskSize: 20Gi + gpus: [] + instanceType: u1.micro + maxReplicas: 1 + minReplicas: 0 + resources: {} + roles: + - worker + storageClass: replicated + version: v1.35 + oidc: + mode: CustomConfig + customConfig: + config: | + apiVersion: apiserver.config.k8s.io/v1beta1 + kind: AuthenticationConfiguration + jwt: + - issuer: + url: https://idp.byo.example.test + audiences: + - cozystack-byo-oidc-byo + claimMappings: + username: + claim: preferred_username + prefix: "" + groups: + claim: groups + prefix: "" + users: + - email: byo-admin@example.test + role: admin diff --git a/hack/e2e-chainsaw/kubernetes-oidc-system/chainsaw-test.yaml b/hack/e2e-chainsaw/kubernetes-oidc-system/chainsaw-test.yaml new file mode 100644 index 000000000..14076097e --- /dev/null +++ b/hack/e2e-chainsaw/kubernetes-oidc-system/chainsaw-test.yaml @@ -0,0 +1,123 @@ +# Chainsaw port of hack/e2e-apps/kubernetes-oidc-system.bats. +# +# Phase-1 OIDC System-mode e2e — render-side only. Pairs with the +# kubernetes-oidc-customconfig suite (BYO issuer path). +# +# Does NOT spin up a full Kamaji control plane: that path is covered by the +# kubernetes-latest/previous suites and would add another ~25 min bringup for +# a feature whose end-to-end OIDC browser flow is explicitly out of scope +# (see docs/oidc-tenant.md -> Phase 1). minReplicas: 0 means no worker VMs; +# the suite runs in minutes. Accordingly the HR is only asserted to EXIST +# (cozystack-api materialised it), never Ready. +# +# What is exercised on a live cluster: the new spec.oidc.* fields are +# admitted by the aggregated API; the HelmRelease renders the OIDC templates; +# and the resulting KamajiControlPlane / AuthenticationConfiguration Secret / +# KeycloakClient / KeycloakClientScope carry the expected per-cluster shape. +# The Keycloak asserts are unconditional: the chart capability-gates their +# rendering on v1.edp.epam.com/v1 (oidc-keycloak.yaml), and the CI platform +# always ships the operator — the bats' api-resources guard was dead code +# there (and its `skip` did not exist under cozytest anyway). +# +# The Kubernetes CR is applied declaratively, so Chainsaw auto-cleanup +# deletes it at test end — the bats' hand-rolled cleanup_kr/cozy_cleanup +# hooks have no equivalent here by construction. +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: kubernetes-oidc-system +spec: + catch: + - events: {} + - describe: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + name: kubernetes-oidc-system + - describe: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha1 + kind: KamajiControlPlane + name: kubernetes-oidc-system + steps: + - name: create-kubernetes-oidc-system + try: + - apply: + file: kubernetes-oidc-system.yaml + # cozystack-api materialises the HelmRelease… + - assert: + timeout: 1m + resource: + apiVersion: helm.toolkit.fluxcd.io/v2 + kind: HelmRelease + metadata: + name: kubernetes-oidc-system + # …and the chart renders the KamajiControlPlane (no Ready wait — that + # would require the Kamaji bringup this suite deliberately avoids). + - assert: + timeout: 3m + resource: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha1 + kind: KamajiControlPlane + metadata: + name: kubernetes-oidc-system + + - name: kamajicontrolplane-oidc-wiring + try: + - assert: + resource: + apiVersion: controlplane.cluster.x-k8s.io/v1alpha1 + kind: KamajiControlPlane + metadata: + name: kubernetes-oidc-system + spec: + apiServer: + # --authentication-config is appended to extraArgs; the + # chart-owned mount lands on extraVolumeMounts. + (contains(extraArgs, '--authentication-config=/etc/kubernetes/authentication-config/config.yaml')): true + (extraVolumeMounts[?name == 'authentication-config']): + - name: authentication-config + deployment: + # The chart-owned volume lands on extraVolumes. + (extraVolumes[?name == 'authentication-config']): + - name: authentication-config + + - name: authn-config-secret + try: + - assert: + timeout: 1m + resource: + apiVersion: v1 + kind: Secret + metadata: + name: kubernetes-oidc-system-oidc-authn-config + # The issuer check is a regex (any keycloak.), so it stays a script; + # the per-cluster audience literal rides along. + - script: + content: | + set -eu + body=$(kubectl -n "$NAMESPACE" get secret kubernetes-oidc-system-oidc-authn-config \ + -o jsonpath='{.data.config\.yaml}' | base64 -d) + echo "$body" | grep -qE 'url: https://keycloak\.[^/]+/realms/cozy' + echo "$body" | grep -qF -- "- tenant-test-kubernetes-oidc-system" + check: + ($error == null): true + + - name: keycloak-objects-in-cozy-realm + try: + - assert: + timeout: 1m + resource: + apiVersion: v1.edp.epam.com/v1 + kind: KeycloakClient + metadata: + name: tenant-test-kubernetes-oidc-system + spec: + public: true + - assert: + timeout: 1m + resource: + apiVersion: v1.edp.epam.com/v1 + kind: KeycloakClientScope + metadata: + name: tenant-test-kubernetes-oidc-system-audience + spec: + (protocolMappers[0].protocolMapper): oidc-audience-mapper diff --git a/hack/e2e-chainsaw/kubernetes-oidc-system/kubernetes-oidc-system.yaml b/hack/e2e-chainsaw/kubernetes-oidc-system/kubernetes-oidc-system.yaml new file mode 100644 index 000000000..e50601d47 --- /dev/null +++ b/hack/e2e-chainsaw/kubernetes-oidc-system/kubernetes-oidc-system.yaml @@ -0,0 +1,34 @@ +apiVersion: apps.cozystack.io/v1alpha1 +kind: Kubernetes +metadata: + name: oidc-system +spec: + addons: + ingressNginx: + enabled: false + hosts: [] + valuesOverride: {} + controlPlane: + apiServer: + resources: {} + resourcesPreset: small + replicas: 1 + nodeGroups: + md0: + diskSize: 20Gi + gpus: [] + instanceType: u1.micro + maxReplicas: 1 + minReplicas: 0 + resources: {} + roles: + - worker + storageClass: replicated + version: v1.35 + oidc: + mode: System + users: + - email: e2e-admin@example.test + role: admin + - email: e2e-viewer@example.test + role: view