Skip to content

Routing: curated CORS block on the generated SecurityPolicy #138

Description

@viniciusdc

Summary

Split out of #137. That issue bundled two independent surfaces — a low-level
HTTPRouteFilter pass-through and a curated CORS block — that close on different
timelines. The filters pass-through stays in #137; this issue tracks the curated
CORS block on its own.

Problem

NebariAppSpec.Routing has no ergonomic way to configure gateway-level CORS for a
NebariApp. The operator already emits a SecurityPolicy per app, and Envoy Gateway's
SecurityPolicy.spec.cors is the natural place to express it, but the operator
exposes nothing for it today. Deployers who want CORS handling at the gateway must
fall back to raw routing.filters (the #137 escape hatch) or do it in the backend.

Proposed change

Add a curated CORS *CORSConfig field to RoutingConfig, mirroring Envoy Gateway's
CORS schema, emitted as spec.cors on the SecurityPolicy the operator already
manages:

type CORSConfig struct {
    // AllowOrigins lists origins permitted to make cross-origin requests. Use a
    // specific origin when AllowCredentials is true — webhook validation rejects
    // ["*"] + credentials per CORS spec.
    AllowOrigins []string `json:"allowOrigins,omitempty"`

    // AllowMethods lists HTTP methods permitted on cross-origin requests.
    AllowMethods []string `json:"allowMethods,omitempty"`

    // AllowHeaders lists request headers the client may use.
    AllowHeaders []string `json:"allowHeaders,omitempty"`

    // ExposeHeaders lists response headers the browser exposes to JS.
    ExposeHeaders []string `json:"exposeHeaders,omitempty"`

    // MaxAge controls preflight cache duration.
    MaxAge *metav1.Duration `json:"maxAge,omitempty"`

    // AllowCredentials enables credentialed CORS requests. When true,
    // AllowOrigins must not contain "*" (CORS-spec requirement, enforced by
    // webhook validation).
    AllowCredentials *bool `json:"allowCredentials,omitempty"`
}

Sample usage:

spec:
  routing:
    cors:
      allowOrigins:
        - https://app.example.com
      allowMethods: [GET, POST]
      allowHeaders: [Content-Type, Authorization]
      maxAge: 1h
      allowCredentials: true

How this relates to the filters pass-through (#137)

  • routing.cors configures gateway-emitted CORS headers (preflight responses,
    Access-Control-Allow-Origin injection). Useful when the backend does no CORS.
  • routing.filters (Routing: expose HTTPRouteFilter pass-through on generated HTTPRoutes #137) modifies any header on the response/request,
    including those the backend emits. Useful when the backend already does CORS but
    does it wrong.
  • Filters run after CORS injection in Envoy's chain, so a filter can override
    anything the curated block sets.

Scope / priority note

Deferred relative to the filters pass-through. A curated CORS schema is a permanent
contract for the operator to own, so it warrants its own design pass rather than
riding along with the escape hatch. The concrete backend bug that originally
motivated #137 (an upstream emitting an invalid Allow-Credentials: true +
Allow-Origin: * combo) has since been fixed at the source in
https://redirect.github.com/nebari-dev/nebi/pull/385, so neither half is urgent.

Definition of Done

  • RoutingConfig.CORS *CORSConfig field added with the fields above
  • Emitted as spec.cors on the SecurityPolicy the operator already manages
  • Webhook validation rejects allowOrigins: ["*"] + allowCredentials: true
  • Unit test renders the SecurityPolicy and asserts CORS fields propagate
  • Behaves correctly when enforceAtGateway: false (SecurityPolicy is dropped) —
    either CORS becomes a no-op with a status condition, or the operator keeps a
    CORS-only SecurityPolicy
  • CRD reference documents the field with a CORS example and calls out the
    gateway-injected vs. backend-emitted distinction

Related

Metadata

Metadata

Assignees

No one assigned

    Fields

    Priority

    None yet

    Start date

    None yet

    Target date

    None yet

    Size

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions