Skip to content

Conversation

@shaun-nx
Copy link
Contributor

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 AuthenticationFilter using type: Basic will require credentials sent as part of the request.

Example HTTP requests:

# With credentials provided
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee1 -u user1:password1
Server address: 10.244.0.18:8080
Server name: coffee-5b9c74f9d9-bf6bl
Date: 10/Dec/2025:14:32:31 +0000
URI: /coffee1
Request ID: 1dab2e14a2893e71f9a5a4992b477adc

# Without credentials provided
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee1
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>

Example GRPC requests:

# With credentials provided
grpcurl -plaintext -proto grpc.proto -authority bar.com  -H "Authorization: Basic dXNlcjE6cGFzc3dvcmQx" -d '{"name": "exact"}' ${GW_IP}:${GW_PORT} helloworld.Greeter/SayHello
{
  "message": "Hello exact"
}

# Without credential provided
grpcurl -plaintext -proto grpc.proto -authority bar.com -d '{"name": "exact"}' ${GW_IP}:${GW_PORT} helloworld.Greeter/SayHello
ERROR:
  Code: Unknown
  Message: unexpected HTTP status code received from server: 204 (No Content)

Closes #4312

Checklist

Before creating a PR, run through this checklist and mark each as complete.

  • I have read the CONTRIBUTING doc
  • I have added tests that prove my fix is effective or that my feature works
  • I have checked that all unit tests pass after adding my changes
  • I have updated necessary documentation
  • I have rebased my branch onto main
  • I will ensure my PR is targeting the main branch and pulling from my branch from my own fork

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.

Support Basic Auth through AuthenticationFilter

@github-actions github-actions bot added enhancement New feature or request helm-chart Relates to helm chart labels Dec 10, 2025
@shaun-nx shaun-nx requested a review from Copilot December 10, 2025 15:44
Copy link

Copilot AI left a 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 AuthenticationFilter CRD for configuring Basic Auth via secret references
  • Extends HTTPRoute and GRPCRoute processing to support the new filter type
  • Adds NGINX configuration generation for auth_basic directives 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.

shaun-nx and others added 3 commits December 10, 2025 15:51
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
)

const (
AuthKeyBasic = "auth" // Key in the Secret data for Basic Auth credentials.
Copy link
Collaborator

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?

Copy link
Contributor Author

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"

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@github-actions github-actions bot added the dependencies Pull requests that update a dependency file label Dec 15, 2025
@shaun-nx shaun-nx marked this pull request as ready for review December 17, 2025 16:02
@shaun-nx shaun-nx requested a review from a team as a code owner December 17, 2025 16:02
Copy link
Collaborator

@sjberman sjberman left a 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.

Comment on lines 997 to 999
func generateAuthBasicUserFileID(id string) AuthBasicUserFileID {
return AuthBasicUserFileID(id)
}
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Comment on lines 726 to 730
userFilePathAndName := fmt.Sprintf(
"%s/%s_%s",
secretsFolder,
authenticationFilter.Basic.SecretNamespace,
authenticationFilter.Basic.SecretName,
Copy link
Collaborator

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.

Copy link
Contributor Author

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",
Copy link
Collaborator

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?

Copy link
Contributor

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

Copy link
Contributor Author

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!

Copy link
Contributor Author

@shaun-nx shaun-nx Dec 18, 2025

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,
}
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@salonichf5 salonichf5 left a 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",
Copy link
Contributor

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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 {
Copy link
Contributor

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 }};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why user?

Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

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",
}
}

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@bjee19 bjee19 left a 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",
}
}

Copy link
Contributor

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?

Copy link
Contributor Author

@shaun-nx shaun-nx Dec 19, 2025

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

Copy link
Collaborator

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.

Copy link
Contributor

@bjee19 bjee19 Dec 19, 2025

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.

Copy link
Collaborator

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.

@github-actions github-actions bot added the tests Pull requests that update tests label Dec 19, 2025
@shaun-nx shaun-nx requested a review from Copilot December 19, 2025 11:16
Copy link

Copilot AI left a 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) \
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request helm-chart Relates to helm chart release-notes tests Pull requests that update tests

Projects

Status: 🆕 New

Development

Successfully merging this pull request may close these issues.

6 participants