-
Notifications
You must be signed in to change notification settings - Fork 154
Add support for Basic Auth through AuthenticationFilter #4436
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: feat/authentication-filter-basic-auth
Are you sure you want to change the base?
Conversation
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.
Pull request overview
This PR implements support for Basic Authentication through a new AuthenticationFilter CRD. Routes (both HTTPRoute and GRPCRoute) can now reference an AuthenticationFilter to require Basic Auth credentials on requests. The implementation includes validation of the filter configuration, secret references, and NGINX configuration generation to enforce authentication at the location level.
Key Changes
- Introduces the
AuthenticationFilterCRD for configuring Basic Auth via secret references - Extends HTTPRoute and GRPCRoute processing to support the new filter type
- Adds NGINX configuration generation for
auth_basicdirectives and user file management
Reviewed changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| apis/v1alpha1/authenticationfilter_types.go | Adds constant for Basic Auth secret key |
| apis/v1alpha1/register.go | Registers AuthenticationFilter types with the scheme |
| internal/framework/kinds/kinds.go | Adds AuthenticationFilter kind constant |
| internal/controller/state/graph/authentication_filter.go | Implements validation and processing logic for AuthenticationFilters |
| internal/controller/state/graph/secret.go | Updates secret validation to accept Opaque secrets with "auth" key |
| internal/controller/state/graph/extension_ref_filter.go | Adds AuthenticationFilter support to extension ref validation |
| internal/controller/state/graph/common_filter.go | Updates filter resolution to use map-based resolver lookup |
| internal/controller/state/graph/httproute.go | Threads AuthenticationFilter support through HTTP route processing |
| internal/controller/state/graph/grpcroute.go | Threads AuthenticationFilter support through GRPC route processing |
| internal/controller/state/graph/route_common.go | Updates route building to accept AuthenticationFilters |
| internal/controller/state/graph/graph.go | Adds AuthenticationFilters to cluster state and graph |
| internal/controller/state/dataplane/types.go | Defines AuthenticationFilter and BasicAuth dataplane types |
| internal/controller/state/dataplane/convert.go | Implements conversion from graph to dataplane representation |
| internal/controller/state/dataplane/configuration.go | Threads authentication filters through configuration building |
| internal/controller/state/conditions/conditions.go | Adds condition constructors for AuthenticationFilter status |
| internal/controller/state/status/status_setters.go | Implements status setter for AuthenticationFilter |
| internal/controller/state/status/prepare_requests.go | Prepares status update requests for AuthenticationFilters |
| internal/controller/state/change_processor.go | Adds AuthenticationFilter to change processing |
| internal/controller/handler.go | Updates status handling to include AuthenticationFilters |
| internal/controller/manager.go | Registers AuthenticationFilter controller and initial sync |
| internal/controller/nginx/config/servers.go | Implements NGINX configuration for Basic Auth |
| internal/controller/nginx/config/servers_template.go | Adds template directives for auth_basic |
| internal/controller/nginx/config/http/config.go | Adds AuthBasic configuration types |
| internal/controller/nginx/config/generator.go | Generates auth user files for Basic Auth |
| charts/nginx-gateway-fabric/templates/clusterrole.yaml | Adds RBAC permissions for AuthenticationFilter resources |
| Test files | Updates tests to pass nil for new AuthenticationFilter parameters |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| ) | ||
|
|
||
| const ( | ||
| AuthKeyBasic = "auth" // Key in the Secret data for Basic Auth credentials. |
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 add a properly formatted comment above the field. And why "Basic" in the name?
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.
Sounds good. I chose the word AuthKeyBasic so we can distinguish between the key for JWT when we introduce that. So that new key might be set to `AuthKeyJWT = "jwks.json"
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 though we decided we were just going to use auth as the generic field name for both Basic and JWT?
The JWT is going to be base64 encoded anyway, and technically not a JSON format in the Secret itself.
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 don't think that's something we agreed on. The proposal has the key for JWT auth to be jwks.json.
As I think about it, since now we're defining specific secret types for both Basic and JWT, we could keep auth as the same key for both.
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.
Overall looks good! Just a few new comments, and a couple of old ones that haven't been addressed yet.
| func generateAuthBasicUserFileID(id string) AuthBasicUserFileID { | ||
| return AuthBasicUserFileID(id) | ||
| } |
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.
Why is this wrapper needed? Maybe it would make more sense to pass the namespace/name here so you don't have to format it elsewhere.
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 I remember, I did this to follow the same pattern that we had for the CertBundle. It's likely that there's more "wrapping" happening here than is needed. I'll see if I can make this more simple.
| userFilePathAndName := fmt.Sprintf( | ||
| "%s/%s_%s", | ||
| secretsFolder, | ||
| authenticationFilter.Basic.SecretNamespace, | ||
| authenticationFilter.Basic.SecretName, |
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.
You're building this path/ID in a few different places, in a few different ways. We should use shared logic so they don't have the risk of drifting.
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.
Glad you pointed this out. I was feeling the same way about it, but I couldn't think of a clean way to do this. I'll re-think this and see if we can make this more generic.
| }, | ||
| }, | ||
| expectErrCount: 0, | ||
| name: "valid AuthenticationFilter HTTP extension ref filter", |
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.
Can we add a test case for multiple auth filters being invalid?
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.
yeah a mixed combination of invalid & valid would be nice to have
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.
Can we add a test case for multiple auth filters being invalid?
I have a test case for that covered in http/grpc test files. I'll see if I can do the same in here. Thanks for catching that!
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 tests seem to be structured to validate a filter independently, and not the interaction with other filters.
There are four tests cases in httproute_test.go that cover these mixed scenarios: https://github.com/nginx/nginx-gateway-fabric/pull/4436/changes#diff-dcec4792f1cac5c101cfdfe57fd3dc12325b6ae336ab7d7b8d8e4385627832c5R1207-R1415
These are also covered in grpcroute_test.go.
These cover:
- Invalid + Unresolved
- Valid + Invalid
- Valid + Unresolved
- Valid + Valid
This seems like the more appropriate place to cover these since we can also verify the error messages we get back.
| Source: secret, | ||
| }, | ||
| err: validationErr, | ||
| } |
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 we can reduce a bit of duplication in this function.
The common pieces could all be done at the end. It looks like the certBundle field is the only unique part, so that could be the one difference.
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.
Sounds good. I'll see what I can do.
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.
You pointed out correctly here that it doesn't make sense for the Basic Auth case.
The way this code was previously looks to be the only way to accomplish this.
It would just mean the value for CertBundle would be nil for any situation that isn't the TLS case.
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.
Looks to be in good shape. With a couple of minor nits addressed and Saylor’s comments addressed, it should be ready to go.
| }, | ||
| }, | ||
| expectErrCount: 0, | ||
| name: "valid AuthenticationFilter HTTP extension ref filter", |
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.
yeah a mixed combination of invalid & valid would be nice to have
| name: "rule with valid snippets filter extension ref filter", | ||
| }, | ||
| { | ||
| validator: validatorInvalidFieldsInRule, |
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 be using &validationfakes.FakeHTTPFieldsValidator{} here since it is a valid case?
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, I didn't notice this.
I templated this test from the "rule with valid snippets filter extension ref filter" test, which is also using validatorInvalidFieldsInRule. I wonder if this was just a mistake. It also makes me think how that works...
I'll look into this more.
| return filepath.Join(secretsFolder, string(id)+".crt") | ||
| } | ||
|
|
||
| func generateAuthBasicUserFile(id dataplane.AuthBasicUserFileID, data []byte) agent.File { |
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, why user?
| {{- if $l.AuthBasic }} | ||
| auth_basic "{{ $l.AuthBasic.Realm }}"; | ||
| auth_basic_user_file {{ $l.AuthBasic.UserFile }}; |
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.
why user?
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 call used it UserFile to keep it consistent with the name of the directive,
So the struct is AuthBasic.UserFile and the directive is auth_basic_user_file
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.
yes, but my question started from UserFile, since it is not created by user. Same we need to call UserProxySettings 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.
This has been changed now. So should just be AuthBasic.File
| Message: "The AuthenticationFilter is accepted", | ||
| } | ||
| } | ||
|
|
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.
Can be partially Invalid/Accepted in any case?
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 don't think there is a scenario where the filter itself can be partially invalid.
bjee19
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.
nice job! a couple of outstanding comments from others, but this looks pretty good to me, ill approve after other comments are resolved
| Message: "The SnippetsFilter is accepted", | ||
| } | ||
| } | ||
|
|
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 chance Programmed condition is also related to AuthFilter?
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 a really good point actually.
From what I read up, we set the Programmed status when "the resource or part thereof has been Accepted and programmed into the underlying dataplane. Users should expect the configuration to be ready for traffic to flow at some point in the near future".
We set this status for the Gateway resource when the NGINX configuration is loaded and ready to accept traffic. So it makes sense in that scenario.
If we set Programmed on the AuthenticationFilter, we would be telling the user that "Yes, this filter has programmed the underlying dataplane".
I'd love to know what everyone thinks about this. @salonichf5 @sjberman @bjee19
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.
Are we sure that Programmed isn't just meant for policies? This is technically a filter, not a policy, so the status conditions are potentially treated differently.
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.
Yea I'm pretty sure we'll only set Programmed for policies.
Which are described here https://gateway-api.sigs.k8s.io/geps/gep-713/#status-conditions.
And is described there as
// PolicyConditionProgrammed indicated whether the policy's spec is guaranteed by the controller to
// be fully programmed for enforcement.
If we were to add in the programmed condition, I think that would affect all other filters, since they affect the underlying dataplane, and that might be a precedence we don't want to start.
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.
Filters may not be defined yet in this aspect.
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.
Pull request overview
Copilot reviewed 50 out of 50 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| --image=$(CONFORMANCE_PREFIX):$(CONFORMANCE_TAG) --image-pull-policy=Never \ | ||
| --overrides='{ "spec": { "serviceAccountName": "conformance" } }' \ | ||
| --restart=Never -- sh -c "go test -v . -tags conformance,experimental -args --gateway-class=$(GATEWAY_CLASS) \ | ||
| --restart=Never -- sh -c "go test -v -timeout 45m . -tags conformance,experimental -args --gateway-class=$(GATEWAY_CLASS) \ |
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.
Why was this needed? I'd like to understand the culprit if we can. These tests should never take this long, they're usually done in just a couple of minutes.
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 more added this to see if it had an affect. I hadn't planned on keeping it there. It was strange though the timeout issue happened more often for me locally than it did in the pipeline.
Proposed changes
This change adds support for Basic Authentication using the new AuthenticationFilter CRD.
For both HTTPRoute and GRPCRoute resource, requests made to route rules that reference an
AuthenticationFilterusingtype: Basicwill require credentials sent as part of the request.Example HTTP requests:
Example GRPC requests:
Closes #4312
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.