From d91a5f4f25feddd99eeb57d491810a5c718cff5a Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Thu, 6 Nov 2025 18:16:33 +0000 Subject: [PATCH 01/12] Add Implementable Proposal for AuthenticationFilter --- .../authentication-filter/reference-1.png | Bin 0 -> 165947 bytes docs/proposals/authentication-filter.md | 1073 ++++++++++++++++- 2 files changed, 1072 insertions(+), 1 deletion(-) create mode 100644 docs/images/authentication-filter/reference-1.png diff --git a/docs/images/authentication-filter/reference-1.png b/docs/images/authentication-filter/reference-1.png new file mode 100644 index 0000000000000000000000000000000000000000..09f9ed88eef2484ef12c54a0693d346832082766 GIT binary patch literal 165947 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�qaD0$AGyc9R@GzTp0G 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 literal 0 HcmV?d00001 diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index be1c679a19..6f1a1843b4 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -1,7 +1,7 @@ # Enhancement Proposal-4052: Authentiation Filter - Issue: https://github.com/nginx/nginx-gateway-fabric/issues/4052 -- Status: Provisional +- Status: Implementable ## Summary @@ -22,3 +22,1074 @@ This new filter should eventually expose all forms of authentication available t - Design for OIDC Auth - An Auth filter for TCP and UDP routes - Design for integration with [ExternalAuth in the Gateway API](https://gateway-api.sigs.k8s.io/geps/gep-1494/) + +## Introduction + +This document focus expliclty on Authentiaction (AuthN) and not Authorization (AuthZ). Authentiaction (AuthN) defines the verification of identiy. It asks the question, "Who are you?". This is different from Authorization (AuthZ), which preceeds Authentication. It asks the question, "What are you allowed to do". + +This document also focus on HTTP Basic Authentication and JWT Authentication. Other authentication methods such as OpenID Connect (OIDC) are mentioned, but are not part of the CRD design. These will be covered in future design and implementation tasks. + + +## Use Cases + +- As an Application Developer, I want to secure access to my APIs and Backend Applications. +- As an Application Developer, I want to enforce authenticaiton on specific routes and matches. + +### Understanding NGINX authentication methods + +| **Authentication Method** | **OSS** | **Plus** | **NGINX Module** | **Details** | +|-------------------------------|--------------|----------------|----------------------------------|--------------------------------------------------------------------| +| **HTTP Basic Authentication** | ✅ | ✅ | [ngx_http_auth_basic](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) | Requires a username and password sent in an HTTP header. | +| **JWT (JSON Web Token)** | ❌ | ✅ | [ngx_http_auth_jwt_module](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) | Tokens are used for stateless authentication between client and server. | +| **OpenID Connect** | ❌ | ✅ | [ngx_http_oidc_module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html)| Allows authentication through third-party providers like Google. | + +## API, Customer Driven Interfaces, and User Experience + +This portion of the proposal will cover API design and interaction experience for use of Basic Auth and JWT. +This portioan also contains: +1. The Golang API +2. Example spec for Basic Auth + - Example HTTPRoutes and NINGX configuration +3. Example spec for JWT Auth + - Example HTTPRoutes + - Examples for Local & Remote JWKS configration + - Example NINGX configuration for both Local & Remote JWKS + - Example of additioanl optional fields + +### Golang API + +Below is the Golang API for the `AuthenticationFilter` API: + +```go +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +kubebuilder:object:root=true +// +kubebuilder:storageversion +// +kubebuilder:subresource:status +// +kubebuilder:resource:categories=nginx-gateway-fabric,shortName=authfilter;authenticationfilter +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` + +// AuthenticationFilter configures request authentication (Basic or JWT) and is +// referenced by HTTPRoute filters via ExtensionRef. +type AuthenticationFilter struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of the AuthenticationFilter. + Spec AuthenticationFilterSpec `json:"spec"` + + // Status defines the state of the AuthenticationFilter, following the same + // pattern as SnippetsFilter: per-controller conditions with an Accepted condition. + // + // +optional + Status AuthenticationFilterStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// AuthenticationFilterList contains a list of AuthenticationFilter. +type AuthenticationFilterList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AuthenticationFilter `json:"items"` +} + +// AuthenticationFilterSpec defines the desired configuration. +// Exactly one of Basic or JWT must be set according to Type. +type AuthenticationFilterSpec struct { + // Type selects the authentication mechanism. + // + // +kubebuilder:validation:Enum=Basic;JWT + Type AuthType `json:"type"` + + // Basic configures HTTP Basic Authentication. + // + // +optional + Basic *BasicAuth `json:"basic,omitempty"` + + // JWT configures JSON Web Token authentication (NGINX Plus). + // + // +optional + JWT *JWTAuth `json:"jwt,omitempty"` +} + +// AuthType defines the authentication mechanism. +type AuthType string + +const ( + AuthTypeBasic AuthType = "Basic" + AuthTypeJWT AuthType = "JWT" +) + +// BasicAuth configures HTTP Basic Authentication. +type BasicAuth struct { + // Secret is the name of the Secret containing htpasswd data. + // The Secret must be in the same namespace as this filter. + Secret string `json:"secret"` + + // Key is the key within the Secret that contains the htpasswd data. + // Default: "htpasswd". + // + // +optional + Key *string `json:"key,omitempty"` + + // Realm used by NGINX auth_basic; helps with logging and WWW-Authenticate. + // + // +optional + Realm *string `json:"realm,omitempty"` + + // OnFailure customizes the 401 response for failed authentication. + // + // +optional + OnFailure *AuthFailureResponse `json:"onFailure,omitempty"` +} + + // JWTAuth configures JWT-based authentication (NGINX Plus). +type JWTAuth struct { + // Realm used by NGINX auth_jwt; sets realm in the auth challenge. + // + // +optional + Realm *string `json:"realm,omitempty"` + + // Mode selects how JWT keys are provided: local file or remote JWKS. + // Default: File. + // + // +optional + // +kubebuilder:validation:Enum=File;Remote + Mode JWTKeyMode `json:"mode,omitempty"` + + // File specifies local JWKS configuration (Secret or ConfigMap, mount path, file name). + // Required when Mode == File. Exactly one of ConfigMapRef or SecretRef must be set. + // + // +optional + File *JWTFileKeySource `json:"file,omitempty"` + + // Remote specifies remote JWKS configuration. + // Required when Mode == Remote. + // + // +optional + Remote *JWTRemoteKeySource `json:"remote,omitempty"` + + // Leeway is the acceptable clock skew for exp/nbf checks (auth_jwt_leeway). + // Example: "60s". + // + // +optional + Leeway *string `json:"leeway,omitempty"` + + // Type sets token type: signed | encrypted | nested (auth_jwt_type). + // Default: "signed". + // + // +optional + // +kubebuilder:validation:Enum=signed;encrypted;nested + Type *JWTTokenType `json:"type,omitempty"` + + // KeyCache is the cache duration for keys (auth_jwt_key_cache). + // Example: "10m". + // + // +optional + KeyCache *string `json:"keyCache,omitempty"` + + // OnFailure customizes the 401 response for failed authentication. + // + // +optional + OnFailure *AuthFailureResponse `json:"onFailure,omitempty"` + + // Require defines claims that must match exactly (e.g., iss, aud). + // NGF will translate these into NGINX maps and auth_jwt_require directives. + // + // +optional + Require *JWTRequiredClaims `json:"require,omitempty"` + + // TokenSource defines where the client presents the token. + // Defaults to Authorization header only. + // + // +optional + TokenSource *JWTTokenSource `json:"tokenSource,omitempty"` + + // Propagation controls identity header propagation to upstream and header stripping. + // + // +optional + Propagation *JWTPropagation `json:"propagation,omitempty"` +} + +// JWTKeyMode selects where JWT keys come from. +type JWTKeyMode string + +const ( + JWTKeyModeFile JWTKeyMode = "File" + JWTKeyModeRemote JWTKeyMode = "Remote" +) + +// JWTFileKeySource specifies local JWKS key configuration. +type JWTFileKeySource struct { + // ConfigMapRef references a ConfigMap containing the JWKS. + // Exactly one of ConfigMapRef or SecretRef must be set. + // + // +optional + ConfigMapRef *LocalObjectReference `json:"configMapRef,omitempty"` + + // SecretRef references a Secret containing the JWKS (with optional key). + // Exactly one of ConfigMapRef or SecretRef must be set. + // + // +optional + SecretRef *SecretKeyReference `json:"secretRef,omitempty"` + + // MountPath is the path where NGF will mount the data into the NGINX container. + // Example: "/etc/nginx/keys". + MountPath string `json:"mountPath"` + + // FileName is the file name of the JWKS within the mount path. + // Example: "jwks.json". + FileName string `json:"fileName"` + + // KeyCache is the cache duration for keys (auth_jwt_key_cache). + // Example: "10m". + // + // +optional + KeyCache *string `json:"keyCache,omitempty"` +} + + // JWTRemoteKeySource specifies remote JWKS configuration. +type JWTRemoteKeySource struct { + // URL is the JWKS endpoint, e.g. "https://issuer.example.com/.well-known/jwks.json". + URL string `json:"url"` + + // Cache configures NGINX proxy_cache for JWKS fetches made via auth_jwt_key_request. + // When set, NGF will render proxy_cache_path in http{} and attach proxy_cache to the internal JWKS location. + // + // +optional + Cache *JWKSCache `json:"cache,omitempty"` +} + + // JWKSCache controls NGINX proxy_cache_path and proxy_cache settings used for JWKS responses. +type JWKSCache struct { + // Path is the filesystem path for cached JWKS objects. + // Example: "/var/cache/nginx/jwks". + Path string `json:"path"` + + // Levels specifies the directory hierarchy for cached files. + // Example: "1:2". + // + // +optional + Levels *string `json:"levels,omitempty"` + + // KeysZoneName is the name of the cache keys zone. + // If omitted, the controller SHOULD derive a unique, stable name per filter instance. + // + // +optional + KeysZoneName *string `json:"keysZoneName,omitempty"` + + // KeysZoneSize is the size of the cache keys zone (e.g., "10m"). + // This is required to avoid unbounded allocations. + KeysZoneSize string `json:"keysZoneSize"` + + // MaxSize limits the total size of the cache (e.g., "50m"). + // + // +optional + MaxSize *string `json:"maxSize,omitempty"` + + // Inactive defines the inactivity timeout before cached items are evicted (e.g., "10m"). + // + // +optional + Inactive *string `json:"inactive,omitempty"` + + // UseTempPath controls whether a temporary file is used for cache writes. + // Maps to use_temp_path=(on|off). Default: false (off). + // + // +optional + UseTempPath *bool `json:"useTempPath,omitempty"` +} + +// JWTTokenType represents NGINX auth_jwt_type. +type JWTTokenType string + +const ( + JWTTokenTypeSigned JWTTokenType = "signed" + JWTTokenTypeEncrypted JWTTokenType = "encrypted" + JWTTokenTypeNested JWTTokenType = "nested" +) + +// JWTRequiredClaims specifies exact-match requirements for claims. +type JWTRequiredClaims struct { + // Issuer (iss) required exact value. + // + // +optional + Iss *string `json:"iss,omitempty"` + + // Audience (aud) required exact value. + // + // +optional + Aud *string `json:"aud,omitempty"` +} + +// JWTTokenSource specifies where tokens may be read from. +type JWTTokenSource struct { + // Read token from Authorization header. Default: true. + // + // +optional + Header *bool `json:"header,omitempty"` + + // Read token from a cookie. Default: false. + // + // +optional + Cookie *bool `json:"cookie,omitempty"` + + // CookieName when Cookie is true. Example: "access_token". + // + // +optional + CookieName *string `json:"cookieName,omitempty"` + + // Read token from query string. Default: false. + // + // +optional + Query *bool `json:"query,omitempty"` + + // QueryParam when Query is true. Example: "access_token". + // + // +optional + QueryParam *string `json:"queryParam,omitempty"` +} + +// JWTPropagation controls identity header propagation and header stripping. +type JWTPropagation struct { + // AddIdentityHeaders defines headers to add on success with values + // typically derived from jwt_claim_* variables. + // + // +optional + AddIdentityHeaders []HeaderValue `json:"addIdentityHeaders,omitempty"` + + // StripAuthorization removes the incoming Authorization header before proxying. + // + // +optional + StripAuthorization *bool `json:"stripAuthorization,omitempty"` +} + +// HeaderValue defines a header name and a value (may reference NGINX variables). +type HeaderValue struct { + Name string `json:"name"` + ValueFrom string `json:"valueFrom"` +} + +// AuthScheme enumerates supported WWW-Authenticate schemes. +type AuthScheme string + +const ( + AuthSchemeBasic AuthScheme = "Basic" + AuthSchemeBearer AuthScheme = "Bearer" +) + +// AuthFailureBodyPolicy controls the failure response body behavior. +type AuthFailureBodyPolicy string + +const ( + AuthFailureBodyPolicyUnauthorized AuthFailureBodyPolicy = "Unauthorized" + AuthFailureBodyPolicyForbidden AuthFailureBodyPolicy = "Forbidden" + AuthFailureBodyPolicyEmpty AuthFailureBodyPolicy = "Empty" +) + +// AuthFailureResponse customizes 401/403 failures. +type AuthFailureResponse struct { + // Allowed: 401, 403. Default: 401. + // + // +optional + StatusCode *int32 `json:"statusCode,omitempty"` + + // Challenge scheme. If omitted, inferred from filter Type (Basic|Bearer). + // + // +optional + // +kubebuilder:validation:Enum=Basic;Bearer + Scheme *AuthScheme `json:"scheme,omitempty"` + + // Controls whether a default canned body is sent or an empty body. + // Default: Default. + // + // +optional + // +kubebuilder:validation:Enum=Unauthorized;Forbidden;Empty + BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"` +} + +// LocalObjectReference references a namespaced object in the same namespace. +type LocalObjectReference struct { + Name string `json:"name"` +} + +// SecretKeyReference references a Secret and an optional key. +type SecretKeyReference struct { + Name string `json:"name"` + // Key within the Secret data. If omitted, controller defaults apply (e.g., "jwks.json"). + // + // +optional + Key *string `json:"key,omitempty"` +} + +// AuthenticationFilterStatus defines the state of AuthenticationFilter (similar to SnippetsFilter). +type AuthenticationFilterStatus struct { + // Controllers is a list of Gateway API controllers that processed the AuthenticationFilter + // and the status of the AuthenticationFilter with respect to each controller. + // + // +kubebuilder:validation:MaxItems=16 + Controllers []ControllerStatus `json:"controllers,omitempty"` +} + +// AuthenticationFilterConditionType is a type of condition associated with AuthenticationFilter. +type AuthenticationFilterConditionType string + +// AuthenticationFilterConditionReason is a reason for an AuthenticationFilter condition type. +type AuthenticationFilterConditionReason string + +const ( + // AuthenticationFilterConditionTypeAccepted indicates that the AuthenticationFilter is accepted. + // + // Possible reasons for this condition to be True: + // * Accepted + // + // Possible reasons for this condition to be False: + // * Invalid + AuthenticationFilterConditionTypeAccepted AuthenticationFilterConditionType = "Accepted" + + // AuthenticationFilterConditionReasonAccepted is used with the Accepted condition type when + // the condition is true. + AuthenticationFilterConditionReasonAccepted AuthenticationFilterConditionReason = "Accepted" + + // AuthenticationFilterConditionReasonInvalid is used with the Accepted condition type when + // the filter is invalid. + AuthenticationFilterConditionReasonInvalid AuthenticationFilterConditionReason = "Invalid" +) +``` + +### Example spec for Basic Auth + +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: basic-auth +spec: + type: Basic + basic: + secret: basic-auth-users # Secret containing htpasswd data + key: htpasswd # key within the Secret + realm: "Restricted" # Optional. Helps with logging + onFailure: # Optional. These setting may be defaults. + statusCode: 401 + scheme: Basic +``` + +In the case of Basic Auth, the deployed Secret and HTTPRoute may look like this: + +#### Secret referenced by filter + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: basic-auth-users +type: Opaque +stringData: + htpasswd: | + admin:$apr1$ZxY12345$abcdefghijklmnopqrstuvwx/ + user:$apr1$AbC98765$mnopqrstuvwxyzabcdefghiJKL/ +``` + +#### HTTPRoute that will reference this filter + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: api-basic +spec: + parentRefs: + - name: gateway + hostnames: + - api.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /v2 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.nginx.org + kind: AuthenticationFilter + name: basic-auth + backendRefs: + - name: backend + port: 80 +``` + +#### Generated NGINX config + +```nginx +http { + upstream backend_default { + server 10.0.0.10:80; + server 10.0.0.11:80; + } + + server { + listen 80; + server_name api.example.com; + + location /v2 { + # Injected by BasicAuthFilter "basic-auth" + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/secrets/basic-auth-users/htpasswd; + + # Optional: customize failure per filter onFailure + # Ensures a consistent body and explicit WWW-Authenticate header + error_page 401 = @basic_auth_failure; + + # Optional: do not forward client Authorization header to upstream + proxy_set_header Authorization ""; + + # NGF standard proxy headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Pass traffic to upstream + proxy_pass http://backend_default; + } + + # Internal location for custom 401 response + location @basic_auth_failure { + add_header WWW-Authenticate 'Basic realm="Restricted"' always; + add_header Content-Type "text/plain; charset=utf-8" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Cache-Control "no-store" always; + add_header Pragma "no-cache" always; + return 401 'Unauthorized'; + } + } +} +``` + +### Example spec for JWT Auth + +For JWT Auth, there is two options. +1. Local JWKS file stored as as a Secret or as a ConfigMap +2. Remote JWKS from an IdP provider like Keycloak + +#### Example JWT AuthenticationFilter with Local JWKS + +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: jwt-auth +spec: + type: JWT + jwt: + realm: "Restricted" + # Key verification mode: Local file or Remote JWKs + mode: File # Defaults to File. + file: + # In File mode, exactly one of configMapRef or secretRef must be defined. + configMapRef: + name: jwt-keys + secretRef: + name: jwt-keys-secure + key: jwks.json + mountPath: /etc/nginx/keys + fileName: jwks.json + keyCache: 10m # Optional cache time for keys (auth_jwt_key_cache) + # Acceptable clock skew for exp/nbf + leeway: 60s # Configures auth_jwt_leeway + # Sets auth_jwt_type + type: signed # signed | encrypted | nested + onFailure: + statusCode: 403 # Set to 403 for example purposes. Defaults to 401. + scheme: Bearer +``` + +#### Example JWT AuthenticationFilter with Remote JWKs + +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: jwt-auth +spec: + type: JWT + jwt: + realm: "Restricted" + # Key verification mode: Local file or Remote JWKs + mode: Remote # Defaults to File. + remote: + url: https://issuer.example.com/.well-known/jwks.json + # Acceptable clock skew for exp/nbf + leeway: 60s # Configures auth_jwt_leeway + # Sets auth_jwt_type + type: signed # signed | encrypted | nested + # Optional cache duration for keys (auth_jwt_key_cache) + keyCache: 10m + onFailure: + statusCode: 403 # Set to 403 for example purposes. Defaults to 401. + scheme: Bearer +``` + +#### ConfigMap referenced by filter (if using configMapRef) + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jwt-keys +data: + jwks.json: ewogICJrZXlzIjogWwogICAgewogICAgICAia3R5IjogIlJTQSIsCiAgICAgICJ1c2UiOiAic2lnIiwKICAgICAgImtpZCI6ICJleGFtcGxlLWtleS1pZCIsCiAgICAgICJhbGciOiAiUlMyNTYiLAogICAgICAibiI6ICJiYXNlNjR1cmwtbW9kdWx1cyIsCiAgICAgICJlIjogIkFRQUIiCiAgICB9CiAgXQp9Cg== +``` + +#### Secret referenced by filter (if using secretRef) + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: jwt-keys-secure +type: Opaque +data: + jwks.json: ewogICJrZXlzIjogWwogICAgewogICAgICAia3R5IjogIlJTQSIsCiAgICAgICJ1c2UiOiAic2lnIiwKICAgICAgImtpZCI6ICJleGFtcGxlLWtleS1pZCIsCiAgICAgICJhbGciOiAiUlMyNTYiLAogICAgICAibiI6ICJiYXNlNjR1cmwtbW9kdWx1cyIsCiAgICAgICJlIjogIkFRQUIiCiAgICB9CiAgXQp9Cg== +``` + +Note: Secret data values must be base64-encoded and are decoded by the kubelet on mount, producing a valid jwks.json file. ConfigMap data values are plain text and should contain the raw JSON (not base64). + +#### HTTPRoute that will reference this filter + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: api-jwt +spec: + parentRefs: + - name: gateway + hostnames: + - api.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /v2 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.nginx.org + kind: AuthenticationFilter + name: jwt-auth + backendRefs: + - name: backend + port: 80 +``` + +#### Generated NGINX Config + +Below are `two` potential NGINX configurations based on the mode used. + +1. NGINX Config when using `Mode: Key` (i.e. locally referenced JWKS key) + +```nginx +http { + upstream backend_default { + server 10.0.0.10:80; + server 10.0.0.11:80; + } + + # Exact claim matching via maps for iss/aud + map $jwt_claim_iss $valid_jwt_iss { + "https://issuer.example.com" 1; + default 0; + } + map $jwt_claim_aud $valid_jwt_aud { + "api" 1; + default 0; + } + + server { + listen 80; + server_name api.example.com; + + location /v2 { + auth_jwt "Restricted"; + + # File-based JWKS + auth_jwt_key_file /etc/nginx/keys/jwks.json; + + # Optional: key cache duration + auth_jwt_key_cache 10m; + + # Leeway for exp/nbf + auth_jwt_leeway 60s; + + # Token type + auth_jwt_type signed; + + # Required claims (enforced via maps above) + auth_jwt_require $valid_jwt_iss; + auth_jwt_require $valid_jwt_aud; + + # Identity headers to pass back on success + add_header X-User-Id $jwt_claim_sub always; + add_header X-User-Email $jwt_claim_email always; + add_header X-Auth-Mechanism "jwt" always; + + # Optional: customize failure per filter onFailure + error_page 401 = @jwt_auth_failure; + + # Optional: do not forward client Authorization header to upstream + proxy_set_header Authorization ""; + + # NGF standard proxy headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Pass traffic to upstream + proxy_pass http://backend_default; + } + + # Internal location for custom 401 response + location @jwt_auth_failure { + add_header WWW-Authenticate 'Bearer realm="Restricted", error="insufficient_scope"' always; + add_header Content-Type "text/plain; charset=utf-8" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Cache-Control "no-store" always; + add_header Pragma "no-cache" always; + return 403 'Forbidden'; + } + } +} +``` + +2. NGINX Config when using `Mode: Remote` + +These are some directives the `Remote` mode uses over the `File` mode: + - `auth_jwt_key_request`: When using the `Remote` mode, this is used in place of `auth_jwt_key_file`. This will call the `internal` NGINX location `/jwks_uri` to redirect the request to the external auth provider (e.g. KeyCloak) + - `proxy_cache_path`: This is used to configuring caching of the JWKS after an initial request allowing subseuqnt requests to not request re-authenticaiton for a time + +```nginx +http { + # Serve JWKS from cache after the first fetch + proxy_cache_path /var/cache/nginx/jwks levels=1:2 keys_zone=jwks_jwt_auth:10m max_size=50m inactive=10m use_temp_path=off; + + upstream backend_default { + server 10.0.0.10:80; + server 10.0.0.11:80; + } + + # Exact claim matching via maps for iss/aud + map $jwt_claim_iss $valid_jwt_iss { + "https://issuer.example.com" 1; + "https://issuer.example1.com" 1; + default 0; + } + map $jwt_claim_aud $valid_jwt_aud { + "api" 1; + "cli" 1; + default 0; + } + + server { + listen 80; + server_name api.example.com; + + location /v2 { + auth_jwt "Restricted"; + # Remote JWKS + auth_jwt_key_request /jwks_uri; + + # Optional: key cache duration + auth_jwt_key_cache 10m; + + # Leeway for exp/nbf + auth_jwt_leeway 60s; + + # Token type + auth_jwt_type signed; + + # Required claims (enforced via maps above) + auth_jwt_require $valid_jwt_iss; + auth_jwt_require $valid_jwt_aud; + + # Identity headers to pass back on success + add_header X-User-Id $jwt_claim_sub always; + add_header X-User-Email $jwt_claim_email always; + add_header X-Auth-Mechanism "jwt" always; + + # Optional: customize failure per filter onFailure + error_page 401 = @jwt_auth_failure; + + # Optional: do not forward client Authorization header to upstream + proxy_set_header Authorization ""; + + # NGF standard proxy headers + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Pass traffic to upstream + proxy_pass http://backend_default; + } + + # Internal endpoint to fetch JWKS from IdP + location = /jwks_uri { + internal; + # Enable caching of JWKS + proxy_cache jwks_jwt_auth; + proxy_pass https://issuer.example.com/.well-known/jwks.json; + } + + # Internal location for custom 401 response + location @jwt_auth_failure { + add_header WWW-Authenticate 'Bearer realm="Restricted", error="invalid_token"' always; + add_header Content-Type "text/plain; charset=utf-8" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Cache-Control "no-store" always; + add_header Pragma "no-cache" always; + return 401 'Unauthorized'; + } + } +} +``` + +#### Additional Optional Fields + +`require`, `tokenSource` and `propagation` are some additioanl fields we may choose to include. + +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: jwt-auth +spec: + type: JWT + jwt: + realm: "Restricted" + keys: + mode: Remote + remote: + url: https://issuer.example.com/.well-known/jwks.json + + # Required claims (exact matching done via maps in NGINX; see config) + require: + iss: + - "https://issuer.example.com" + - "https://issuer2.example.com" + aud: + - "api" + - "cli" + + # Where client presents the token (defaults to Authorization header) + tokenSource: + header: true + cookie: false + cookieName: access_token + query: false + queryParam: access_token + + # Identity propagation to backend and header stripping + propagation: + addIdentityHeaders: + - name: X-User-Id + valueFrom: "$jwt_claim_sub" + - name: X-User-Email + valueFrom: "$jwt_claim_email" + stripAuthorization: true # Optionally remove client Authorization header +``` + +### Caching configuration + +Users may also choose to change the caching configuration set by `proxy_cache_path`. +This can be made available in the `cache` configuration under `jwt.remote.cache` + +```yaml +kind: AuthenticationFilter +metadata: + name: jwt-auth +spec: + type: JWT + jwt: + realm: "Restricted" + mode: Remote + remote: + url: https://issuer.example.com/.well-known/jwks.json + cache: + path: /var/cache/nginx/jwks # required when cache is set + levels: "1:2" # optional; defaults to "1:2" + keysZoneName: jwks_jwtauth # optional; controller can default to a derived name + keysZoneSize: 10m # required; size for keys_zone + maxSize: 50m # optional; limit total cache size + inactive: 10m # optional; inactivity TTL before eviction + useTempPath: false # optional; sets use_temp_path +``` + +### Attachment + +Filters must be attached to a HTTPRoute at the `rules.matces` level. +This means that a single `AuthenticationFilter` may be attached mutliple times to a single HTTPRoute. + +#### Basic example + +This example shows a single HTTPRoute, with a single `filter` defined in a `rule` + +![reference-1](/docs/images/authentication-filter/reference-1.png) + +### Status + +#### Referencing multiple AuthenticationFilter resources in a single rule + +Only a single `AuthenticationFilter` may be referened in a single rule. + +The `Status` the HTTPRoute/GRPCRoute in this scenario should be set to `Invalid`, and the resource should be `Rejected` + +This behavour falls in line with the expected behaviour of filters in the Gateway API, which generally allows only one type of a specific filter (authentication, rewriting, etc.) within a rule. + +Below is an eample of an **invalid** HTTPRoute that references multiple `AuthenticationFilter` resources in a single rule + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: invalid-httproute +spec: + parentRefs: + - name: gateway + hostnames: + - api.example.com + rules: + - matches: + - path: + type: PathPrefix + value: /api + filters: + - type: ExtensionRef + extensionRef: + group: gateway.nginx.org + kind: AuthenticationFilter + name: basic-auth + - type: ExtensionRef + extensionRef: + group: gateway.nginx.org + kind: AuthenticationFilter + name: jwt-auth + backendRefs: + - name: backend + port: 80 +``` + +## Testing + +- Unit tests +- Functional tests to validate behavioural scenarios when referncing filters in different combinations. The details of these tests are out of scope for this document. + +## Security Considerations + +### Basic Auth and Local JWKS + +Basic Auth sends credentials in an Authorization header that is base64-encoded. +JWT Auth requires users to provided a bearer token through the Authroization header. + +Both of these methods can be easily intercepted over HTTP. + +Users that attach an `AuthenticaitonFilter` to a HTTPRoute/GRPCRoute should be advised to enable HTTPS traffic at the Gateway level for the routes. + +Any exmaple configurations and deployments for the `AuthenticationFilter` should enable HTTPS at the Gateway level by default. + +The `mountPath` for local JWKS should be mounted to a fixed location (e.g., /etc/nginx/keys). +The `fileName` for a local JWKS should be sanatized to a pattern of [A-Za-z0-9._-]. + +### Remote JWKS + +Proxy cache TTL should be configurable and set to a resonable default, reducing periods of stale cached JWKs. + +### Key rotation + +Users sholud be advised to regularly rotate their JWKS keys in cases where they chose to reference a local JWKS via a `secrefRef` or `configMapRef` + +### Auth failure behaviour + +3xx response codes should not be allowed and AuthenticationFilter.onFailure must not support redirect targets. This is to prevent to prevent open-redirect abuse. + +401 and 403 should be the only allowable auth failure codes. + +### Auth failure default headers + +Below are a list of default defensive headers for authentication failure reponses. +We may choose to include these headers by default for improved robustness in auth falure responses. + +``` +add_header Content-Type "text/plain; charset=utf-8" always; +add_header X-Content-Type-Options "nosniff" always; +add_header Cache-Control "no-store" always; +add_header Pragma "no-cache" always; +``` + +Detailed header breakdown: + +- Content-Type: "text/plain; charset=utf-8" + - This header explicitly set the body as plan text. This prevents browsers from treating the response as HTML or JavaScript, and is effective at mitigating Cross-side scrpting (XSS) through error pages + +- X-Content-Type-Options: "nosniff" + - This header prevents content type confusion. This occurrs when browsers guesses HTML & JavaScript, and executes it despite a benign type. + +- Cache-Control: "no-store" + - This header informs browsers and proxies not to cache the response. Avoids sensitive, auth-related content, from being being stored and served later to unintended recipients. + +- Pragma: "no-cache" + - This header is commonly paired with `Cache-Control: "no-store"` for broad coverage. It acts as an additional signal for older intermediaries that do not honor Cache-Control. + + +### Validation + +When referencing an `AuthenticationFilter` in either a HTTPRoute or GRPCRoute, it is important that we ensure all configurable fields are validated, and that the resulting NGINX configuration is correct and secure + +All fields in the `AuthenticationFilter` will be validated with Open API Schema. +We should also include [CEL](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules) validation where required. + +We should validated that only one `AuthenticationFilter` is referenced per-rule. Multiple references to an `AuthenticationFilter` in a single rule should result in an `Invalid` HTTPRoute/GRPCRoute, and the resource should be `Rejected`. + +an `AuthenticationFilter` that sets a `onFailure.statusCode` to anything other than `401` or `403` should be rejected. This relates to the "Auth failure behaviour" section in the Security Condierations section. + +## Alternatives + +The Gateway API defines a means to standardise authentication through use of the [HTTPExternalAuthFilter](https://gateway-api.sigs.k8s.io/reference/spec/#httpexternalauthfilter) available in the HTTPRoute specification. + +This allows users to reference an external authentication services, such as Keycloak, to handle the authentication requests. +While this API is available in the experimental channel, it is subject to change. + +Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quckly provide authenticaiton to our users while allowing us to closley monitor progress of the ExternalAuthFilter. + +It is certainly possible for us to provide an External Authentication Services that leverages NGINX and is something we can further investigate as the API progresses. + +## Additional considerations + +### Documenting filter behavour + +In regards to documentation of filter behavour with the `AuthenticationFilter`, the Gateway API documentation on filters states the following: + +``` +Wherever possible, implementations SHOULD implement filters in the order they are specified. + +Implementations MAY choose to implement this ordering strictly, rejecting +any combination or order of filters that cannot be supported. +If implementations choose a strict interpretation of filter ordering, they MUST clearly +document that behavior. +``` + +## References + + - [Gateway API ExternalAuthFilter GEP]((https://gateway-api.sigs.k8s.io/geps/gep-1494/)) + - [HTTPExternalAuthFilter Specification](https://gateway-api.sigs.k8s.io/reference/spec/#httpexternalauthfilter) + - [Kubernetes documentation on CEL validaton](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules) + - [NGINX HTTP Basic Auth Module](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) + - [NGINX JWT Auth Module](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) + - [NGINX OIDC Module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) From 9047c3ebb39d0c78f4af619f55781aea7e787d25 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Thu, 6 Nov 2025 18:19:28 +0000 Subject: [PATCH 02/12] Update auth header code block --- docs/proposals/authentication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 6f1a1843b4..fa2e2ea112 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -1026,7 +1026,7 @@ Users sholud be advised to regularly rotate their JWKS keys in cases where they Below are a list of default defensive headers for authentication failure reponses. We may choose to include these headers by default for improved robustness in auth falure responses. -``` +```nginx add_header Content-Type "text/plain; charset=utf-8" always; add_header X-Content-Type-Options "nosniff" always; add_header Cache-Control "no-store" always; From c937366f1475a35a3013f06de8a576bda690479d Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Thu, 6 Nov 2025 18:34:01 +0000 Subject: [PATCH 03/12] Fix pre-commit and lint errors --- docs/proposals/authentication-filter.md | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index fa2e2ea112..4deaaa2081 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -47,11 +47,12 @@ This document also focus on HTTP Basic Authentication and JWT Authentication. Ot This portion of the proposal will cover API design and interaction experience for use of Basic Auth and JWT. This portioan also contains: + 1. The Golang API 2. Example spec for Basic Auth - Example HTTPRoutes and NINGX configuration 3. Example spec for JWT Auth - - Example HTTPRoutes + - Example HTTPRoutes - Examples for Local & Remote JWKS configration - Example NINGX configuration for both Local & Remote JWKS - Example of additioanl optional fields @@ -575,6 +576,7 @@ http { ### Example spec for JWT Auth For JWT Auth, there is two options. + 1. Local JWKS file stored as as a Secret or as a ConfigMap 2. Remote JWKS from an IdP provider like Keycloak @@ -593,8 +595,8 @@ spec: mode: File # Defaults to File. file: # In File mode, exactly one of configMapRef or secretRef must be defined. - configMapRef: - name: jwt-keys + configMapRef: + name: jwt-keys secretRef: name: jwt-keys-secure key: jwks.json @@ -769,11 +771,12 @@ http { } ``` -2. NGINX Config when using `Mode: Remote` +1. NGINX Config when using `Mode: Remote` These are some directives the `Remote` mode uses over the `File` mode: - - `auth_jwt_key_request`: When using the `Remote` mode, this is used in place of `auth_jwt_key_file`. This will call the `internal` NGINX location `/jwks_uri` to redirect the request to the external auth provider (e.g. KeyCloak) - - `proxy_cache_path`: This is used to configuring caching of the JWKS after an initial request allowing subseuqnt requests to not request re-authenticaiton for a time + +- `auth_jwt_key_request`: When using the `Remote` mode, this is used in place of `auth_jwt_key_file`. This will call the `internal` NGINX location `/jwks_uri` to redirect the request to the external auth provider (e.g. KeyCloak) +- `proxy_cache_path`: This is used to configuring caching of the JWKS after an initial request allowing subseuqnt requests to not request re-authenticaiton for a time ```nginx http { @@ -878,7 +881,7 @@ spec: mode: Remote remote: url: https://issuer.example.com/.well-known/jwks.json - + # Required claims (exact matching done via maps in NGINX; see config) require: iss: @@ -887,7 +890,7 @@ spec: aud: - "api" - "cli" - + # Where client presents the token (defaults to Authorization header) tokenSource: header: true @@ -895,7 +898,7 @@ spec: cookieName: access_token query: false queryParam: access_token - + # Identity propagation to backend and header stripping propagation: addIdentityHeaders: @@ -1076,20 +1079,20 @@ It is certainly possible for us to provide an External Authentication Services t In regards to documentation of filter behavour with the `AuthenticationFilter`, the Gateway API documentation on filters states the following: -``` +```text Wherever possible, implementations SHOULD implement filters in the order they are specified. Implementations MAY choose to implement this ordering strictly, rejecting -any combination or order of filters that cannot be supported. +any combination or order of filters that cannot be supported. If implementations choose a strict interpretation of filter ordering, they MUST clearly document that behavior. ``` ## References - - [Gateway API ExternalAuthFilter GEP]((https://gateway-api.sigs.k8s.io/geps/gep-1494/)) - - [HTTPExternalAuthFilter Specification](https://gateway-api.sigs.k8s.io/reference/spec/#httpexternalauthfilter) - - [Kubernetes documentation on CEL validaton](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules) - - [NGINX HTTP Basic Auth Module](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) - - [NGINX JWT Auth Module](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) - - [NGINX OIDC Module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) +- [Gateway API ExternalAuthFilter GEP]((https://gateway-api.sigs.k8s.io/geps/gep-1494/)) +- [HTTPExternalAuthFilter Specification](https://gateway-api.sigs.k8s.io/reference/spec/#httpexternalauthfilter) +- [Kubernetes documentation on CEL validaton](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules) +- [NGINX HTTP Basic Auth Module](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) +- [NGINX JWT Auth Module](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) +- [NGINX OIDC Module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) From 1b8bac29656981de388f5a487aefdf7de760c243 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 09:08:57 +0000 Subject: [PATCH 04/12] Update Golang API with defaults and CEL validation with kubebuilder --- docs/proposals/authentication-filter.md | 39 +++++++++++++++++++------ 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 4deaaa2081..a9c1d4629f 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -66,6 +66,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1" ) // +genclient @@ -102,6 +103,12 @@ type AuthenticationFilterList struct { // AuthenticationFilterSpec defines the desired configuration. // Exactly one of Basic or JWT must be set according to Type. +// +kubebuilder:validation:XValidation:message="for type=Basic, spec.basic must be set and spec.jwt must be empty; for type=JWT, spec.jwt must be set and spec.basic must be empty",rule="self.type == 'Basic' ? self.basic != null && self.jwt == null : self.type == 'JWT' ? self.jwt != null && self.basic == null : false" + +// +kubebuilder:validation:XValidation:message="type 'Basic' requires spec.basic to be set. All other spec types must be unset",rule="self.type == 'Basic' ? self.type != null && self.jwt == null : true" +// +kubebuilder:validation:XValidation:message="type 'JWT' requires spec.jwt to be set. All other spec types must be unset",rule="self.type == 'JWT' ? self.type != null && self.basic == null : true" +// +kubebuilder:validation:XValidation:message="when spec.basic is set, type must be 'Basic'",rule="self.basic != null ? self.type == 'Basic' : true" +// +kubebuilder:validation:XValidation:message="when spec.jwt is set, type must be 'JWT'",rule="self.jwt != null ? self.type == 'JWT' : true" type AuthenticationFilterSpec struct { // Type selects the authentication mechanism. // @@ -134,7 +141,6 @@ type BasicAuth struct { Secret string `json:"secret"` // Key is the key within the Secret that contains the htpasswd data. - // Default: "htpasswd". // // +optional Key *string `json:"key,omitempty"` @@ -150,7 +156,11 @@ type BasicAuth struct { OnFailure *AuthFailureResponse `json:"onFailure,omitempty"` } - // JWTAuth configures JWT-based authentication (NGINX Plus). +// JWTAuth configures JWT-based authentication (NGINX Plus). +// +kubebuilder:validation:XValidation:message="mode 'File' requires file set and remote unset",rule="self.mode == 'File' ? self.file != null && self.remote == null : true" +// +kubebuilder:validation:XValidation:message="mode 'Remote' requires remote set and file unset",rule="self.mode == 'Remote' ? self.remote != null && self.file == null : true" +// +kubebuilder:validation:XValidation:message="when file is set, mode must be 'File'",rule="self.file != null ? self.mode == 'File' : true" +// +kubebuilder:validation:XValidation:message="when remote is set, mode must be 'Remote'",rule="self.remote != null ? self.mode == 'Remote' : true" type JWTAuth struct { // Realm used by NGINX auth_jwt; sets realm in the auth challenge. // @@ -162,6 +172,8 @@ type JWTAuth struct { // // +optional // +kubebuilder:validation:Enum=File;Remote + // +kubebuilder:default=File + // +kubebuilder:validation:XValidation:message="mode must be one of [File, Remote]",rule="self in ['File','Remote']" Mode JWTKeyMode `json:"mode,omitempty"` // File specifies local JWKS configuration (Secret or ConfigMap, mount path, file name). @@ -180,7 +192,8 @@ type JWTAuth struct { // Example: "60s". // // +optional - Leeway *string `json:"leeway,omitempty"` + // +kubebuilder:default=60s + Leeway *v1alpha1.Duration `json:"leeway,omitempty"` // Type sets token type: signed | encrypted | nested (auth_jwt_type). // Default: "signed". @@ -333,26 +346,31 @@ type JWTTokenSource struct { // Read token from Authorization header. Default: true. // // +optional + // +kubebuilder:default=true Header *bool `json:"header,omitempty"` // Read token from a cookie. Default: false. // // +optional + // +kubebuilder:default=false Cookie *bool `json:"cookie,omitempty"` // CookieName when Cookie is true. Example: "access_token". // // +optional + // +kubebuilder:default=access_token CookieName *string `json:"cookieName,omitempty"` // Read token from query string. Default: false. // // +optional + // +kubebuilder:default=false Query *bool `json:"query,omitempty"` // QueryParam when Query is true. Example: "access_token". // // +optional + // +kubebuilder:default=access_token QueryParam *string `json:"queryParam,omitempty"` } @@ -395,9 +413,12 @@ const ( // AuthFailureResponse customizes 401/403 failures. type AuthFailureResponse struct { - // Allowed: 401, 403. Default: 401. + // Allowed: 401, 403. + // Default: 401. // // +optional + // +kubebuilder:default=401 + // +kubebuilder:validation:XValidation:message="statusCode must be 401 or 403",rule="self == null || self in [401, 403]" StatusCode *int32 `json:"statusCode,omitempty"` // Challenge scheme. If omitted, inferred from filter Type (Basic|Bearer). @@ -407,10 +428,11 @@ type AuthFailureResponse struct { Scheme *AuthScheme `json:"scheme,omitempty"` // Controls whether a default canned body is sent or an empty body. - // Default: Default. + // Default: Unauthorized. // // +optional // +kubebuilder:validation:Enum=Unauthorized;Forbidden;Empty + // +kubebuilder:default=Unauthorized BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"` } @@ -422,13 +444,12 @@ type LocalObjectReference struct { // SecretKeyReference references a Secret and an optional key. type SecretKeyReference struct { Name string `json:"name"` + // Key within the Secret data. If omitted, controller defaults apply (e.g., "jwks.json"). - // - // +optional - Key *string `json:"key,omitempty"` + Key string `json:"key,omitempty"` } -// AuthenticationFilterStatus defines the state of AuthenticationFilter (similar to SnippetsFilter). +// AuthenticationFilterStatus defines the state of AuthenticationFilter. type AuthenticationFilterStatus struct { // Controllers is a list of Gateway API controllers that processed the AuthenticationFilter // and the status of the AuthenticationFilter with respect to each controller. From 2f143e9a6cfd2426a123f68ca2dc7371f248635d Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 09:13:30 +0000 Subject: [PATCH 05/12] Add additional defaults and CEL validations --- docs/proposals/authentication-filter.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index a9c1d4629f..afa36f6418 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -200,6 +200,7 @@ type JWTAuth struct { // // +optional // +kubebuilder:validation:Enum=signed;encrypted;nested + // +kubebuilder:default=signed Type *JWTTokenType `json:"type,omitempty"` // KeyCache is the cache duration for keys (auth_jwt_key_cache). @@ -240,6 +241,7 @@ const ( ) // JWTFileKeySource specifies local JWKS key configuration. +// +kubebuilder:validation:XValidation:message="exactly one of configMapRef or secretRef must be set",rule="(self.configMapRef == null) != (self.secretRef == null)" type JWTFileKeySource struct { // ConfigMapRef references a ConfigMap containing the JWKS. // Exactly one of ConfigMapRef or SecretRef must be set. From 47ff38be01895d3c049c553b1977afdd3aab2995 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 13:25:02 +0000 Subject: [PATCH 06/12] Fix typos --- docs/proposals/authentication-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index afa36f6418..8b82826fde 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -25,7 +25,7 @@ This new filter should eventually expose all forms of authentication available t ## Introduction -This document focus expliclty on Authentiaction (AuthN) and not Authorization (AuthZ). Authentiaction (AuthN) defines the verification of identiy. It asks the question, "Who are you?". This is different from Authorization (AuthZ), which preceeds Authentication. It asks the question, "What are you allowed to do". +This document focuses expliclty on Authentication (AuthN) and not Authorization (AuthZ). Authentication (AuthN) defines the verification of identiy. It asks the question, "Who are you?". This is different from Authorization (AuthZ), which preceeds Authentication. It asks the question, "What are you allowed to do". This document also focus on HTTP Basic Authentication and JWT Authentication. Other authentication methods such as OpenID Connect (OIDC) are mentioned, but are not part of the CRD design. These will be covered in future design and implementation tasks. From 40b8224f9358e3f3c9f2ca6e433a89ee8b75344e Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 13:51:23 +0000 Subject: [PATCH 07/12] Update comments in GolangAPI to decribe relative NGINX directives --- docs/proposals/authentication-filter.md | 66 +++++++++++++++++-------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 8b82826fde..2a541a37cc 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -141,11 +141,10 @@ type BasicAuth struct { Secret string `json:"secret"` // Key is the key within the Secret that contains the htpasswd data. - // - // +optional - Key *string `json:"key,omitempty"` + Key string `json:"key,omitempty"` - // Realm used by NGINX auth_basic; helps with logging and WWW-Authenticate. + // Realm used by NGINX `auth_basic`. + // Configures "realm="" in WWW-Authenticate header in error page location. // // +optional Realm *string `json:"realm,omitempty"` @@ -162,17 +161,19 @@ type BasicAuth struct { // +kubebuilder:validation:XValidation:message="when file is set, mode must be 'File'",rule="self.file != null ? self.mode == 'File' : true" // +kubebuilder:validation:XValidation:message="when remote is set, mode must be 'Remote'",rule="self.remote != null ? self.mode == 'Remote' : true" type JWTAuth struct { - // Realm used by NGINX auth_jwt; sets realm in the auth challenge. + // Realm used by NGINX `auth_jwt` directive. + // Configures "realm="" in WWW-Authenticate header in error page location. // // +optional + // +kubebuilder:default="Restricted" Realm *string `json:"realm,omitempty"` // Mode selects how JWT keys are provided: local file or remote JWKS. // Default: File. // // +optional - // +kubebuilder:validation:Enum=File;Remote // +kubebuilder:default=File + // +kubebuilder:validation:Enum=File;Remote // +kubebuilder:validation:XValidation:message="mode must be one of [File, Remote]",rule="self in ['File','Remote']" Mode JWTKeyMode `json:"mode,omitempty"` @@ -188,23 +189,27 @@ type JWTAuth struct { // +optional Remote *JWTRemoteKeySource `json:"remote,omitempty"` - // Leeway is the acceptable clock skew for exp/nbf checks (auth_jwt_leeway). - // Example: "60s". + // Leeway is the acceptable clock skew for exp/nbf checks. + // Configures `auth_jwt_leeway` directive. + // Example: "auth_jwt_leeway 60s". // // +optional // +kubebuilder:default=60s Leeway *v1alpha1.Duration `json:"leeway,omitempty"` - // Type sets token type: signed | encrypted | nested (auth_jwt_type). - // Default: "signed". + // Type sets token type: signed | encrypted | nested. + // Default: signed. + // Configures `auth_jwt_type` directive. + // Example: "auth_jwt_type signed;". // // +optional - // +kubebuilder:validation:Enum=signed;encrypted;nested // +kubebuilder:default=signed + // +kubebuilder:validation:Enum=signed;encrypted;nested Type *JWTTokenType `json:"type,omitempty"` - // KeyCache is the cache duration for keys (auth_jwt_key_cache). - // Example: "10m". + // KeyCache is the cache duration for keys. + // Configures auth_jwt_key_cache directive. + // Example: "auth_jwt_key_cache 10m". // // +optional KeyCache *string `json:"keyCache,omitempty"` @@ -214,8 +219,23 @@ type JWTAuth struct { // +optional OnFailure *AuthFailureResponse `json:"onFailure,omitempty"` - // Require defines claims that must match exactly (e.g., iss, aud). - // NGF will translate these into NGINX maps and auth_jwt_require directives. + // Require defines claims that must match exactly (e.g. iss, aud). + // These translate into NGINX maps and auth_jwt_require directives. + // Example directives and maps: + // + // auth_jwt_require $valid_jwt_iss; + // auth_jwt_require $valid_jwt_aud; + // + // map $jwt_claim_iss $valid_jwt_iss { + // "https://issuer.example.com" 1; + // "https://issuer.example1.com" 1; + // default 0; + // } + // map $jwt_claim_aud $valid_jwt_aud { + // "api" 1; + // "cli" 1; + // default 0; + // } // // +optional Require *JWTRequiredClaims `json:"require,omitempty"` @@ -256,15 +276,18 @@ type JWTFileKeySource struct { SecretRef *SecretKeyReference `json:"secretRef,omitempty"` // MountPath is the path where NGF will mount the data into the NGINX container. + // Used in `auth_jwt_key_file` directive. // Example: "/etc/nginx/keys". MountPath string `json:"mountPath"` // FileName is the file name of the JWKS within the mount path. + // Used in `auth_jwt_key_file` directive. // Example: "jwks.json". FileName string `json:"fileName"` - // KeyCache is the cache duration for keys (auth_jwt_key_cache). - // Example: "10m". + // KeyCache is the cache duration for keys. + // Configures `auth_jwt_key_cache` directive + // Example: "auth_jwt_key_cache 10m;". // // +optional KeyCache *string `json:"keyCache,omitempty"` @@ -282,14 +305,15 @@ type JWTRemoteKeySource struct { Cache *JWKSCache `json:"cache,omitempty"` } - // JWKSCache controls NGINX proxy_cache_path and proxy_cache settings used for JWKS responses. + // JWKSCache controls NGINX `proxy_cache_path` and `proxy_cache` settings used for JWKS responses. type JWKSCache struct { // Path is the filesystem path for cached JWKS objects. // Example: "/var/cache/nginx/jwks". Path string `json:"path"` // Levels specifies the directory hierarchy for cached files. - // Example: "1:2". + // Used in `proxy_cache_path` directive. + // Example: "levels=1:2". // // +optional Levels *string `json:"levels,omitempty"` @@ -424,8 +448,10 @@ type AuthFailureResponse struct { StatusCode *int32 `json:"statusCode,omitempty"` // Challenge scheme. If omitted, inferred from filter Type (Basic|Bearer). + // Configures WWW-Authenticate header in error page location. // // +optional + // +kubebuilder:default=Basic // +kubebuilder:validation:Enum=Basic;Bearer Scheme *AuthScheme `json:"scheme,omitempty"` @@ -433,8 +459,8 @@ type AuthFailureResponse struct { // Default: Unauthorized. // // +optional - // +kubebuilder:validation:Enum=Unauthorized;Forbidden;Empty // +kubebuilder:default=Unauthorized + // +kubebuilder:validation:Enum=Unauthorized;Forbidden;Empty BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"` } From 24966b894c2feadefc622170587c6ced309b2e69 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 14:06:58 +0000 Subject: [PATCH 08/12] Update API and Security Considerations for ReferenceGrant integration --- docs/proposals/authentication-filter.md | 125 ++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 2a541a37cc..93dcf2e013 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -143,6 +143,12 @@ type BasicAuth struct { // Key is the key within the Secret that contains the htpasswd data. Key string `json:"key,omitempty"` + // SecretRef allows referencing a Secret in the same or another namespace. + // When namespace is set and differs from the filter's namespace, a ReferenceGrant in the target namespace is required. + // + // +optional + SecretRef *NamespacedSecretKeyReference `json:"secretRef,omitempty"` + // Realm used by NGINX `auth_basic`. // Configures "realm="" in WWW-Authenticate header in error page location. // @@ -267,13 +273,13 @@ type JWTFileKeySource struct { // Exactly one of ConfigMapRef or SecretRef must be set. // // +optional - ConfigMapRef *LocalObjectReference `json:"configMapRef,omitempty"` + ConfigMapRef *NamespacedObjectReference `json:"configMapRef,omitempty"` // SecretRef references a Secret containing the JWKS (with optional key). // Exactly one of ConfigMapRef or SecretRef must be set. // // +optional - SecretRef *SecretKeyReference `json:"secretRef,omitempty"` + SecretRef *NamespacedSecretKeyReference `json:"secretRef,omitempty"` // MountPath is the path where NGF will mount the data into the NGINX container. // Used in `auth_jwt_key_file` directive. @@ -324,16 +330,16 @@ type JWKSCache struct { // +optional KeysZoneName *string `json:"keysZoneName,omitempty"` - // KeysZoneSize is the size of the cache keys zone (e.g., "10m"). + // KeysZoneSize is the size of the cache keys zone (e.g. "10m"). // This is required to avoid unbounded allocations. KeysZoneSize string `json:"keysZoneSize"` - // MaxSize limits the total size of the cache (e.g., "50m"). + // MaxSize limits the total size of the cache (e.g. "50m"). // // +optional MaxSize *string `json:"maxSize,omitempty"` - // Inactive defines the inactivity timeout before cached items are evicted (e.g., "10m"). + // Inactive defines the inactivity timeout before cached items are evicted (e.g. "10m"). // // +optional Inactive *string `json:"inactive,omitempty"` @@ -469,14 +475,32 @@ type LocalObjectReference struct { Name string `json:"name"` } -// SecretKeyReference references a Secret and an optional key. + // SecretKeyReference references a Secret and an optional key. type SecretKeyReference struct { Name string `json:"name"` - // Key within the Secret data. If omitted, controller defaults apply (e.g., "jwks.json"). + // Key within the Secret data. If omitted, controller defaults apply (e.g. "jwks.json"). Key string `json:"key,omitempty"` } +// NamespacedObjectReference references an object by name with an optional namespace. +// If namespace is omitted, it defaults to the AuthenticationFilter's namespace. +type NamespacedObjectReference struct { + // +optional + Namespace *string `json:"namespace,omitempty"` + Name string `json:"name"` +} + +// NamespacedSecretKeyReference references a Secret and optional key, with an optional namespace. +// If namespace differs from the filter's, a ReferenceGrant in the target namespace is required. +type NamespacedSecretKeyReference struct { + // +optional + Namespace *string `json:"namespace,omitempty"` + Name string `json:"name"` + // +optional + Key *string `json:"key,omitempty"` +} + // AuthenticationFilterStatus defines the state of AuthenticationFilter. type AuthenticationFilterStatus struct { // Controllers is a list of Gateway API controllers that processed the AuthenticationFilter @@ -1056,9 +1080,93 @@ Users that attach an `AuthenticaitonFilter` to a HTTPRoute/GRPCRoute should be a Any exmaple configurations and deployments for the `AuthenticationFilter` should enable HTTPS at the Gateway level by default. -The `mountPath` for local JWKS should be mounted to a fixed location (e.g., /etc/nginx/keys). +The `mountPath` for local JWKS should be mounted to a fixed location (e.g. /etc/nginx/keys). The `fileName` for a local JWKS should be sanatized to a pattern of [A-Za-z0-9._-]. +### Namespace isolataion and cross-namespace references +Both Auth and Local JWKS should only have access to Secrets and ConfigMaps in the same namespace by default. + +Cross-namespace references are allowed only when authorized via a Gateway API ReferenceGrant in the target namespace. + +Controller behavior: +- Same-namespace references are permitted without a grant. +- For cross-namespace references, the controller MUST verify a ReferenceGrant exists in the target namespace: + - from: group=gateway.nginx.org, kind=AuthenticationFilter, namespace= + - to: group="", kind=(Secret|ConfigMap), name= +- If no valid grant is found, the filter status should update the status to `Accepted=False` with `reason=RefNotPermitted` and a clear message. We should avoid rendering any NGINX configuration in this scenario. + +Example: Grant BasicAuth in app-ns to read a Secret in security-ns +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: ReferenceGrant +metadata: + name: allow-basic-auth-secret + namespace: security-ns # target namespace where the Secret lives +spec: + from: + - group: gateway.nginx.org + kind: AuthenticationFilter + namespace: app-ns + to: + - group: "" # core API group + kind: Secret + name: basic-auth-users +``` + +AuthenticationFilter referencing the cross-namespace Secret +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: basic-auth + namespace: app-ns +spec: + type: Basic + basic: + secretRef: + namespace: security-ns + name: basic-auth-users + key: htpasswd + realm: "Restricted" +``` + +Example: Grant JWT file-based JWKS in keys-ns to filter in app-ns +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: ReferenceGrant +metadata: + name: allow-jwks-configmap + namespace: keys-ns +spec: + from: + - group: gateway.nginx.org + kind: AuthenticationFilter + namespace: app-ns + to: + - group: "" # core API group + kind: ConfigMap + name: jwt-keys +``` + +AuthenticationFilter referencing cross-namespace JWKS ConfigMap +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: jwt-auth + namespace: app-ns +spec: + type: JWT + jwt: + mode: File + file: + configMapRef: + namespace: keys-ns + name: jwt-keys + mountPath: /etc/nginx/keys + fileName: jwks.json +``` + ### Remote JWKS Proxy cache TTL should be configurable and set to a resonable default, reducing periods of stale cached JWKs. @@ -1099,7 +1207,6 @@ Detailed header breakdown: - Pragma: "no-cache" - This header is commonly paired with `Cache-Control: "no-store"` for broad coverage. It acts as an additional signal for older intermediaries that do not honor Cache-Control. - ### Validation When referencing an `AuthenticationFilter` in either a HTTPRoute or GRPCRoute, it is important that we ensure all configurable fields are validated, and that the resulting NGINX configuration is correct and secure From da1b17e44c6e96c2e4a2c3897d3740796e5c7c9f Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 14:21:12 +0000 Subject: [PATCH 09/12] Fix pre-commit errors --- docs/proposals/authentication-filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 93dcf2e013..477ab412b1 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -108,7 +108,7 @@ type AuthenticationFilterList struct { // +kubebuilder:validation:XValidation:message="type 'Basic' requires spec.basic to be set. All other spec types must be unset",rule="self.type == 'Basic' ? self.type != null && self.jwt == null : true" // +kubebuilder:validation:XValidation:message="type 'JWT' requires spec.jwt to be set. All other spec types must be unset",rule="self.type == 'JWT' ? self.type != null && self.basic == null : true" // +kubebuilder:validation:XValidation:message="when spec.basic is set, type must be 'Basic'",rule="self.basic != null ? self.type == 'Basic' : true" -// +kubebuilder:validation:XValidation:message="when spec.jwt is set, type must be 'JWT'",rule="self.jwt != null ? self.type == 'JWT' : true" +// +kubebuilder:validation:XValidation:message="when spec.jwt is set, type must be 'JWT'",rule="self.jwt != null ? self.type == 'JWT' : true" type AuthenticationFilterSpec struct { // Type selects the authentication mechanism. // @@ -165,7 +165,7 @@ type BasicAuth struct { // +kubebuilder:validation:XValidation:message="mode 'File' requires file set and remote unset",rule="self.mode == 'File' ? self.file != null && self.remote == null : true" // +kubebuilder:validation:XValidation:message="mode 'Remote' requires remote set and file unset",rule="self.mode == 'Remote' ? self.remote != null && self.file == null : true" // +kubebuilder:validation:XValidation:message="when file is set, mode must be 'File'",rule="self.file != null ? self.mode == 'File' : true" -// +kubebuilder:validation:XValidation:message="when remote is set, mode must be 'Remote'",rule="self.remote != null ? self.mode == 'Remote' : true" +// +kubebuilder:validation:XValidation:message="when remote is set, mode must be 'Remote'",rule="self.remote != null ? self.mode == 'Remote' : true" type JWTAuth struct { // Realm used by NGINX `auth_jwt` directive. // Configures "realm="" in WWW-Authenticate header in error page location. From 38dd8f728fb27076527421f20bb14efcf5c6c543 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 15:38:17 +0000 Subject: [PATCH 10/12] Fix typos and grammer --- docs/proposals/authentication-filter.md | 32 ++++++++++--------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 477ab412b1..cd7d23afa1 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -25,7 +25,7 @@ This new filter should eventually expose all forms of authentication available t ## Introduction -This document focuses expliclty on Authentication (AuthN) and not Authorization (AuthZ). Authentication (AuthN) defines the verification of identiy. It asks the question, "Who are you?". This is different from Authorization (AuthZ), which preceeds Authentication. It asks the question, "What are you allowed to do". +This document focuses explicitly on Authentication (AuthN) and not Authorization (AuthZ). Authentication (AuthN) defines the verification of identity. It asks the question, "Who are you?". This is different from Authorization (AuthZ), which preceeds Authentication. It asks the question, "What are you allowed to do". This document also focus on HTTP Basic Authentication and JWT Authentication. Other authentication methods such as OpenID Connect (OIDC) are mentioned, but are not part of the CRD design. These will be covered in future design and implementation tasks. @@ -33,7 +33,7 @@ This document also focus on HTTP Basic Authentication and JWT Authentication. Ot ## Use Cases - As an Application Developer, I want to secure access to my APIs and Backend Applications. -- As an Application Developer, I want to enforce authenticaiton on specific routes and matches. +- As an Application Developer, I want to enforce authentication on specific routes and matches. ### Understanding NGINX authentication methods @@ -46,15 +46,15 @@ This document also focus on HTTP Basic Authentication and JWT Authentication. Ot ## API, Customer Driven Interfaces, and User Experience This portion of the proposal will cover API design and interaction experience for use of Basic Auth and JWT. -This portioan also contains: +This portion also contains: 1. The Golang API 2. Example spec for Basic Auth - - Example HTTPRoutes and NINGX configuration + - Example HTTPRoutes and NGINX configuration 3. Example spec for JWT Auth - Example HTTPRoutes - Examples for Local & Remote JWKS configration - - Example NINGX configuration for both Local & Remote JWKS + - Example NGINX configuration for both Local & Remote JWKS - Example of additioanl optional fields ### Golang API @@ -639,7 +639,6 @@ http { add_header Content-Type "text/plain; charset=utf-8" always; add_header X-Content-Type-Options "nosniff" always; add_header Cache-Control "no-store" always; - add_header Pragma "no-cache" always; return 401 'Unauthorized'; } } @@ -837,7 +836,6 @@ http { add_header Content-Type "text/plain; charset=utf-8" always; add_header X-Content-Type-Options "nosniff" always; add_header Cache-Control "no-store" always; - add_header Pragma "no-cache" always; return 403 'Forbidden'; } } @@ -930,7 +928,6 @@ http { add_header Content-Type "text/plain; charset=utf-8" always; add_header X-Content-Type-Options "nosniff" always; add_header Cache-Control "no-store" always; - add_header Pragma "no-cache" always; return 401 'Unauthorized'; } } @@ -1010,7 +1007,7 @@ spec: ### Attachment -Filters must be attached to a HTTPRoute at the `rules.matces` level. +Filters must be attached to a HTTPRoute at the `rules.matches` level. This means that a single `AuthenticationFilter` may be attached mutliple times to a single HTTPRoute. #### Basic example @@ -1173,7 +1170,7 @@ Proxy cache TTL should be configurable and set to a resonable default, reducing ### Key rotation -Users sholud be advised to regularly rotate their JWKS keys in cases where they chose to reference a local JWKS via a `secrefRef` or `configMapRef` +Users should be advised to regularly rotate their JWKS keys in cases where they chose to reference a local JWKS via a `secrefRef` or `configMapRef` ### Auth failure behaviour @@ -1184,19 +1181,18 @@ Users sholud be advised to regularly rotate their JWKS keys in cases where they ### Auth failure default headers Below are a list of default defensive headers for authentication failure reponses. -We may choose to include these headers by default for improved robustness in auth falure responses. +We may choose to include these headers by default for improved robustness in auth failure responses. ```nginx add_header Content-Type "text/plain; charset=utf-8" always; add_header X-Content-Type-Options "nosniff" always; add_header Cache-Control "no-store" always; -add_header Pragma "no-cache" always; ``` Detailed header breakdown: - Content-Type: "text/plain; charset=utf-8" - - This header explicitly set the body as plan text. This prevents browsers from treating the response as HTML or JavaScript, and is effective at mitigating Cross-side scrpting (XSS) through error pages + - This header explicitly set the body as plain text. This prevents browsers from treating the response as HTML or JavaScript, and is effective at mitigating Cross-side scrpting (XSS) through error pages - X-Content-Type-Options: "nosniff" - This header prevents content type confusion. This occurrs when browsers guesses HTML & JavaScript, and executes it despite a benign type. @@ -1204,19 +1200,17 @@ Detailed header breakdown: - Cache-Control: "no-store" - This header informs browsers and proxies not to cache the response. Avoids sensitive, auth-related content, from being being stored and served later to unintended recipients. -- Pragma: "no-cache" - - This header is commonly paired with `Cache-Control: "no-store"` for broad coverage. It acts as an additional signal for older intermediaries that do not honor Cache-Control. ### Validation -When referencing an `AuthenticationFilter` in either a HTTPRoute or GRPCRoute, it is important that we ensure all configurable fields are validated, and that the resulting NGINX configuration is correct and secure +When referencing an `AuthenticationFilter` in either a HTTPRoute or GRPCRoute, it is important that we ensure all configurable fields are validated, and that the resulting NGINX configuration is correct and secure. All fields in the `AuthenticationFilter` will be validated with Open API Schema. We should also include [CEL](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules) validation where required. We should validated that only one `AuthenticationFilter` is referenced per-rule. Multiple references to an `AuthenticationFilter` in a single rule should result in an `Invalid` HTTPRoute/GRPCRoute, and the resource should be `Rejected`. -an `AuthenticationFilter` that sets a `onFailure.statusCode` to anything other than `401` or `403` should be rejected. This relates to the "Auth failure behaviour" section in the Security Condierations section. +An `AuthenticationFilter` that sets a `onFailure.statusCode` to anything other than `401` or `403` should be rejected. This relates to the "Auth failure behaviour" section in the Security Considerations section. ## Alternatives @@ -1225,7 +1219,7 @@ The Gateway API defines a means to standardise authentication through use of the This allows users to reference an external authentication services, such as Keycloak, to handle the authentication requests. While this API is available in the experimental channel, it is subject to change. -Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quckly provide authenticaiton to our users while allowing us to closley monitor progress of the ExternalAuthFilter. +Our decision to go forward with our own `AuthenticationFilter` was to ensure we could quickly provide authentication to our users while allowing us to closely monitor progress of the ExternalAuthFilter. It is certainly possible for us to provide an External Authentication Services that leverages NGINX and is something we can further investigate as the API progresses. @@ -1233,7 +1227,7 @@ It is certainly possible for us to provide an External Authentication Services t ### Documenting filter behavour -In regards to documentation of filter behavour with the `AuthenticationFilter`, the Gateway API documentation on filters states the following: +In regards to documentation of filter behaviour with the `AuthenticationFilter`, the Gateway API documentation on filters states the following: ```text Wherever possible, implementations SHOULD implement filters in the order they are specified. From e36274577876e9330f3f55c30960554928e01cd7 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 15:53:48 +0000 Subject: [PATCH 11/12] Update BasicAuth AIP and examples to use `secretRef` --- docs/proposals/authentication-filter.md | 31 +++++-------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index cd7d23afa1..0583119ac1 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -136,14 +136,7 @@ const ( // BasicAuth configures HTTP Basic Authentication. type BasicAuth struct { - // Secret is the name of the Secret containing htpasswd data. - // The Secret must be in the same namespace as this filter. - Secret string `json:"secret"` - - // Key is the key within the Secret that contains the htpasswd data. - Key string `json:"key,omitempty"` - - // SecretRef allows referencing a Secret in the same or another namespace. + // SecretRef allows referencing a Secret in the same or different namespace. // When namespace is set and differs from the filter's namespace, a ReferenceGrant in the target namespace is required. // // +optional @@ -470,19 +463,6 @@ type AuthFailureResponse struct { BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"` } -// LocalObjectReference references a namespaced object in the same namespace. -type LocalObjectReference struct { - Name string `json:"name"` -} - - // SecretKeyReference references a Secret and an optional key. -type SecretKeyReference struct { - Name string `json:"name"` - - // Key within the Secret data. If omitted, controller defaults apply (e.g. "jwks.json"). - Key string `json:"key,omitempty"` -} - // NamespacedObjectReference references an object by name with an optional namespace. // If namespace is omitted, it defaults to the AuthenticationFilter's namespace. type NamespacedObjectReference struct { @@ -546,10 +526,11 @@ metadata: spec: type: Basic basic: - secret: basic-auth-users # Secret containing htpasswd data - key: htpasswd # key within the Secret - realm: "Restricted" # Optional. Helps with logging - onFailure: # Optional. These setting may be defaults. + secretRef: + name: basic-auth-users # Secret containing htpasswd data + key: htpasswd # key within the Secret + realm: "Restricted" # Optional. Helps with logging + onFailure: # Optional. These setting may be defaults. statusCode: 401 scheme: Basic ``` From dd5aaa89ca8e88c0f004bd853d54ff8027aa9c39 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 7 Nov 2025 15:59:21 +0000 Subject: [PATCH 12/12] Update KeyCache to use v1alpha1.Duration --- docs/proposals/authentication-filter.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 0583119ac1..9c6e8889cd 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -211,7 +211,7 @@ type JWTAuth struct { // Example: "auth_jwt_key_cache 10m". // // +optional - KeyCache *string `json:"keyCache,omitempty"` + KeyCache *v1alpha1.Duration `json:"keyCache,omitempty"` // OnFailure customizes the 401 response for failed authentication. // @@ -289,7 +289,7 @@ type JWTFileKeySource struct { // Example: "auth_jwt_key_cache 10m;". // // +optional - KeyCache *string `json:"keyCache,omitempty"` + KeyCache *v1alpha1.Duration `json:"keyCache,omitempty"` } // JWTRemoteKeySource specifies remote JWKS configuration.