This is an automated email from the ASF dual-hosted git repository.
ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new e7d192b6 feat: support forward-auth for ingress annotations (#2641)
e7d192b6 is described below
commit e7d192b6f8c2b754584cb7d6e63f907ea59202a7
Author: AlinsRan <[email protected]>
AuthorDate: Tue Nov 4 16:49:15 2025 +0800
feat: support forward-auth for ingress annotations (#2641)
Co-authored-by: Ashing Zheng <[email protected]>
---
.../translator/annotations/plugins/forward-auth.go | 48 +++++++++++++++++
.../adc/translator/annotations/plugins/plugins.go | 1 +
internal/adc/translator/annotations/types.go | 4 ++
internal/adc/translator/annotations_test.go | 41 ++++++++++++++
internal/webhook/v1/ingress_webhook.go | 5 --
test/e2e/framework/manifests/nginx.yaml | 13 +++++
test/e2e/ingress/annotations.go | 63 ++++++++++++++++++++++
7 files changed, 170 insertions(+), 5 deletions(-)
diff --git a/internal/adc/translator/annotations/plugins/forward-auth.go
b/internal/adc/translator/annotations/plugins/forward-auth.go
new file mode 100644
index 00000000..61fad5ec
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/forward-auth.go
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package plugins
+
+import (
+ adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type forwardAuth struct{}
+
+// NewForwardAuthHandler creates a handler to convert
+// annotations about forward authentication to APISIX forward-auth plugin.
+func NewForwardAuthHandler() PluginAnnotationsHandler {
+ return &forwardAuth{}
+}
+
+func (i *forwardAuth) PluginName() string {
+ return "forward-auth"
+}
+
+func (i *forwardAuth) Handle(e annotations.Extractor) (any, error) {
+ uri := e.GetStringAnnotation(annotations.AnnotationsForwardAuthURI)
+ sslVerify :=
e.GetStringAnnotation(annotations.AnnotationsForwardAuthSSLVerify) !=
annotations.FalseString
+ if len(uri) > 0 {
+ return &adctypes.ForwardAuthConfig{
+ URI: uri,
+ SSLVerify: sslVerify,
+ RequestHeaders:
e.GetStringsAnnotation(annotations.AnnotationsForwardAuthRequestHeaders),
+ UpstreamHeaders:
e.GetStringsAnnotation(annotations.AnnotationsForwardAuthUpstreamHeaders),
+ ClientHeaders:
e.GetStringsAnnotation(annotations.AnnotationsForwardAuthClientHeaders),
+ }, nil
+ }
+
+ return nil, nil
+}
diff --git a/internal/adc/translator/annotations/plugins/plugins.go
b/internal/adc/translator/annotations/plugins/plugins.go
index b48bfff5..2475ef55 100644
--- a/internal/adc/translator/annotations/plugins/plugins.go
+++ b/internal/adc/translator/annotations/plugins/plugins.go
@@ -45,6 +45,7 @@ var (
NewKeyAuthHandler(),
NewResponseRewriteHandler(),
NewIPRestrictionHandler(),
+ NewForwardAuthHandler(),
}
)
diff --git a/internal/adc/translator/annotations/types.go
b/internal/adc/translator/annotations/types.go
index 1ce19783..61ef517f 100644
--- a/internal/adc/translator/annotations/types.go
+++ b/internal/adc/translator/annotations/types.go
@@ -91,6 +91,10 @@ const (
AnnotationsSvcNamespace = AnnotationsPrefix + "svc-namespace"
)
+const (
+ FalseString = "false"
+)
+
// Handler abstracts the behavior so that the apisix-ingress-controller knows
type IngressAnnotationsParser interface {
// Handle parses the target annotation and converts it to the
type-agnostic structure.
diff --git a/internal/adc/translator/annotations_test.go
b/internal/adc/translator/annotations_test.go
index 46d4550a..6fe51da9 100644
--- a/internal/adc/translator/annotations_test.go
+++ b/internal/adc/translator/annotations_test.go
@@ -288,6 +288,47 @@ func TestTranslateIngressAnnotations(t *testing.T) {
ServiceNamespace: "custom-namespace",
},
},
+ {
+ name: "forward auth",
+ anno: map[string]string{
+ annotations.AnnotationsForwardAuthURI:
"http://127.0.0.1:9080",
+
annotations.AnnotationsForwardAuthRequestHeaders: "Authorization",
+
annotations.AnnotationsForwardAuthClientHeaders: "Location",
+
annotations.AnnotationsForwardAuthUpstreamHeaders: "X-User-ID",
+ },
+ expected: &IngressConfig{
+ Plugins: adctypes.Plugins{
+ "forward-auth":
&adctypes.ForwardAuthConfig{
+ URI:
"http://127.0.0.1:9080",
+ SSLVerify: true,
+ RequestHeaders:
[]string{"Authorization"},
+ UpstreamHeaders:
[]string{"X-User-ID"},
+ ClientHeaders:
[]string{"Location"},
+ },
+ },
+ },
+ },
+ {
+ name: "forward auth with ssl-verify false",
+ anno: map[string]string{
+ annotations.AnnotationsForwardAuthURI:
"http://127.0.0.1:9080",
+ annotations.AnnotationsForwardAuthSSLVerify:
"false",
+
annotations.AnnotationsForwardAuthRequestHeaders: "Authorization",
+
annotations.AnnotationsForwardAuthClientHeaders: "Location",
+
annotations.AnnotationsForwardAuthUpstreamHeaders: "X-User-ID",
+ },
+ expected: &IngressConfig{
+ Plugins: adctypes.Plugins{
+ "forward-auth":
&adctypes.ForwardAuthConfig{
+ URI:
"http://127.0.0.1:9080",
+ SSLVerify: false,
+ RequestHeaders:
[]string{"Authorization"},
+ UpstreamHeaders:
[]string{"X-User-ID"},
+ ClientHeaders:
[]string{"Location"},
+ },
+ },
+ },
+ },
}
for _, tt := range tests {
diff --git a/internal/webhook/v1/ingress_webhook.go
b/internal/webhook/v1/ingress_webhook.go
index 740c25a3..2dfc6de5 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -40,11 +40,6 @@ var ingresslog = logf.Log.WithName("ingress-resource")
// ref:
https://apisix.apache.org/docs/ingress-controller/upgrade-guide/#limited-support-for-ingress-annotations
var unsupportedAnnotations = []string{
"k8s.apisix.apache.org/use-regex",
- "k8s.apisix.apache.org/auth-uri",
- "k8s.apisix.apache.org/auth-ssl-verify",
- "k8s.apisix.apache.org/auth-request-headers",
- "k8s.apisix.apache.org/auth-upstream-headers",
- "k8s.apisix.apache.org/auth-client-headers",
"k8s.apisix.apache.org/auth-type",
}
diff --git a/test/e2e/framework/manifests/nginx.yaml
b/test/e2e/framework/manifests/nginx.yaml
index e75c1dae..abdc7e9e 100644
--- a/test/e2e/framework/manifests/nginx.yaml
+++ b/test/e2e/framework/manifests/nginx.yaml
@@ -51,6 +51,19 @@ data:
}
}
+ location /auth {
+ content_by_lua_block {
+ local auth = ngx.req.get_headers()["Authorization"]
+ if auth == "123" then
+ ngx.header["X-User-ID"] = "user-123"
+ ngx.exit(200)
+ else
+ ngx.header["Location"] = "http://example.com/auth"
+ ngx.exit(401)
+ end
+ }
+ }
+
location /ws {
content_by_lua_block {
local server = require "resty.websocket.server"
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index 049d51bb..b29aeaee 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -573,6 +573,30 @@ spec:
name: httpbin-service-e2e-test
port:
number: 80
+`
+ ingressForwardAuth = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: forward-auth
+ annotations:
+ k8s.apisix.apache.org/auth-uri: %s
+ k8s.apisix.apache.org/auth-request-headers: Authorization
+ k8s.apisix.apache.org/auth-upstream-headers: X-User-ID
+ k8s.apisix.apache.org/auth-client-headers: Location
+spec:
+ ingressClassName: %s
+ rules:
+ - host: httpbin.example
+ http:
+ paths:
+ - path: /get
+ pathType: Exact
+ backend:
+ service:
+ name: httpbin-service-e2e-test
+ port:
+ number: 80
`
)
BeforeEach(func() {
@@ -1017,6 +1041,45 @@ spec:
Check:
scaffold.WithExpectedStatus(http.StatusForbidden),
})
})
+ It("forward-auth", func() {
+ s.DeployNginx(framework.NginxOptions{
+ Namespace: s.Namespace(),
+ Replicas: ptr.To(int32(1)),
+ })
+
+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressForwardAuth,
"http://nginx/auth", s.Namespace()))).
+ ShouldNot(HaveOccurred(), "creating
ApisixConsumer for forwardAuth")
+
+ tests := []*scaffold.RequestAssert{
+ {
+ Method: "GET",
+ Path: "/get",
+ Host: "httpbin.example",
+ Headers: map[string]string{
+ "Authorization": "123",
+ },
+ Checks: []scaffold.ResponseCheckFunc{
+
scaffold.WithExpectedStatus(http.StatusOK),
+
scaffold.WithExpectedBodyContains(`"X-User-Id": "user-123"`),
+ },
+ },
+ {
+ Method: "GET",
+ Path: "/get",
+ Host: "httpbin.example",
+ Headers: map[string]string{
+ "Authorization": "456",
+ },
+ Checks: []scaffold.ResponseCheckFunc{
+
scaffold.WithExpectedStatus(http.StatusUnauthorized),
+
scaffold.WithExpectedHeader("Location", "http://example.com/auth"),
+ },
+ },
+ }
+ for _, test := range tests {
+ s.RequestAssert(test)
+ }
+ })
})
Context("Service Namespace", func() {