-
Notifications
You must be signed in to change notification settings - Fork 142
Enhancement Proposal: Authentication Filter #4235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4235 +/- ##
==========================================
- Coverage 86.08% 86.06% -0.03%
==========================================
Files 132 132
Lines 14342 14342
Branches 35 35
==========================================
- Hits 12347 12344 -3
- Misses 1791 1793 +2
- Partials 204 205 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| type JWTTokenSource struct { | ||
| // Read token from Authorization header. Default: true. | ||
| // | ||
| // +optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set defaults using kubebuilder: default
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is an optional field though, it shouldn't have a default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a blanket statement? In the case of JWTTokenType, that is defined as an optional field. (i.e. it's optional for the user to set it) but we default to assigning the value to signed (e.g. auth_jwt_type signed;)
In the NGINX documentation, this directive defaults to signed when the directive is not specified.
So in my mind, making is optional for the user to specify this field, and providing a default value for it makes sense to me.
What are your thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @sjberman
I did a review of the current optional fields with defaults. From what I can see, they should stay with those defaults. Curious what you think though.
These are:
// +optional
// +kubebuilder:default="Restricted"
Realm *string `json:"realm,omitempty"`
// +optional
// +kubebuilder:default=60s
Leeway *v1alpha1.Duration `json:"leeway,omitempty"`
// +optional
// +kubebuilder:default=signed
Type *JWTTokenType `json:"type,omitempty"`
// +optional
// +kubebuilder:default=access_token
TokenName string `json:"tokenName,omitempty"`
// +optional
// +kubebuilder:default=401
StatusCode *int32 `json:"statusCode,omitempty"`
// +optional
// +kubebuilder:default=Basic
Scheme *AuthScheme `json:"scheme,omitempty"`
// +optional
// +kubebuilder:default=Unauthorized
BodyPolicy *AuthFailureBodyPolicy `json:"bodyPolicy,omitempty"`
ciarams87
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are mostly focussed on typos I spotted while doing a first pass, I haven't fully absorbed the content yet
| 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, this proposed AuthenticationFilter is to supply different auth methods than provided in the ExternalAuth filter, and is technically unrelated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sjberman that's not entirely true. If we went forward with ExternalAuth filter, we could build out an external auth service that uses NGINX. It wouldn't prevent us from supplying those auth methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that is unrelated to this AuthenticationFilter. This filter is to configure our NGINX data plane to perform native auth, without the need for an external entity. The ExternalAuthFilter is for just using an external entity for auth, whatever that entity may be (it wouldn't be our direct NGINX data plane, but it could be an external NGINX instance).
These are two different solutions for implementing auth for the user, and are not technically related to each other. I only mention this because this sentence in the doc implies that this Filter is being implemented as a workaround to the ExternalAuth filter, but it's really not, it's a different auth solution. Just being pedantic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sjberman do you think this statement needs to be changed in any way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine
sjberman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll do a more thorough review once I'm back online full time.
| 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, this proposed AuthenticationFilter is to supply different auth methods than provided in the ExternalAuth filter, and is technically unrelated.
| // Configures "realm="<realm_value>" in WWW-Authenticate header in error page location. | ||
| // | ||
| // +optional | ||
| // +kubebuilder:default="Restricted" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realm has a default here but not in Basic Auth. Is that ok? And does it need to be capitalized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. It doesn't need to be capitalized. This is mostly just for error logging from auth requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we lowercase it then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We certainly can. The example in the nginx basic auth and jwt auth docs show the realm set as "closed site" so there really isn't any standard default for this.
This mainly is used for things like credential caching. It uses scheme + origin + realm as the cache key.
As I think about it more, it might be safer to leave this as an empty string by default so users don't get confused by behaviour.
What do you think?
| // Example: "auth_jwt_leeway 60s". | ||
| // | ||
| // +optional | ||
| // +kubebuilder:default=60s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this the nginx default? If so, I don't think it's needed in here. Same for any other fields that may already be nginx defaults. Up until now we haven't set nginx defaults directly in the policy, because if they ever change, then we have to update our policy too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thanks for catching that. For auth_jwt_leeway it defaults to 0s, so we can leave the default out of this one.
I'll make that same adjustment for the other fields. From what I ready up, 60s is a recommended setting in most scenarios when adjusting for clock skew. We can just document that though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been changed now. The comment shows +kubebuilder:default=0s to reflect the NGINX docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't want a default, then we don't need that comment at all. just let nginx define it, we don't need to set it in the policy
| Realm *string `json:"realm,omitempty"` | ||
|
|
||
| // Mode selects how JWT keys are provided: local file or remote JWKS. | ||
| Mode JWTKeyMode `json:"mode,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should probably be named KeyMode to be more specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, when I commented this I meant the field name, not the type. The type can stay as JWTKeyMode
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with the other fields that I left similar comments on.
| // Required when Mode == File. | ||
| // | ||
| // +optional | ||
| File *JWTFileKeySource `json:"file,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd name this FileKeySource, since File by itself isn't really clear
| // Required when Mode == Remote. | ||
| // | ||
| // +optional | ||
| Remote *JWTRemoteKeySource `json:"remote,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RemoteKeySource for same reasons
| // | ||
| // +optional | ||
| // +kubebuilder:default=signed | ||
| Type *JWTTokenType `json:"type,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TokenType is more specific
| // Defaults to reading from Authorization header. | ||
| // | ||
| // +optional | ||
| TokenSource *JWTTokenSource `json:"tokenSource,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any field types that have the name JWTToken in them should just be JWT, since the T in JWT stands for Token.
| // KeyMode selects where JWT keys come from. | ||
| // +kubebuilder:validation:Enum=File;Remote | ||
| type JWTKeyMode string | ||
| type KeyMode string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not opposed to having JWT in the names of these fields. It makes it clear that they are for JWT. My only gripe was having JWTToken, which is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I looked over it, while KeyMode was only related to JWT right now, the NGINX OIDC Module lets you specify a local secret, as well as a remote URL. So this could eventually be re-used when we implement OIDC auth
That being said, I'll look back over it and see if other fields where JWT was removed could still keep it.
| // Configures "realm="<realm_value>" in WWW-Authenticate header in error page location. | ||
| // | ||
| // +optional | ||
| // +kubebuilder:default="Restricted" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we lowercase it then?
|
Being that JWT is an NGINX Plus feature, we should also outline that fact, and what the behavior would be if an OSS user attemps to specify it (probably reject it, make the route as invalid). |
Good point. I'll add that to the |
We should mark the rule invalid, but not the entire route. Other rules that don't use the filter can still work as intended, but this rule should not. |
Sounds good. I'll make a note of that. Thanks @sjberman 👍 |
|
|
||
| #### Attaching a JWT AuthenticationFilter to a route when using NGINX OSS | ||
|
|
||
| If a user attempts to attach a JWT tpye AuthenticationFilter while using NGINX OSS, the rule referncing the filter will be `Rejected`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| If a user attempts to attach a JWT tpye AuthenticationFilter while using NGINX OSS, the rule referncing the filter will be `Rejected`. | |
| If a user attempts to attach a JWT type AuthenticationFilter while using NGINX OSS, the rule referencing the filter will be `Rejected`. |
|
|
||
| If a user attempts to attach a JWT tpye AuthenticationFilter while using NGINX OSS, the rule referncing the filter will be `Rejected`. | ||
|
|
||
| This can appear as `UnresolvedRef` to inform the user that the rule has been `Rejected`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is Invalid an option? UnresolvedRef generally means "we couldn't find this resource".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to use the ResolvedRefs condition on the Route to show an error message but condition should still be true to not mark the route invalid. You said you will just remove the route rule to handle the error. Similar to this.
|
|
||
| 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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and the route rule referencing the extensionRef should be rejected. Resource makes it sound like the route would be rejected.
salonichf5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just one small edit. Lgtm
| type FileKeySource struct { | ||
| // SecretRef references a Secret containing the JWKS. | ||
| SecretRef SecretObjectReference `json:"secretRef,omitempty"` | ||
| SecretRef LocalObjectReferenceWithKey `json:"secretRef,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not use SecretRef as the name either, for flexibility.
sjberman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some nits
| ### Additional Fields for JWT | ||
| `require`, `tokenSource` and `propagation` are some additional fields that may be incldued in future updates to the API. | ||
| These fields allow for more customization of how the JWT auth behavtes, but aren't required for the minial delivery of JWT Auth. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| These fields allow for more customization of how the JWT auth behavtes, but aren't required for the minial delivery of JWT Auth. | |
| These fields allow for more customization of how the JWT auth behaves, but aren't required for the minimal delivery of JWT Auth. |
| Example GoLang API changes: | ||
| ```go |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Example GoLang API changes: | |
| ```go | |
| Example GoLang API changes: | |
| ```go |
| Example: Grant BasicAuth in app-ns to read a Secret in security-ns | ||
| ```yaml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Example: Grant BasicAuth in app-ns to read a Secret in security-ns | |
| ```yaml | |
| Example: Grant BasicAuth in app-ns to read a Secret in security-ns | |
| ```yaml |
| AuthenticationFilter referencing the cross-namespace Secret | ||
| ```yaml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| AuthenticationFilter referencing the cross-namespace Secret | |
| ```yaml | |
| AuthenticationFilter referencing the cross-namespace Secret | |
| ```yaml |
| Example of what implementation of these fields might look like: | ||
| ```yaml |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Example of what implementation of these fields might look like: | |
| ```yaml | |
| Example of what implementation of these fields might look like: | |
| ```yaml |
|
|
||
| This example shows a single HTTPRoute, with a single `filter` defined in a `rule` | ||
|
|
||
|  |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This image is probably outdated a bit for the field names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It may not even be that necessary to have, up to you.
| // with required `key` field to extract data. | ||
| type LocalObjectReferenceWithKey struct { | ||
| v1.LocalObjectReference | ||
| Key string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we set a default key that we can use in our docs so users don't have to specify this unless they explicitly change it.
Proposed changes
Status set to Implementable after merging #4136
This document proposes a solution for enabling Authentication use cases through NGINX Gateway Fabric.
Closes #4052
Checklist
Before creating a PR, run through this checklist and mark each as complete.
Release notes
If this PR introduces a change that affects users and needs to be mentioned in the release notes,
please add a brief note that summarizes the change.