From 16338ce1cfa2a8df58ef8d062f2f5fce92a01b79 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 08:51:51 +0000 Subject: [PATCH 01/10] Add initial valid and invalid filter reference scenarios --- docs/proposals/authentication-filter.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 41b22fb511..8a49fdc332 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -906,7 +906,25 @@ This can use the status `RouteConditionPartiallyInvalid` defined in the Gateway ## Testing - Unit tests -- Functional tests to validate behavioural scenarios when referencing filters in different combinations. The details of these tests are out of scope for this document. +- Functional tests to validate behavioural scenarios when referencing filters in different combinations. + +### Functional Test Cases + + + +Valid reference scenarios +- Resolved filter referenced by a single route rule within a single HTTP/GRPCRoute +- Resolved filter referenced by multiple route rules within a single HTTP/GRPCRoute +- Resolved filter reference by multiple HTTP/GRPCRoutes + +Invalid reference scenarios +- Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute +- Resolved filter referenced multiple times by multiple route rules within a single HTTP/GRPCRoute +- Unresolved filter referenced by a single route rule within a single HTTP/GRPCRoute +- Unresolved filter referenced by multiple route rules within a single HTTP/GRPCRoute ## Security Considerations From fdcc50460fd528a6eec93c85fa0512532d8156c8 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 09:03:23 +0000 Subject: [PATCH 02/10] Add details around resolved references --- docs/proposals/authentication-filter.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 8a49fdc332..dccc77dfce 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -910,17 +910,21 @@ This can use the status `RouteConditionPartiallyInvalid` defined in the Gateway ### Functional Test Cases - +Invalid resolved filter secnarios: +- Resolved filter that references a secret that does not exist +- Resolved filter that referenced a secret with the incorrect data key -Valid reference scenarios +Valid reference scenarios: - Resolved filter referenced by a single route rule within a single HTTP/GRPCRoute - Resolved filter referenced by multiple route rules within a single HTTP/GRPCRoute - Resolved filter reference by multiple HTTP/GRPCRoutes -Invalid reference scenarios +Invalid reference scenarios: - Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute - Resolved filter referenced multiple times by multiple route rules within a single HTTP/GRPCRoute - Unresolved filter referenced by a single route rule within a single HTTP/GRPCRoute From c82527a338a51fcdd0a94bcca3626b7e3cdfed7e Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 09:06:02 +0000 Subject: [PATCH 03/10] Update API spec for Basic Auth only --- docs/proposals/authentication-filter.md | 310 +++++------------------- 1 file changed, 54 insertions(+), 256 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index dccc77dfce..925e265696 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -74,14 +74,14 @@ This portion also contains: ### Golang API -Below is the Golang API for the `AuthenticationFilter` API: +Below is the Golang API for the `AuthenticationFilter` API. +This is currently designed for Basic Auth. ```go package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1" ) // +genclient @@ -91,20 +91,17 @@ import ( // +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. +// AuthenticationFilter configures request authentication and is +// referenced by HTTPRoute and GRPCRoute filters using ExtensionRef. type AuthenticationFilter struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` - // Spec defines the desired state of the AuthenticationFilter. - Spec AuthenticationFilterSpec `json:"spec"` + // 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"` + // Status defines the state of the AuthenticationFilter. + Status AuthenticationFilterStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true @@ -112,257 +109,58 @@ type AuthenticationFilter struct { // AuthenticationFilterList contains a list of AuthenticationFilter resources. type AuthenticationFilterList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` + metav1.ListMeta `json:"metadata"` Items []AuthenticationFilter `json:"items"` } // 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" +// +kubebuilder:validation:XValidation:message="for type=Basic, spec.basic must be set",rule="!(!has(self.basic) && self.type == 'Basic')" +// +//nolint:lll type AuthenticationFilterSpec struct { - // Type selects the authentication mechanism. - Type AuthType `json:"type"` - - // Basic configures HTTP Basic Authentication. - // Required when Type == Basic. - // - // +optional - Basic *BasicAuth `json:"basic,omitempty"` + // Basic configures HTTP Basic Authentication. + // + // +optional + Basic *BasicAuth `json:"basic,omitempty"` - // JWT configures JSON Web Token authentication (NGINX Plus). - // Required when Type == JWT. - // - // +optional - JWT *JWTAuth `json:"jwt,omitempty"` + // Type selects the authentication mechanism. + Type AuthType `json:"type"` } // AuthType defines the authentication mechanism. -// +kubebuilder:validation:Enum=Basic;JWT +// +// +kubebuilder:validation:Enum=Basic; type AuthType string const ( - AuthTypeBasic AuthType = "Basic" - AuthTypeJWT AuthType = "JWT" + // AuthTypeBasic is the HTTP Basic Authentication mechanism. + AuthTypeBasic AuthType = "Basic" ) // BasicAuth configures HTTP Basic Authentication. type BasicAuth struct { - // SecretRef allows referencing a Secret in the same namespace - SecretRef LocalObjectReference `json:"secretRef"` - - // Realm used by NGINX `auth_basic` directive. - // https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html#auth_basic - // Also configures "realm="" in WWW-Authenticate header in error page location. - Realm string `json:"realm"` - - // OnFailure customizes the 401 response for failed authentication. - // - // +optional - OnFailure *AuthFailureResponse `json:"onFailure,omitempty"` -} - -// JWTKeyMode selects where JWT keys come from. -// +kubebuilder:validation:Enum=File;Remote -type JWTKeyMode string - -const ( - JWTKeyModeFile JWTKeyMode = "File" - JWTKeyModeRemote JWTKeyMode = "Remote" -) - -// 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` directive - // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt - // Configures "realm="" in WWW-Authenticate header in error page location. - Realm string `json:"realm"` - - // Mode selects how JWT keys are provided: local file or remote JWKS. - Mode JWTKeyMode `json:"mode"` - - // File specifies local JWKS configuration. - // Required when Mode == File. - // - // +optional - File *JWTFileKeySource `json:"file,omitempty"` - - // Remote specifies remote JWKS configuration. - // Required when Mode == Remote. - // - // +optional - Remote *RemoteKeySource `json:"remote,omitempty"` - - // Leeway is the acceptable clock skew for exp/nbf checks. - // Configures `auth_jwt_leeway` directive. - // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_leeway - // Example: "auth_jwt_leeway 60s". - // - // +optional - Leeway *v1alpha1.Duration `json:"leeway,omitempty"` - - // Type sets token type: signed | encrypted | nested. - // Default: signed. - // Configures `auth_jwt_type` directive. - // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_type - // Example: "auth_jwt_type signed;". - // - // +optional - // +kubebuilder:default=signed - Type *JWTType `json:"type,omitempty"` - - // KeyCache is the cache duration for keys. - // Configures auth_jwt_key_cache directive. - // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_cache - // Example: "auth_jwt_key_cache 10m". - // - // +optional - KeyCache *v1alpha1.Duration `json:"keyCache,omitempty"` + // SecretRef allows referencing a Secret in the same namespace. + SecretRef LocalObjectReference `json:"secretRef"` - // OnFailure customizes the 401 response for failed authentication. - // - // +optional - OnFailure *AuthFailureResponse `json:"onFailure,omitempty"` -} - -// JWTFileKeySource specifies local JWKS key configuration. -type JWTFileKeySource struct { - // SecretRef references a Secret containing the JWKS. - SecretRef LocalObjectReference `json:"secretRef"` - - // KeyCache is the cache duration for keys. - // Configures `auth_jwt_key_cache` directive. - // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_cache - // Example: "auth_jwt_key_cache 10m;". - // - // +optional - KeyCache *v1alpha1.Duration `json:"keyCache,omitempty"` + // Realm used by NGINX `auth_basic` directive. + // https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html#auth_basic + // Also configures "realm="" in WWW-Authenticate header in error page location. + Realm string `json:"realm"` } -// LocalObjectReference specifies a local Kubernetes object -// with a required `key` field to extract data. +// LocalObjectReference specifies a local Kubernetes object. type LocalObjectReference struct { - Name string: `json:"name"` -} - - - // RemoteKeySource specifies remote JWKS configuration. -type RemoteKeySource 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 { - // Levels specifies the directory hierarchy for cached files. - // Used in `proxy_cache_path` directive. - // https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path - // Example: "levels=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"` -} - -// JWTType represents NGINX auth_jwt_type. -// +kubebuilder:validation:Enum=signed;encrypted;nested -type JWTType string - -const ( - JWTTypeSigned JWTType = "signed" - JWTTypeEncrypted JWTType = "encrypted" - JWTTypeNested JWTType = "nested" -) - -// AuthScheme enumerates supported WWW-Authenticate schemes. -// +kubebuilder:validation:Enum=Basic;Bearer -type AuthScheme string - -const ( - AuthSchemeBasic AuthScheme = "Basic" - AuthSchemeBearer AuthScheme = "Bearer" -) - -// AuthFailureBodyPolicy controls the failure response body behavior. -// +kubebuilder:validation:Enum=Unauthorized;Forbidden;Empty -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 - // +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). - // Configures WWW-Authenticate header in error page location. - // - // +optional - // +kubebuilder:default=Basic - Scheme *AuthScheme `json:"scheme,omitempty"` - - // Controls whether a default canned body is sent or an empty body. - // Default: Unauthorized. - // - // +optional - // +kubebuilder:default=Unauthorized - BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"` + // Name is the referenced object. + Name string `json:"name"` } // 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. - // - // +kubebuilder:validation:MaxItems=16 - Controllers []ControllerStatus `json:"controllers,omitempty"` + // 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. @@ -372,22 +170,22 @@ type AuthenticationFilterConditionType string 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" + // 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" ) ``` From 4163a49f1f7fed67199c82da4c929261bb630f72 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 09:39:28 +0000 Subject: [PATCH 04/10] Remove AuthFailure from API. Move to stretch goals --- docs/proposals/authentication-filter.md | 344 +++++++++++++++++------- 1 file changed, 253 insertions(+), 91 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 925e265696..2ee8acf8e6 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -74,14 +74,14 @@ This portion also contains: ### Golang API -Below is the Golang API for the `AuthenticationFilter` API. -This is currently designed for Basic Auth. +Below is the Golang API for the `AuthenticationFilter` API: ```go package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1" ) // +genclient @@ -91,17 +91,20 @@ import ( // +kubebuilder:resource:categories=nginx-gateway-fabric,shortName=authfilter;authenticationfilter // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// AuthenticationFilter configures request authentication and is -// referenced by HTTPRoute and GRPCRoute filters using ExtensionRef. +// 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"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec defines the desired state of the AuthenticationFilter. - Spec AuthenticationFilterSpec `json:"spec"` + // Spec defines the desired state of the AuthenticationFilter. + Spec AuthenticationFilterSpec `json:"spec"` - // Status defines the state of the AuthenticationFilter. - Status AuthenticationFilterStatus `json:"status,omitempty"` + // 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 @@ -109,32 +112,41 @@ type AuthenticationFilter struct { // AuthenticationFilterList contains a list of AuthenticationFilter resources. type AuthenticationFilterList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` + metav1.ListMeta `json:"metadata,omitempty"` Items []AuthenticationFilter `json:"items"` } // AuthenticationFilterSpec defines the desired configuration. -// +kubebuilder:validation:XValidation:message="for type=Basic, spec.basic must be set",rule="!(!has(self.basic) && self.type == 'Basic')" -// -//nolint:lll +// 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 { - // Basic configures HTTP Basic Authentication. - // - // +optional - Basic *BasicAuth `json:"basic,omitempty"` + // Type selects the authentication mechanism. + Type AuthType `json:"type"` + + // Basic configures HTTP Basic Authentication. + // Required when Type == Basic. + // + // +optional + Basic *BasicAuth `json:"basic,omitempty"` - // Type selects the authentication mechanism. - Type AuthType `json:"type"` + // JWT configures JSON Web Token authentication (NGINX Plus). + // Required when Type == JWT. + // + // +optional + JWT *JWTAuth `json:"jwt,omitempty"` } // AuthType defines the authentication mechanism. -// -// +kubebuilder:validation:Enum=Basic; +// +kubebuilder:validation:Enum=Basic;JWT type AuthType string const ( - // AuthTypeBasic is the HTTP Basic Authentication mechanism. - AuthTypeBasic AuthType = "Basic" + AuthTypeBasic AuthType = "Basic" + AuthTypeJWT AuthType = "JWT" ) // BasicAuth configures HTTP Basic Authentication. @@ -154,13 +166,157 @@ type LocalObjectReference struct { Name string `json:"name"` } +// JWTKeyMode selects where JWT keys come from. +// +kubebuilder:validation:Enum=File;Remote +type JWTKeyMode string + +const ( + JWTKeyModeFile JWTKeyMode = "File" + JWTKeyModeRemote JWTKeyMode = "Remote" +) + +// 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` directive + // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt + // Configures "realm="" in WWW-Authenticate header in error page location. + Realm string `json:"realm"` + + // Mode selects how JWT keys are provided: local file or remote JWKS. + Mode JWTKeyMode `json:"mode"` + + // File specifies local JWKS configuration. + // Required when Mode == File. + // + // +optional + File *JWTFileKeySource `json:"file,omitempty"` + + // Remote specifies remote JWKS configuration. + // Required when Mode == Remote. + // + // +optional + Remote *RemoteKeySource `json:"remote,omitempty"` + + // Leeway is the acceptable clock skew for exp/nbf checks. + // Configures `auth_jwt_leeway` directive. + // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_leeway + // Example: "auth_jwt_leeway 60s". + // + // +optional + Leeway *v1alpha1.Duration `json:"leeway,omitempty"` + + // Type sets token type: signed | encrypted | nested. + // Default: signed. + // Configures `auth_jwt_type` directive. + // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_type + // Example: "auth_jwt_type signed;". + // + // +optional + // +kubebuilder:default=signed + Type *JWTType `json:"type,omitempty"` + + // KeyCache is the cache duration for keys. + // Configures auth_jwt_key_cache directive. + // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_cache + // Example: "auth_jwt_key_cache 10m". + // + // +optional + KeyCache *v1alpha1.Duration `json:"keyCache,omitempty"` +} + +// JWTFileKeySource specifies local JWKS key configuration. +type JWTFileKeySource struct { + // SecretRef references a Secret containing the JWKS. + SecretRef LocalObjectReference `json:"secretRef"` + + // KeyCache is the cache duration for keys. + // Configures `auth_jwt_key_cache` directive. + // https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_cache + // Example: "auth_jwt_key_cache 10m;". + // + // +optional + KeyCache *v1alpha1.Duration `json:"keyCache,omitempty"` +} + + // RemoteKeySource specifies remote JWKS configuration. +type RemoteKeySource 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 { + // Levels specifies the directory hierarchy for cached files. + // Used in `proxy_cache_path` directive. + // https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path + // Example: "levels=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"` +} + +// JWTType represents NGINX auth_jwt_type. +// +kubebuilder:validation:Enum=signed;encrypted;nested +type JWTType string + +const ( + JWTTypeSigned JWTType = "signed" + JWTTypeEncrypted JWTType = "encrypted" + JWTTypeNested JWTType = "nested" +) + +// AuthScheme enumerates supported WWW-Authenticate schemes. +// +kubebuilder:validation:Enum=Basic;Bearer +type AuthScheme string + +const ( + AuthSchemeBasic AuthScheme = "Basic" + AuthSchemeBearer AuthScheme = "Bearer" +) + // 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. - // - // +kubebuilder:validation:MaxItems=16 - Controllers []ControllerStatus `json:"controllers,omitempty"` + // 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. @@ -170,22 +326,22 @@ type AuthenticationFilterConditionType string 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" + // 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" ) ``` @@ -202,9 +358,6 @@ spec: secretRef: name: basic-auth-users # Secret containing auth data. realm: "Restricted" - 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: @@ -304,10 +457,6 @@ http { # Path is generated by NGF using the name and key from the secret auth_basic_user_file /etc/nginx/secrets/basic-auth-users/auth; - # 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 ""; @@ -320,12 +469,6 @@ http { # 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; - return 401 'Unauthorized'; - } } } ``` @@ -358,9 +501,6 @@ spec: 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 @@ -384,9 +524,6 @@ spec: 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 ``` #### Secret referenced by filter @@ -493,9 +630,6 @@ http { 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 ""; @@ -508,12 +642,6 @@ http { # 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; - return 403 'Forbidden'; - } } } ``` @@ -574,9 +702,6 @@ http { 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 ""; @@ -597,12 +722,6 @@ http { 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; - return 401 'Unauthorized'; - } } } ``` @@ -710,19 +829,24 @@ This can use the status `RouteConditionPartiallyInvalid` defined in the Gateway Note: The keyword "resolved" is used to refer to a filter that the controller has found, and matches the reference of the route rule. For a filter to be considered "resolved", it must: + 1. Exist in the same namespace as the HTTP/GRPCRoute 2. The group and kind referenced must match -Invalid resolved filter secnarios: +Invalid resolved filter scenarios: + - Resolved filter that references a secret that does not exist - Resolved filter that referenced a secret with the incorrect data key Valid reference scenarios: + - Resolved filter referenced by a single route rule within a single HTTP/GRPCRoute - Resolved filter referenced by multiple route rules within a single HTTP/GRPCRoute - Resolved filter reference by multiple HTTP/GRPCRoutes Invalid reference scenarios: + +- Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute - Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute - Resolved filter referenced multiple times by multiple route rules within a single HTTP/GRPCRoute - Unresolved filter referenced by a single route rule within a single HTTP/GRPCRoute @@ -749,12 +873,6 @@ Proxy cache TTL should be configurable and set to a reasonable default, reducing Users should be advised to regularly rotate their JWKS keys in cases where they chose to reference a local JWKS via a `secrefRef` -### 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. - ### Optional headers Below are a list of optional defensive headers that user's may choose to include. @@ -789,8 +907,6 @@ We should validated that only one `AuthenticationFilter` is referenced per-rule. This scenario can use the status `RouteConditionPartiallyInvalid` defined in the Gateway API here: https://github.com/nginx/nginx-gateway-fabric/blob/3934c5c8c60b5aea91be4337d63d4e1d8640baa8/internal/controller/state/conditions/conditions.go#L402 -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 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. @@ -819,6 +935,52 @@ document that behavior. ## Stretch Goals +### Custom authentiation failure response + +By default, authentication failures return a 401 response. +If a used wanted to change this response code, or include additioanl headers in this response, we can include a custom named location that can be called by the [error_page](https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page) directive. + +Example AuthenticationFilter configuration: + +```yaml +apiVersion: gateway.nginx.org/v1alpha1 +kind: AuthenticationFilter +metadata: + name: basic-auth +spec: + type: Basic + basic: + secretRef: + name: basic-auth-users + realm: "Restricted" + onFailure: + statusCode: 401 + scheme: Basic +``` + +Example NGINX configuration: + +```nginx +server{ + location /api { + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/secrets/basic-auth-users/auth; + + # Calls named location + error_page 401 = @basic_auth_failure; + } + + location @basic_auth_failure { + add_header WWW-Authenticate 'Basic realm="Restricted"' always; + return 401 'Unauthorized'; + } +} +``` + +If we support this configuration, 3xx response codes should not be allowed and AuthenticationFilter.onFailure must not support redirect targets. This is to prevent to prevent open-redirect abuse. + +We should only allow 401 and 403 response codes. + ### Cross namespace access When referencing secrets for Basic Auth and JWT Auth, the initial implementation will use `LocalObjectReference`. From eb0b8eee45035fac120e6c1258eb420b46ce9e77 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 10:06:09 +0000 Subject: [PATCH 05/10] Fix typos and indentation --- docs/proposals/authentication-filter.md | 121 ++++++++++++------------ 1 file changed, 59 insertions(+), 62 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 2ee8acf8e6..f618865dcf 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -14,7 +14,7 @@ This new filter should eventually expose all forms of authentication available t - Design Authentication CRD with Basic Auth and JWT Auth in mind - Determine initial resource specification - Evaluate filter early in request processing, occurring before URLRewrite, header modifiers and backend selection -- Authentication failures returns 401 Unauthorized by default +- Authentication failures return 401 Unauthorized by default - Ensure response codes are configurable ## Non-Goals @@ -25,9 +25,9 @@ This new filter should eventually expose all forms of authentication available t ## Introduction -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 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 follows 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. +This document also focuses 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 @@ -68,7 +68,7 @@ This portion also contains: - Example HTTPRoutes and NGINX configuration 3. Example spec for JWT Auth - Example HTTPRoutes - - Examples for Local & Remote JWKS configration + - Examples for Local & Remote JWKS configuration - Example NGINX configuration for both Local & Remote JWKS - Example of additional optional fields @@ -80,7 +80,7 @@ Below is the Golang API for the `AuthenticationFilter` API: package v1alpha1 import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/nginx/nginx-gateway-fabric/v2/apis/v1alpha1" ) @@ -111,9 +111,9 @@ type AuthenticationFilter struct { // AuthenticationFilterList contains a list of AuthenticationFilter resources. type AuthenticationFilterList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []AuthenticationFilter `json:"items"` + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AuthenticationFilter `json:"items"` } // AuthenticationFilterSpec defines the desired configuration. @@ -151,19 +151,19 @@ const ( // BasicAuth configures HTTP Basic Authentication. type BasicAuth struct { - // SecretRef allows referencing a Secret in the same namespace. - SecretRef LocalObjectReference `json:"secretRef"` + // SecretRef allows referencing a Secret in the same namespace. + SecretRef LocalObjectReference `json:"secretRef"` - // Realm used by NGINX `auth_basic` directive. - // https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html#auth_basic - // Also configures "realm="" in WWW-Authenticate header in error page location. - Realm string `json:"realm"` + // Realm used by NGINX `auth_basic` directive. + // https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html#auth_basic + // Also configures "realm="" in WWW-Authenticate header in error page location. + Realm string `json:"realm"` } // LocalObjectReference specifies a local Kubernetes object. type LocalObjectReference struct { - // Name is the referenced object. - Name string `json:"name"` + // Name is the referenced object. + Name string `json:"name"` } // JWTKeyMode selects where JWT keys come from. @@ -296,9 +296,9 @@ type JWKSCache struct { type JWTType string const ( - JWTTypeSigned JWTType = "signed" - JWTTypeEncrypted JWTType = "encrypted" - JWTTypeNested JWTType = "nested" + JWTTypeSigned JWTType = "signed" + JWTTypeEncrypted JWTType = "encrypted" + JWTTypeNested JWTType = "nested" ) // AuthScheme enumerates supported WWW-Authenticate schemes. @@ -364,20 +364,20 @@ In the case of Basic Auth, the deployed Secret and HTTPRoute may look like this: #### Secret referenced by filter -To create this kind of secreet for Basic Auth first run this command: +To create this kind of secret for Basic Auth first run this command: ```bash htpasswd -c auth user ``` -This will create a file called `auth` with the user name and an MD5 hashes password: +This will create a file called `auth` with the username and an MD5-hashed password: ```bash cat auth user:$apr1$prQ3Bh4t$A6bmTv7VgmemGe5eqR61j0 ``` -Use these options in the `htpasswd` command for stronger hashing algorithims: +Use these options in the `htpasswd` command for stronger hashing algorithms: ```bash -2 Force SHA-256 hashing of the password (secure). @@ -391,7 +391,7 @@ You can then run this command to generate the secret from the `auth` file: kubectl create secret generic basic-auth --from-file=auth ``` -Note: `auth` will be the default key for secrets referenced `AuthenticationFilters` of `Type: Basic`. +Note: `auth` will be the default key for secrets referenced by `AuthenticationFilters` of `Type: Basic`. Example secret: @@ -437,7 +437,7 @@ spec: Note: For Basic Auth, NGF will store the file used by `auth_basic_user_file` in `/etc/nginx/secrets/` The full path will use the `name` and `key` of the secret referenced by `AuthenticationFilter` -In this case, the full path will be `/etc/nginx/secrets/basic-auth-users/htpasswd` +In this case, the full path will be `/etc/nginx/secrets/basic-auth-users/auth` ```nginx http { @@ -475,9 +475,9 @@ http { ### Example spec for JWT Auth -For JWT Auth, there is two options. +For JWT Auth, there are two options. -1. Local JWKS file stored as as a Secret +1. Local JWKS file stored as a Secret 2. Remote JWKS from an IdP provider like Keycloak #### Example JWT AuthenticationFilter with Local JWKS @@ -534,7 +534,7 @@ To create the example secret, run the following command: kubectl create secret generic basic-auth --from-file=jwks.json ``` -Note: `jwks.json` will be the default key for secrets referenced `AuthenticationFilters` of `Type: JWT`. +Note: `jwks.json` will be the default key for secrets referenced by `AuthenticationFilters` of `Type: JWT`. ```yaml apiVersion: v1 @@ -610,7 +610,7 @@ http { # File-based JWKS # Path is generated by NGF using the name and key from the secret - auth_jwt_key_file /etc/nginx/keys/jwt-keys-secure/jwks; + auth_jwt_key_file /etc/nginx/keys/jwt-keys-secure/jwks.json; # Optional: key cache duration auth_jwt_key_cache 10m; @@ -650,8 +650,8 @@ http { 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 `/_ngf-internal_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 subsequent 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 `/_ngf-internal_jwks_uri` to redirect the request to the external auth provider (e.g. Keycloak) +- `proxy_cache_path`: This is used to configure caching of the JWKS after an initial request, allowing subsequent requests to avoid re-authentication for a time ```nginx http { @@ -753,8 +753,8 @@ spec: ### Attachment -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. +Filters must be attached to an HTTPRoute at the `rules.matches` level. +This means that a single `AuthenticationFilter` may be attached multiple times to a single HTTPRoute. #### Basic example @@ -766,10 +766,10 @@ This example shows a single HTTPRoute, with a single `filter` defined in a `rule #### Referencing multiple AuthenticationFilter resources in a single rule -Only a single `AuthenticationFilter` may be referened in a single rule. +Only a single `AuthenticationFilter` may be referenced in a single rule. -The `Status` the HTTPRoute/GRPCRoute in this scenario should be set to `Invalid`, and the resource should be `Rejected`. -In this scenario, the route rule that is referencing multiple `AuthenticationFilter` resources will be `Rejected`/ +The Status of the HTTPRoute/GRPCRoute in this scenario should be set to `Invalid`, and the resource should be `Rejected`. +In this scenario, the route rule that is referencing multiple `AuthenticationFilter` resources will be `Rejected`. All other route rules will remain working. The HTTPRoute/GRPCRoute resource will display an `UnresolvedRef` message to inform the user that the rule has been `Rejected`. @@ -812,7 +812,7 @@ spec: #### Referencing an AuthenticationFilter resource that is invalid Note: With appropriate use of CEL validation, we are less likely to encounter a scenario where an AuthenticationFilter has been deployed to the cluster with an invalid configuration. -If this does happen, and a route rule references this AuthenticationFilter, the route rule will be set to `Invalid` and the the HTTPRoute/GRPCRoute will display the `UnresolvedRef` status. +If this does happen, and a route rule references this AuthenticationFilter, the route rule will be set to `Invalid` and the HTTPRoute/GRPCRoute will display the `UnresolvedRef` status. #### Attaching a JWT AuthenticationFilter to a route when using NGINX OSS @@ -836,13 +836,13 @@ For a filter to be considered "resolved", it must: Invalid resolved filter scenarios: - Resolved filter that references a secret that does not exist -- Resolved filter that referenced a secret with the incorrect data key +- Resolved filter that references a secret with the incorrect data key Valid reference scenarios: - Resolved filter referenced by a single route rule within a single HTTP/GRPCRoute - Resolved filter referenced by multiple route rules within a single HTTP/GRPCRoute -- Resolved filter reference by multiple HTTP/GRPCRoutes +- Resolved filter referenced by multiple HTTP/GRPCRoutes Invalid reference scenarios: @@ -871,12 +871,12 @@ Proxy cache TTL should be configurable and set to a reasonable default, reducing ### Key rotation -Users should be advised to regularly rotate their JWKS keys in cases where they chose to reference a local JWKS via a `secrefRef` +Users should be advised to regularly rotate their JWKS keys in cases where they choose to reference a local JWKS via a `secretRef`. ### Optional headers -Below are a list of optional defensive headers that user's may choose to include. -In certain scenarios, these headers may be deployed to improve overall security from client reponses. +Below are a list of optional defensive headers that users may choose to include. +In certain scenarios, these headers may be deployed to improve overall security from client responses. ```nginx add_header Content-Type "text/plain; charset=utf-8" always; @@ -887,23 +887,23 @@ add_header Cache-Control "no-store" always; Detailed header breakdown: - Content-Type: "text/plain; charset=utf-8" - - 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 + - This header explicitly sets the body as plain text. This prevents browsers from treating the response as HTML or JavaScript, and is effective at mitigating Cross-site scripting (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. + - This header prevents content type confusion. This occurs when browsers guess HTML and JavaScript, and execute 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. + - This header informs browsers and proxies not to cache the response. Avoids sensitive, auth-related content from being stored and served later to unintended recipients. ### 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 an 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. +All fields in the `AuthenticationFilter` will be validated with OpenAPI 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 rule should be `Rejected`. +We should validate 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 rule should be `Rejected`. This scenario can use the status `RouteConditionPartiallyInvalid` defined in the Gateway API here: https://github.com/nginx/nginx-gateway-fabric/blob/3934c5c8c60b5aea91be4337d63d4e1d8640baa8/internal/controller/state/conditions/conditions.go#L402 @@ -911,18 +911,18 @@ This scenario can use the status `RouteConditionPartiallyInvalid` defined in the 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. +This allows users to reference an external authentication service, 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 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. +It is certainly possible for us to provide an External Authentication Service that leverages NGINX and is something we can further investigate as the API progresses. ## Additional considerations -### Documenting filter behavour +### Documenting filter behavior -In regards to documentation of filter behaviour with the `AuthenticationFilter`, the Gateway API documentation on filters states the following: +In regards to documentation of filter behavior 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. @@ -935,10 +935,10 @@ document that behavior. ## Stretch Goals -### Custom authentiation failure response +### Custom authentication failure response By default, authentication failures return a 401 response. -If a used wanted to change this response code, or include additioanl headers in this response, we can include a custom named location that can be called by the [error_page](https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page) directive. +If a user wanted to change this response code, or include additional headers in this response, we can include a custom named location that can be called by the [error_page](https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page) directive. Example AuthenticationFilter configuration: @@ -977,7 +977,7 @@ server{ } ``` -If we support this configuration, 3xx response codes should not be allowed and AuthenticationFilter.onFailure must not support redirect targets. This is to prevent to prevent open-redirect abuse. +If we support this configuration, 3xx response codes should not be allowed and AuthenticationFilter.onFailure must not support redirect targets. This is to prevent open-redirect abuse. We should only allow 401 and 403 response codes. @@ -985,7 +985,7 @@ We should only allow 401 and 403 response codes. When referencing secrets for Basic Auth and JWT Auth, the initial implementation will use `LocalObjectReference`. -Future updates to this will use the `NamespacedSecretKeyReference` in conjunction with `ReferenceGrants` to support access to secrets in different namespace` +Future updates to this will use the `NamespacedSecretKeyReference` in conjunction with `ReferenceGrants` to support access to secrets in different namespaces. Struct for `NamespacedSecretKeyReference`: @@ -1001,9 +1001,8 @@ type NamespacedSecretKeyReference struct { } ``` -For initial implementaion, both Basic Auth and Local JWKS should will only have access to Secrets in the same namespace. +For the initial implementation, both Basic Auth and Local JWKS will only have access to Secrets in the same namespace. -Example: Grant BasicAuth in app-ns to read a Secret in security-ns Example: Grant BasicAuth in app-ns to read a Secret in security-ns ```yaml @@ -1023,7 +1022,6 @@ spec: name: basic-auth-users ``` -AuthenticationFilter referencing the cross-namespace Secret AuthenticationFilter referencing the cross-namespace Secret ```yaml @@ -1043,10 +1041,9 @@ spec: ### Additional Fields for JWT -`require`, `tokenSource` and `propagation` are some additional fields that may be incldued in future updates to the API. +`require`, `tokenSource` and `propagation` are some additional fields that may be included in future updates to the API. These fields allow for more customization of how the JWT auth behaves, but aren't required for the minimal delivery of JWT Auth. -Example of what implementation of these fields might look like: Example of what implementation of these fields might look like: ```yaml @@ -1073,7 +1070,7 @@ spec: - "cli" # Where client presents the token - # By defaults to reading from Authorization header (Bearer) + # By default, reading from Authorization header (Bearer) tokenSource: type: Header # Alternative: read from a cookie named tokenName @@ -1194,7 +1191,7 @@ type HeaderValue struct { - [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) +- [Kubernetes documentation on CEL validation](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 bc31f0d3d2e81c5ed1a68133a8d637e0016b7932 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 10:10:08 +0000 Subject: [PATCH 06/10] Fix reference to KeyCloak --- docs/proposals/authentication-filter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index f618865dcf..8dfbf6884e 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -478,7 +478,7 @@ http { For JWT Auth, there are two options. 1. Local JWKS file stored as a Secret -2. Remote JWKS from an IdP provider like Keycloak +2. Remote JWKS from an IdP provider like KeyCloak #### Example JWT AuthenticationFilter with Local JWKS @@ -650,7 +650,7 @@ http { 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 `/_ngf-internal_jwks_uri` to redirect the request to the external auth provider (e.g. Keycloak) +- `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 `/_ngf-internal_jwks_uri` to redirect the request to the external auth provider (e.g. KeyCloak) - `proxy_cache_path`: This is used to configure caching of the JWKS after an initial request, allowing subsequent requests to avoid re-authentication for a time ```nginx @@ -911,7 +911,7 @@ This scenario can use the status `RouteConditionPartiallyInvalid` defined in the 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 service, such as Keycloak, to handle the authentication requests. +This allows users to reference an external authentication service, 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 quickly provide authentication to our users while allowing us to closely monitor progress of the ExternalAuthFilter. From 32c609bff347d634ddf8e7c9faf2758197c7aeee Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Tue, 9 Dec 2025 13:32:26 +0000 Subject: [PATCH 07/10] Add expected outcomes for each tests case --- docs/proposals/authentication-filter.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 8dfbf6884e..6f84f302f9 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -836,21 +836,29 @@ For a filter to be considered "resolved", it must: Invalid resolved filter scenarios: - Resolved filter that references a secret that does not exist + - Expected outcome: Filter is marked as Invalid. The route rule that references this filter is also marked as Invalid - Resolved filter that references a secret with the incorrect data key + - Expected outcome: Filter is marked as Invalid. The route rule that references this filter is also marked as Invalid Valid reference scenarios: - Resolved filter referenced by a single route rule within a single HTTP/GRPCRoute + - Expected outcome: Requests to this route rule will successfully process authentication requests - Resolved filter referenced by multiple route rules within a single HTTP/GRPCRoute -- Resolved filter referenced by multiple HTTP/GRPCRoutes + - Expected outcome: Requests to all route rules referencing the filter successfully process authentication requests +- Resolved filter referenced by rules in multiple HTTP/GRPCRoutes + - Expected outcome: Requests to all route rules across each HTTP/GRPCRoute successfully process authentication requests Invalid reference scenarios: - Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute -- Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute + - Expected outcome: The route rule referencing multiple filters will be marked as Invalid - Resolved filter referenced multiple times by multiple route rules within a single HTTP/GRPCRoute + - Expected outcome: Each route rule referencing multiple filters will be marked as Invalid - Unresolved filter referenced by a single route rule within a single HTTP/GRPCRoute + - Expected outcome: The route rule referencing multiple filters will be marked as Invalid - Unresolved filter referenced by multiple route rules within a single HTTP/GRPCRoute + - Expected outcome: Each route rule referencing multiple filters will be marked as Invalid ## Security Considerations From c90f2acb329bc10a0e6f1a43559d49739cb97894 Mon Sep 17 00:00:00 2001 From: Shaun Date: Tue, 9 Dec 2025 18:23:03 +0000 Subject: [PATCH 08/10] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/proposals/authentication-filter.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 6f84f302f9..45d4d9d4f2 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -478,7 +478,7 @@ http { For JWT Auth, there are two options. 1. Local JWKS file stored as a Secret -2. Remote JWKS from an IdP provider like KeyCloak +2. Remote JWKS from an IdP provider like Keycloak #### Example JWT AuthenticationFilter with Local JWKS @@ -919,7 +919,7 @@ This scenario can use the status `RouteConditionPartiallyInvalid` defined in the 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 service, such as KeyCloak, to handle the authentication requests. +This allows users to reference an external authentication service, 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 quickly provide authentication to our users while allowing us to closely monitor progress of the ExternalAuthFilter. @@ -1030,7 +1030,7 @@ spec: name: basic-auth-users ``` -AuthenticationFilter referencing the cross-namespace Secret + ```yaml apiVersion: gateway.nginx.org/v1alpha1 From 3bb040e5caed04154a1a702d1adbe78b20760412 Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Wed, 10 Dec 2025 17:50:24 +0000 Subject: [PATCH 09/10] Mention testing of correct NGINX configuration --- docs/proposals/authentication-filter.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index 45d4d9d4f2..e74286c37f 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -827,12 +827,14 @@ This can use the status `RouteConditionPartiallyInvalid` defined in the Gateway ### Functional Test Cases -Note: The keyword "resolved" is used to refer to a filter that the controller has found, and matches the reference of the route rule. +The keyword "resolved" is used to refer to a filter that the controller has found, and matches the reference of the route rule. For a filter to be considered "resolved", it must: 1. Exist in the same namespace as the HTTP/GRPCRoute 2. The group and kind referenced must match +For each test, we aim to ensure the NGINX configuration is always correct for each scenario. + Invalid resolved filter scenarios: - Resolved filter that references a secret that does not exist From 137b22d47799a7b5e690f4878f1eaf943bf0ef5c Mon Sep 17 00:00:00 2001 From: shaun-nx Date: Fri, 12 Dec 2025 14:50:22 +0000 Subject: [PATCH 10/10] Update functional test cases --- docs/proposals/authentication-filter.md | 57 +++++++++++++------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/docs/proposals/authentication-filter.md b/docs/proposals/authentication-filter.md index e74286c37f..9cdf8e5264 100644 --- a/docs/proposals/authentication-filter.md +++ b/docs/proposals/authentication-filter.md @@ -825,42 +825,45 @@ This can use the status `RouteConditionPartiallyInvalid` defined in the Gateway - Unit tests - Functional tests to validate behavioural scenarios when referencing filters in different combinations. -### Functional Test Cases +## Functional Test Cases -The keyword "resolved" is used to refer to a filter that the controller has found, and matches the reference of the route rule. -For a filter to be considered "resolved", it must: +### Valid scenarios -1. Exist in the same namespace as the HTTP/GRPCRoute -2. The group and kind referenced must match +This sections covers deployment scenarios that are considered valid -For each test, we aim to ensure the NGINX configuration is always correct for each scenario. +- Single route rule with a single path in an HTTPRoute/GRPCRoute referencing a valid AuthenticationFilter +- Single route rule with two or more paths in an HTTPRoute/GRPCRoute referencing a valid AuthenticationFilter +- Two or more route rules each with a single path in an HTTPRoute/GRPCRoute referencing a valid AuthenticationFilter +- Two or more route rules each with two or more paths in an HTTPRoute/GRPCRoute referencing a valid AuthenticationFilter +- Two or more HTTPRoute/GRPCRoute resource each with single route rule with a single path referencing a valid AuthenticationFilter. +- Two or more HTTPRoute/GRPCRoute resource each with single route rule, each with two or more paths referencing a valid AuthenticationFilter. +- Two or more HTTPRoute/GRPCRoute resource each with two or more route rules each with a single path referencing a valid AuthenticationFilter. -Invalid resolved filter scenarios: +### Invalid scenarios -- Resolved filter that references a secret that does not exist - - Expected outcome: Filter is marked as Invalid. The route rule that references this filter is also marked as Invalid -- Resolved filter that references a secret with the incorrect data key - - Expected outcome: Filter is marked as Invalid. The route rule that references this filter is also marked as Invalid +This sections covers deployment scenarios that are considered valid -Valid reference scenarios: +- Single route rule with a single path in an HTTPRoute/GRPCRoute referencing an invalid AuthenticationFilter +- Single route rule with two or more paths in an HTTPRoute/GRPCRoute referencing an invalid AuthenticationFilter +- Two or more route rules each with a single path in an HTTPRoute/GRPCRoute referencing an invalid AuthenticationFilter +- Two or more route rules each with two or more paths in an HTTPRoute/GRPCRoute referencing an invalid AuthenticationFilter +- Two or more HTTPRoute/GRPCRoute resource each with single route rule with a single path referencing an invalid AuthenticationFilter. +- Two or more HTTPRoute/GRPCRoute resource each with single route rule, each with two or more paths referencing an invalid AuthenticationFilter. +- Two or more HTTPRoute/GRPCRoute resource each with two or more route rules each with a single path referencing an invalid AuthenticationFilter. +- Two or more route rules each with a single path in an HTTPRoute/GRPCRoute, where one rule references a valid AuthenticationFilter, and the other references an invalid AuthenticationFilter. +- Two or more route rules each with two or more paths in an HTTPRoute/GRPCRoute where one rule references a valid AuthenticationFilter, and the other references an invalid AuthenticationFilter. +- Two or more valid or invalid AuthenticationFilters referenced in a route rule. -- Resolved filter referenced by a single route rule within a single HTTP/GRPCRoute - - Expected outcome: Requests to this route rule will successfully process authentication requests -- Resolved filter referenced by multiple route rules within a single HTTP/GRPCRoute - - Expected outcome: Requests to all route rules referencing the filter successfully process authentication requests -- Resolved filter referenced by rules in multiple HTTP/GRPCRoutes - - Expected outcome: Requests to all route rules across each HTTP/GRPCRoute successfully process authentication requests +### Invalid AuthenticationFilter scenarios -Invalid reference scenarios: +This section covers configuation scenarios for an AuthenticationFilter resource that would be considered invalid -- Resolved filter referenced multiple times in a single route rule within a single HTTP/GRPCRoute - - Expected outcome: The route rule referencing multiple filters will be marked as Invalid -- Resolved filter referenced multiple times by multiple route rules within a single HTTP/GRPCRoute - - Expected outcome: Each route rule referencing multiple filters will be marked as Invalid -- Unresolved filter referenced by a single route rule within a single HTTP/GRPCRoute - - Expected outcome: The route rule referencing multiple filters will be marked as Invalid -- Unresolved filter referenced by multiple route rules within a single HTTP/GRPCRoute - - Expected outcome: Each route rule referencing multiple filters will be marked as Invalid +- An AuthenticationFilter deployed with an empty `Realm` value +- An AuthenticationFilter deployed with an empty `secretRef.Name` value +- An AuthenticationFilter referencing a secret that does not exist +- An AuthenticationFilter referencing a secret in a different namespace +- An AuthenticationFilter referencing a secret with an incorrect type (e.g Opaque) +- An AuthenticationFilter referencing a secret with an incorrect keyd ## Security Considerations