From 2d9110b0c4fa6c25b9ddabc03e6fc9be4dcbb029 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Sun, 10 May 2026 03:31:51 +0000 Subject: [PATCH] auth-bundle-2 Phase 0: dependency-add + oidc auth-type literal + runtime guard Bundle 2 Phase 0 stages the dependencies + auth-type discriminator literal that later phases consume. No handler chain wired yet; an operator who sets CERTCTL_AUTH_TYPE=oidc on this commit gets a clear refuse-to-start error rather than a silent fallback to api-key (the G-1 failure mode that drove "jwt" out of the allowed set). Deliverables: * go.mod: github.com/coreos/go-oidc/v3 v3.18.0 added as a direct require. Per the pre-bundle dependency audit (Apache-2.0, zero CVEs ever per OSV.dev, 2,400+ stars, used by Hashicorp Vault + Dex + Hydra + Authentik + every Kubernetes OIDC integration), this is the ecosystem-standard Go OIDC client. Pinned to a specific minor (v3.18.0) per the prompt's "no bare latest" rule. * go.mod: golang.org/x/oauth2 promoted from // indirect to direct, bumped from v0.34.0 to v0.36.0 by go mod tidy. Both versions are OSV-clean. Maintained by the Go team. * No JSON-path library added (forbidden by the dependency audit; the group-claim resolver is hand-rolled in Phase 3). * internal/config/config.go: AuthTypeOIDC constant added with a load-bearing comment explaining (a) this is the AUTH-TYPE literal, not a JWT alg literal, so the G-1 closure invariant is preserved ("jwt" stays out of ValidAuthTypes forever); (b) the runtime guard in cmd/server/main.go intentionally refuses-to-start when oidc is set pre-Phase-6 to avoid the silent-downgrade failure mode. ValidAuthTypes() now returns {api-key, none, oidc}. * internal/config/config_test.go: TestValidAuthTypesIsExactly_APIKey_None renamed to TestValidAuthTypesIsExactly_APIKey_None_OIDC and now pins the 3-entry set. TestValidAuthTypesDoesNotContainJWT (G-1 closure test) still passes because "jwt" is never added back. TestValidate_GenericInvalidAuthType's bad-types list updated: "oidc" removed (now valid), "saml" added (correctly rejected per Decision 5's SAML deferral). * cmd/server/main.go: defense-in-depth runtime auth-type guard now has an explicit AuthTypeOIDC case that exit(1)s with an actionable message: "the OIDC auth chain is not yet wired in this build (Auth Bundle 2 Phase 6 ships the session middleware that consumes this auth-type literal)." This closes the lying-field gap the literal would otherwise create. Phase 6 of Bundle 2 relaxes this case to fall through alongside api-key + none. * api/openapi.yaml: /v1/auth/info auth_type enum extended from [api-key, none] to [api-key, none, oidc] with an in-line comment explaining the Phase-0-vs-Phase-6 timing so an OpenAPI consumer isn't surprised by "oidc" appearing here pre-Bundle-2-merge. * deploy/helm/certctl/templates/_helpers.tpl::certctl.validateAuthType: valid set extended to include "oidc". Chart-time validation now passes for type=oidc; the binary's runtime guard takes over to refuse the start. Once Bundle 2 ships, the runtime guard relaxes and OIDC works end-to-end with no further chart edits. * .env.example: CERTCTL_AUTH_TYPE comment block updated to document the three valid values + the Phase-0-vs-Phase-6 timing. * internal/auth/oidc/doc.go: new package directory with package doc + transitional blank imports for coreos/go-oidc/v3 + x/oauth2 so go mod tidy keeps both deps as direct requires until Phase 3's service.go replaces the blanks with real symbol use. Doc explains the package layout (oidc/ + oidc/domain/ + oidc/groupclaim/ + oidc/testfixtures/) so the post-Bundle-2 reader can navigate. Verifications: * gofmt clean on every changed file. * go vet clean on internal/config + cmd/server + internal/auth/oidc. * go test -short -count=1 green on internal/config (including the G-1 closure + new validation tests), cmd/server, internal/auth (all Bundle 1 packages), internal/service/auth. * govulncheck ./... clean (M-024 hard CI gate). * All 24 ci-guards pass locally. Phase 0 exit criteria from cowork/auth-bundle-2-prompt.md: * go.mod shows coreos/go-oidc/v3 as direct: yes. * golang.org/x/oauth2 is direct (not indirect): yes. * govulncheck ./... clean: yes. * No JSON-path library in go.mod / go.sum deltas: confirmed (only v3 of go-oidc + the x/oauth2 bump landed). * make verify green: gofmt + vet + go test pass; full make verify (which would invoke golangci-lint) deferred to CI since the sandbox doesn't have golangci-lint installed; the operator runs make verify locally before pushing per CLAUDE.md operating rule. --- .env.example | 20 +++++---- api/openapi.yaml | 23 ++++++++--- cmd/server/main.go | 13 ++++++ deploy/helm/certctl/templates/_helpers.tpl | 4 +- go.mod | 3 +- go.sum | 6 ++- internal/auth/oidc/doc.go | 47 ++++++++++++++++++++++ internal/config/config.go | 24 ++++++++++- internal/config/config_test.go | 24 ++++++----- 9 files changed, 135 insertions(+), 29 deletions(-) create mode 100644 internal/auth/oidc/doc.go diff --git a/.env.example b/.env.example index 9c366e4..31cfe05 100644 --- a/.env.example +++ b/.env.example @@ -30,14 +30,18 @@ CERTCTL_SERVER_PORT=8443 CERTCTL_LOG_LEVEL=info CERTCTL_LOG_FORMAT=json -# Auth type: "api-key" (production) or "none" (demo/development). -# For JWT/OIDC, run an authenticating gateway in front of certctl -# (oauth2-proxy / Envoy ext_authz / Traefik ForwardAuth / Pomerium) and -# set CERTCTL_AUTH_TYPE=none on the upstream — see -# docs/architecture.md "Authenticating-gateway pattern". G-1 removed -# the in-process "jwt" option (no JWT middleware shipped — silent auth -# downgrade); see docs/upgrade-to-v2-jwt-removal.md if you previously -# set CERTCTL_AUTH_TYPE=jwt. +# Auth type: "api-key" (production), "none" (demo/development), or +# "oidc" (Auth Bundle 2 - native OIDC SSO via coreos/go-oidc/v3, ships +# in Bundle 2 phases 5+6; setting CERTCTL_AUTH_TYPE=oidc on a build +# without Bundle 2 wired triggers a clear refuse-to-start error rather +# than a silent fallback to api-key). For JWT / SAML / LDAP, continue to +# run an authenticating gateway in front of certctl (oauth2-proxy / +# Envoy ext_authz / Traefik ForwardAuth / Pomerium) and set +# CERTCTL_AUTH_TYPE=none on the upstream - see docs/architecture.md +# "Authenticating-gateway pattern". G-1 removed the in-process "jwt" +# option (no JWT middleware shipped - silent auth downgrade); see +# docs/upgrade-to-v2-jwt-removal.md if you previously set +# CERTCTL_AUTH_TYPE=jwt. CERTCTL_AUTH_TYPE=none # Required when CERTCTL_AUTH_TYPE is "api-key". # Generate with: openssl rand -base64 32 diff --git a/api/openapi.yaml b/api/openapi.yaml index 2ba89c0..5c72b63 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -134,12 +134,23 @@ paths: type: string # G-1 (P1): "jwt" removed from this enum after the silent # auth downgrade was identified — no JWT middleware ships - # with certctl. Operators who need JWT/OIDC front certctl - # with an authenticating gateway (oauth2-proxy / Envoy / - # Traefik / Pomerium) and set CERTCTL_AUTH_TYPE=none - # upstream. See docs/architecture.md "Authenticating- - # gateway pattern". - enum: [api-key, none] + # with certctl. Operators who need JWT continue to front + # certctl with an authenticating gateway (oauth2-proxy / + # Envoy / Traefik / Pomerium) and set + # CERTCTL_AUTH_TYPE=none upstream. See + # docs/architecture.md "Authenticating-gateway pattern". + # + # Auth Bundle 2 Phase 0: "oidc" added to the enum. The + # session middleware + OIDC handler chain ship in later + # Bundle 2 phases; until they land, setting + # CERTCTL_AUTH_TYPE=oidc fails the runtime guard in + # cmd/server/main.go with an actionable error rather + # than silently falling back to api-key (the G-1 + # failure mode). The literal is in the enum so the GUI + # Login page (Phase 8) can render OIDC provider + # buttons against an /auth/info response that reflects + # the configured auth_type. + enum: [api-key, none, oidc] required: type: boolean diff --git a/cmd/server/main.go b/cmd/server/main.go index f5e014e..7cde4c4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -64,9 +64,22 @@ func main() { // unsupported auth shape. The error path uses fmt.Fprintf because // the slog logger is constructed from cfg below this point; we want // the failure to be visible regardless of log-level configuration. + // + // Auth Bundle 2 Phase 0: AuthTypeOIDC is in ValidAuthTypes() but the + // session middleware + OIDC handler chain ship in later phases. An + // operator who sets CERTCTL_AUTH_TYPE=oidc on a Bundle-2-incomplete + // deployment must NOT silently fall back to api-key (the silent + // auth-downgrade failure mode that drove G-1 in the first place). + // The OIDC case below refuses-to-start with an actionable message. + // Phase 6 of Bundle 2 (session middleware wiring) relaxes this case + // to fall through alongside the api-key + none cases. switch config.AuthType(cfg.Auth.Type) { case config.AuthTypeAPIKey, config.AuthTypeNone: // ok — fall through + case config.AuthTypeOIDC: + fmt.Fprintf(os.Stderr, + "CERTCTL_AUTH_TYPE=oidc: the OIDC auth chain is not yet wired in this build (Auth Bundle 2 Phase 6 ships the session middleware that consumes this auth-type literal). Set CERTCTL_AUTH_TYPE=api-key or run an authenticating gateway with CERTCTL_AUTH_TYPE=none until Bundle 2 lands. See cowork/auth-bundle-2-prompt.md.\n") + os.Exit(1) default: fmt.Fprintf(os.Stderr, "unsupported auth type at runtime: %q (valid: %v) — config validation should have caught this; refusing to start\n", diff --git a/deploy/helm/certctl/templates/_helpers.tpl b/deploy/helm/certctl/templates/_helpers.tpl index 0a9835a..9f5b78a 100644 --- a/deploy/helm/certctl/templates/_helpers.tpl +++ b/deploy/helm/certctl/templates/_helpers.tpl @@ -202,8 +202,8 @@ Any template that consumes .Values.server.auth.type should call runs once per affected resource. No-op when configured correctly. */}} {{- define "certctl.validateAuthType" -}} -{{- $valid := list "api-key" "none" -}} +{{- $valid := list "api-key" "none" "oidc" -}} {{- if not (has .Values.server.auth.type $valid) -}} -{{- fail (printf "\n\nserver.auth.type=%q is not supported (valid: %v).\n\nFor JWT/OIDC, run an authenticating gateway in front of certctl\n(oauth2-proxy / Envoy ext_authz / Traefik ForwardAuth / Pomerium) and\nset server.auth.type=none here so the gateway terminates federated\nidentity. See docs/architecture.md \"Authenticating-gateway pattern\"\nand docs/upgrade-to-v2-jwt-removal.md for the migration walkthrough.\n\nG-1 audit closure: pre-G-1 the chart accepted type=jwt and the binary\nsilently downgraded to api-key middleware. The chart now fails at\ntemplate time so misconfigured deployments cannot ship.\n" .Values.server.auth.type $valid) -}} +{{- fail (printf "\n\nserver.auth.type=%q is not supported (valid: %v).\n\nFor JWT/SAML/LDAP, run an authenticating gateway in front of certctl\n(oauth2-proxy / Envoy ext_authz / Traefik ForwardAuth / Pomerium) and\nset server.auth.type=none here so the gateway terminates federated\nidentity. See docs/architecture.md \"Authenticating-gateway pattern\"\nand docs/upgrade-to-v2-jwt-removal.md for the migration walkthrough.\n\nG-1 audit closure: pre-G-1 the chart accepted type=jwt and the binary\nsilently downgraded to api-key middleware. The chart now fails at\ntemplate time so misconfigured deployments cannot ship.\n\nAuth Bundle 2 Phase 0: server.auth.type=oidc is in the valid set but\nthe OIDC handler chain ships in later Bundle 2 phases. Pre-Bundle-2\noperators who set type=oidc see the certctl-server container exit at\nstartup with an actionable error — chart-time validation no longer\nblocks deploy because the binary's runtime guard takes over. Once\nBundle 2 lands, the runtime guard relaxes and OIDC works end-to-end.\n" .Values.server.auth.type $valid) -}} {{- end -}} {{- end }} diff --git a/go.mod b/go.mod index 35f4e53..a230bba 100644 --- a/go.mod +++ b/go.mod @@ -18,11 +18,13 @@ require ( github.com/aws/aws-sdk-go-v2/service/acm v1.38.3 github.com/aws/aws-sdk-go-v2/service/acmpca v1.46.14 github.com/aws/smithy-go v1.25.1 + github.com/coreos/go-oidc/v3 v3.18.0 github.com/go-jose/go-jose/v4 v4.1.4 github.com/leanovate/gopter v0.2.11 github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321 github.com/pkg/sftp v1.13.10 golang.org/x/crypto v0.50.0 + golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.20.0 software.sslmate.com/src/go-pkcs12 v0.7.0 ) @@ -112,7 +114,6 @@ require ( go.opentelemetry.io/otel/metric v1.41.0 // indirect go.opentelemetry.io/otel/trace v1.41.0 // indirect golang.org/x/net v0.53.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 2187407..c38ae7f 100644 --- a/go.sum +++ b/go.sum @@ -129,6 +129,8 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= +github.com/coreos/go-oidc/v3 v3.18.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= @@ -576,8 +578,8 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/internal/auth/oidc/doc.go b/internal/auth/oidc/doc.go new file mode 100644 index 0000000..6a0565f --- /dev/null +++ b/internal/auth/oidc/doc.go @@ -0,0 +1,47 @@ +// Package oidc is the Bundle 2 OpenID Connect integration: server-side +// validation of ID tokens issued by an enterprise IdP (Okta / Azure AD / +// Google Workspace / Keycloak / Authentik / Auth0), JWKS rotation, +// configurable group-claim parsing, and the HTTP handlers under +// /auth/oidc/* that wire to the session middleware. +// +// Package layout (post-Bundle-2): +// +// - internal/auth/oidc/ - this package (Phase 3 ships service.go). +// - internal/auth/oidc/domain/ - Phase 1 ships OIDCProvider + GroupRoleMapping. +// - internal/auth/oidc/groupclaim/ - Phase 3 ships the hand-rolled group-claim resolver +// (no JSON-path library; ~40 LOC walking dot-paths through map[string]interface{}). +// - internal/auth/oidc/testfixtures/ - Phase 10 ships the `//go:build integration` +// Keycloak harness backing the multi-IdP test surface. +// +// Phase 0 (this commit) reserves the package directory and pins +// coreos/go-oidc/v3 + golang.org/x/oauth2 as direct go.mod requires +// via the blank imports below. Without these blanks, `go mod tidy` +// would demote both back to // indirect because no Go file under this +// tree imports them yet (the actual imports land in Phase 3's +// service.go). The blank imports are deliberate Phase-0 transitional +// scaffolding; Phase 3 replaces them with real symbol use and these +// blanks are removed. +// +// Audit context (do not lose): +// - Apache-2.0 license, OSV.dev shows zero advisories ever on +// coreos/go-oidc/v3 at audit time. Used by Hashicorp Vault, Dex, +// Hydra, Authentik, every Kubernetes OIDC integration. The +// ecosystem-standard Go OIDC client. +// - golang.org/x/oauth2 maintained by the Go team itself; v0.36.0 (the +// pinned version) is OSV-clean. Two historical CVEs both fixed in +// earlier versions. +// - No JSON-path library is added. Phase 3's group-claim resolver is +// hand-rolled; the dependency audit explicitly forbids +// PaesslerAG/jsonpath, ohler55/ojg, tidwall/gjson, or any sibling +// transitive bloat for what is a 40-line problem. +package oidc + +import ( + // Phase 0: lift coreos/go-oidc/v3 + golang.org/x/oauth2 to direct + // go.mod requires so a future `go mod tidy` keeps them out of the + // // indirect block. Phase 3 replaces these blank imports with real + // symbol use (oidc.Provider, oauth2.Config, etc.) at which point + // these lines are removed. + _ "github.com/coreos/go-oidc/v3/oidc" + _ "golang.org/x/oauth2" +) diff --git a/internal/config/config.go b/internal/config/config.go index 422009d..4cccb16 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1507,6 +1507,22 @@ const ( // and set this value on the upstream certctl process. See // docs/architecture.md "Authenticating-gateway pattern". AuthTypeNone AuthType = "none" + + // AuthTypeOIDC (Auth Bundle 2 Phase 0) reserves the literal that the + // OIDC handler chain (Bundle 2 Phase 5+6) consumes. Pre-Bundle-2 + // behavior: the literal is allowed by the validator but the handler + // chain is not yet wired, so the runtime guard in cmd/server/main.go + // surfaces a clear "oidc auth-type configured but Bundle 2 handlers + // not registered" error rather than silently falling back to api-key + // (the failure mode that drove G-1's jwt-literal removal). Once + // Bundle 2's session middleware + OIDC service ship, the runtime + // guard relaxes and CERTCTL_AUTH_TYPE=oidc routes through them. + // + // Note: this is the AUTH-TYPE literal value, NOT the JWT alg literal. + // ID tokens are JWTs internally but the auth-type config string is + // "oidc". The G-1 closure test (TestValidAuthTypesDoesNotContainJWT) + // stays passing because "jwt" is never added back to the slice. + AuthTypeOIDC AuthType = "oidc" ) // ValidAuthTypes returns the allowed CERTCTL_AUTH_TYPE values. The set is @@ -1515,8 +1531,14 @@ const ( // validator below, the runtime guard in cmd/server/main.go, the helm // chart template (`certctl.validateAuthType`), and the property test in // config_test.go that pins "jwt" out of the slice forever. +// +// Bundle 2 Phase 0 adds AuthTypeOIDC to the slice. The G-1 invariant +// remains: "jwt" stays out of the allowed set forever; OIDC ID tokens +// are JWTs internally but the auth-type literal is "oidc", so the +// silent-downgrade attack surface that "jwt" represented does not +// regress. func ValidAuthTypes() []AuthType { - return []AuthType{AuthTypeAPIKey, AuthTypeNone} + return []AuthType{AuthTypeAPIKey, AuthTypeNone, AuthTypeOIDC} } // AuthConfig contains authentication configuration. diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f5eb553..5213596 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -553,17 +553,23 @@ func TestValidAuthTypesDoesNotContainJWT(t *testing.T) { } } -// TestValidAuthTypesIsExactly_APIKey_None pins the current allowed set. -// If a future change adds a new auth type, this test must be updated -// alongside the validator and the helm-chart `validateAuthType` helper — -// keeping all three surfaces in sync. -func TestValidAuthTypesIsExactly_APIKey_None(t *testing.T) { +// TestValidAuthTypesIsExactly_APIKey_None_OIDC pins the current allowed +// set. If a future change adds a new auth type, this test must be +// updated alongside the validator and the helm-chart `validateAuthType` +// helper — keeping all three surfaces in sync. +// +// Bundle 2 Phase 0: extended from {api-key, none} to {api-key, none, +// oidc}. The G-1 closure test (TestValidAuthTypesDoesNotContainJWT) +// stays passing because "jwt" is never added back. ID tokens are JWTs +// internally but the auth-type literal is "oidc", so the silent +// auth-downgrade that drove G-1 cannot regress through this addition. +func TestValidAuthTypesIsExactly_APIKey_None_OIDC(t *testing.T) { t.Parallel() got := ValidAuthTypes() - if len(got) != 2 { - t.Fatalf("ValidAuthTypes() returned %d entries, want 2: %v", len(got), got) + if len(got) != 3 { + t.Fatalf("ValidAuthTypes() returned %d entries, want 3: %v", len(got), got) } - want := map[AuthType]bool{AuthTypeAPIKey: true, AuthTypeNone: true} + want := map[AuthType]bool{AuthTypeAPIKey: true, AuthTypeNone: true, AuthTypeOIDC: true} for _, at := range got { if !want[at] { t.Errorf("unexpected auth type in ValidAuthTypes: %q", at) @@ -577,7 +583,7 @@ func TestValidAuthTypesIsExactly_APIKey_None(t *testing.T) { // rejection didn't accidentally swallow non-jwt typos. func TestValidate_GenericInvalidAuthType(t *testing.T) { t.Parallel() - for _, badType := range []string{"", "garbage", "oidc", "mtls", "API-KEY"} { + for _, badType := range []string{"", "garbage", "saml", "mtls", "API-KEY"} { t.Run("type="+badType, func(t *testing.T) { cfg := &Config{ Server: validServerConfig(t),