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.
This commit is contained in:
shankar0123
2026-05-10 03:31:51 +00:00
parent 18814854b4
commit 7d7bda93ba
9 changed files with 135 additions and 29 deletions
+12 -8
View File
@@ -30,14 +30,18 @@ CERTCTL_SERVER_PORT=8443
CERTCTL_LOG_LEVEL=info CERTCTL_LOG_LEVEL=info
CERTCTL_LOG_FORMAT=json CERTCTL_LOG_FORMAT=json
# Auth type: "api-key" (production) or "none" (demo/development). # Auth type: "api-key" (production), "none" (demo/development), or
# For JWT/OIDC, run an authenticating gateway in front of certctl # "oidc" (Auth Bundle 2 - native OIDC SSO via coreos/go-oidc/v3, ships
# (oauth2-proxy / Envoy ext_authz / Traefik ForwardAuth / Pomerium) and # in Bundle 2 phases 5+6; setting CERTCTL_AUTH_TYPE=oidc on a build
# set CERTCTL_AUTH_TYPE=none on the upstream — see # without Bundle 2 wired triggers a clear refuse-to-start error rather
# docs/architecture.md "Authenticating-gateway pattern". G-1 removed # than a silent fallback to api-key). For JWT / SAML / LDAP, continue to
# the in-process "jwt" option (no JWT middleware shipped — silent auth # run an authenticating gateway in front of certctl (oauth2-proxy /
# downgrade); see docs/upgrade-to-v2-jwt-removal.md if you previously # Envoy ext_authz / Traefik ForwardAuth / Pomerium) and set
# set CERTCTL_AUTH_TYPE=jwt. # 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 CERTCTL_AUTH_TYPE=none
# Required when CERTCTL_AUTH_TYPE is "api-key". # Required when CERTCTL_AUTH_TYPE is "api-key".
# Generate with: openssl rand -base64 32 # Generate with: openssl rand -base64 32
+17 -6
View File
@@ -134,12 +134,23 @@ paths:
type: string type: string
# G-1 (P1): "jwt" removed from this enum after the silent # G-1 (P1): "jwt" removed from this enum after the silent
# auth downgrade was identified — no JWT middleware ships # auth downgrade was identified — no JWT middleware ships
# with certctl. Operators who need JWT/OIDC front certctl # with certctl. Operators who need JWT continue to front
# with an authenticating gateway (oauth2-proxy / Envoy / # certctl with an authenticating gateway (oauth2-proxy /
# Traefik / Pomerium) and set CERTCTL_AUTH_TYPE=none # Envoy / Traefik / Pomerium) and set
# upstream. See docs/architecture.md "Authenticating- # CERTCTL_AUTH_TYPE=none upstream. See
# gateway pattern". # docs/architecture.md "Authenticating-gateway pattern".
enum: [api-key, none] #
# 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: required:
type: boolean type: boolean
+13
View File
@@ -64,9 +64,22 @@ func main() {
// unsupported auth shape. The error path uses fmt.Fprintf because // unsupported auth shape. The error path uses fmt.Fprintf because
// the slog logger is constructed from cfg below this point; we want // the slog logger is constructed from cfg below this point; we want
// the failure to be visible regardless of log-level configuration. // 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) { switch config.AuthType(cfg.Auth.Type) {
case config.AuthTypeAPIKey, config.AuthTypeNone: case config.AuthTypeAPIKey, config.AuthTypeNone:
// ok — fall through // 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: default:
fmt.Fprintf(os.Stderr, fmt.Fprintf(os.Stderr,
"unsupported auth type at runtime: %q (valid: %v) — config validation should have caught this; refusing to start\n", "unsupported auth type at runtime: %q (valid: %v) — config validation should have caught this; refusing to start\n",
+2 -2
View File
@@ -202,8 +202,8 @@ Any template that consumes .Values.server.auth.type should call
runs once per affected resource. No-op when configured correctly. runs once per affected resource. No-op when configured correctly.
*/}} */}}
{{- define "certctl.validateAuthType" -}} {{- define "certctl.validateAuthType" -}}
{{- $valid := list "api-key" "none" -}} {{- $valid := list "api-key" "none" "oidc" -}}
{{- if not (has .Values.server.auth.type $valid) -}} {{- 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 -}}
{{- end }} {{- end }}
+2 -1
View File
@@ -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/acm v1.38.3
github.com/aws/aws-sdk-go-v2/service/acmpca v1.46.14 github.com/aws/aws-sdk-go-v2/service/acmpca v1.46.14
github.com/aws/smithy-go v1.25.1 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/go-jose/go-jose/v4 v4.1.4
github.com/leanovate/gopter v0.2.11 github.com/leanovate/gopter v0.2.11
github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321 github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321
github.com/pkg/sftp v1.13.10 github.com/pkg/sftp v1.13.10
golang.org/x/crypto v0.50.0 golang.org/x/crypto v0.50.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sync v0.20.0 golang.org/x/sync v0.20.0
software.sslmate.com/src/go-pkcs12 v0.7.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/metric v1.41.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect go.opentelemetry.io/otel/trace v1.41.0 // indirect
golang.org/x/net v0.53.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/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect golang.org/x/text v0.36.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
+4 -2
View File
@@ -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/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 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 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-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/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 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-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-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.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.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+47
View File
@@ -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"
)
+23 -1
View File
@@ -1507,6 +1507,22 @@ const (
// and set this value on the upstream certctl process. See // and set this value on the upstream certctl process. See
// docs/architecture.md "Authenticating-gateway pattern". // docs/architecture.md "Authenticating-gateway pattern".
AuthTypeNone AuthType = "none" 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 // 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 // validator below, the runtime guard in cmd/server/main.go, the helm
// chart template (`certctl.validateAuthType`), and the property test in // chart template (`certctl.validateAuthType`), and the property test in
// config_test.go that pins "jwt" out of the slice forever. // 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 { func ValidAuthTypes() []AuthType {
return []AuthType{AuthTypeAPIKey, AuthTypeNone} return []AuthType{AuthTypeAPIKey, AuthTypeNone, AuthTypeOIDC}
} }
// AuthConfig contains authentication configuration. // AuthConfig contains authentication configuration.
+15 -9
View File
@@ -553,17 +553,23 @@ func TestValidAuthTypesDoesNotContainJWT(t *testing.T) {
} }
} }
// TestValidAuthTypesIsExactly_APIKey_None pins the current allowed set. // TestValidAuthTypesIsExactly_APIKey_None_OIDC pins the current allowed
// If a future change adds a new auth type, this test must be updated // set. If a future change adds a new auth type, this test must be
// alongside the validator and the helm-chart `validateAuthType` helper — // updated alongside the validator and the helm-chart `validateAuthType`
// keeping all three surfaces in sync. // helper — keeping all three surfaces in sync.
func TestValidAuthTypesIsExactly_APIKey_None(t *testing.T) { //
// 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() t.Parallel()
got := ValidAuthTypes() got := ValidAuthTypes()
if len(got) != 2 { if len(got) != 3 {
t.Fatalf("ValidAuthTypes() returned %d entries, want 2: %v", len(got), got) 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 { for _, at := range got {
if !want[at] { if !want[at] {
t.Errorf("unexpected auth type in ValidAuthTypes: %q", 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. // rejection didn't accidentally swallow non-jwt typos.
func TestValidate_GenericInvalidAuthType(t *testing.T) { func TestValidate_GenericInvalidAuthType(t *testing.T) {
t.Parallel() 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) { t.Run("type="+badType, func(t *testing.T) {
cfg := &Config{ cfg := &Config{
Server: validServerConfig(t), Server: validServerConfig(t),