Skip to content

docs(auth): SecurityPolicy issuer stays in-cluster — investigation result for #112, with tests#144

Open
tylerpotts wants to merge 1 commit into
mainfrom
issue-112-issuer-semantics
Open

docs(auth): SecurityPolicy issuer stays in-cluster — investigation result for #112, with tests#144
tylerpotts wants to merge 1 commit into
mainfrom
issue-112-issuer-semantics

Conversation

@tylerpotts

Copy link
Copy Markdown
Contributor

Summary

Answers the investigation in #112: SecurityPolicy.spec.oidc.provider.issuer should NOT track KEYCLOAK_EXTERNAL_URL. The in-cluster issuer is correct and the iss-claim mismatch with Keycloak frontendUrl deployments is harmless by construction. This PR records that decision in code comments and docs, and adds tests locking in the invariant that makes it safe.

Investigation findings (verified against Envoy Gateway v1.6.3 and Envoy source)

  1. Envoy Gateway uses the issuer only at control-plane time, to fetch /.well-known/openid-configuration — and only when authorizationEndpoint or tokenEndpoint is not explicitly set (internal/gatewayapi/securitypolicy.go, buildOIDCProvider). When KEYCLOAK_EXTERNAL_URL is configured the operator sets both, so EG never contacts the issuer and the IR passed to the data plane does not carry it.
  2. The Envoy oauth2 filter never validates iss. Its config proto (api/envoy/extensions/filters/http/oauth2/v3/oauth.proto) has no issuer field at all; the filter's only JWT decode is an unverified exp read for cookie lifetimes (source/extensions/filters/http/oauth2/filter.cc). This holds through Envoy 1.39-dev.
  3. EG does not enforce the OIDC Discovery issuer-match rule (OpenID Connect Discovery 1.0 §4.3): it unmarshals only the endpoint fields from the discovery document and ignores issuer. So even the discovery fallback path works when Keycloak returns a public issuer on the internal endpoint.
  4. Consumers that DO strictly enforce issspec.jwt providers (Envoy jwt_authn), e.g. the one the LLM serving pack's operator generates — already receive the public issuer via the client Secret's issuer-url key, populated from GetExternalIssuerURL(). That key is empty when KEYCLOAK_EXTERNAL_URL is unset, so strict-iss consumers require it to be set.
  5. Flipping the issuer to the external URL would be a pure regression: no data-plane effect, but the discovery fallback would then be fetched from the envoy-gateway controller pod against the public URL — reintroducing the public TLS-chain trust and hairpin-routing problems that fix(auth/keycloak): use external Keycloak URL for browser-facing OIDC endpoints #111 deliberately avoided for the token endpoint.

Changes

  • GetIssuerURL / GetEndpointOverrides doc comments record the verified semantics and the both-endpoints-set invariant.
  • TestKeycloakProvider_GetIssuerURL: new case asserting the issuer stays in-cluster when ExternalURL is set.
  • TestBuildSecurityPolicySpec_KeycloakIssuerAndEndpointSplit: reconciler-level test through buildSecurityPolicySpec with the real KeycloakProvider — issuer and token endpoint on the in-cluster host, authorization/end-session on the external URL, plus the no-ExternalURL fallback. This is the reconciler-level endpoint-split test requested in Add reconciler test asserting SecurityPolicy.spec.oidc.provider has Token on in-cluster URL and Authorization on public URL #113.
  • docs/reconcilers/authentication.md: new Issuer Semantics (Keycloak) section.

Closes #112
Closes #113

Test plan

  • go test ./internal/controller/reconcilers/auth/... passes; new tests verified to execute (-v -run)
  • golangci-lint run — 0 issues
  • go test ./... — no regressions vs main (pre-existing TestControllers HTTPRoute scheme failure reproduced on main, unrelated)

…the invariant

Resolves the investigation in issue #112: SecurityPolicy.spec.oidc.provider.issuer
deliberately remains the in-cluster Keycloak realm URL even when Keycloak
(frontendUrl configured) emits a public iss claim.

Verified against Envoy Gateway v1.6.3 and Envoy oauth2 filter source:
- EG uses the issuer only for control-plane OIDC discovery, and skips
  discovery entirely when authorizationEndpoint and tokenEndpoint are both
  explicitly set (which the operator does whenever KEYCLOAK_EXTERNAL_URL is
  configured). The issuer never reaches the data plane.
- The Envoy oauth2 filter has no issuer config field and never inspects the
  token's iss claim; its only JWT decode is an unverified exp read.
- EG ignores the discovery document's issuer field, so the OIDC Discovery
  issuer-match rule is not enforced.
- Strict-iss consumers (e.g. downstream spec.jwt providers) read the public
  issuer from the client Secret's issuer-url key (GetExternalIssuerURL).

Changes:
- GetIssuerURL/GetEndpointOverrides doc comments record the verified
  semantics and the invariant that makes the in-cluster issuer safe.
- TestKeycloakProvider_GetIssuerURL: new case asserting the issuer stays
  in-cluster when ExternalURL is set.
- TestBuildSecurityPolicySpec_KeycloakIssuerAndEndpointSplit: reconciler-level
  test through buildSecurityPolicySpec with the real KeycloakProvider,
  asserting issuer + token endpoint in-cluster and authorization/end-session
  on the external URL (also satisfies issue #113).
- docs/reconcilers/authentication.md: new Issuer Semantics section.
@github-actions

Copy link
Copy Markdown

Docker Images Built

Images pushed to Quay.io for branch issue-112-issuer-semantics:

Image Tag Platforms
Operator quay.io/nebari/nebari-operator:issue-112-issuer-semantics linux/amd64 + linux/arm64

Test the operator:

kubectl apply -k https://github.com/nebari-dev/nebari-operator.git/config/default?ref=issue-112-issuer-semantics
kubectl set image deployment/nebari-operator-controller-manager manager=quay.io/nebari/nebari-operator:issue-112-issuer-semantics -n nebari-operator-system

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants