Skip to content

Commit e7d192b

Browse files
AlinsRanronething
andauthored
feat: support forward-auth for ingress annotations (#2641)
Co-authored-by: Ashing Zheng <[email protected]>
1 parent d3bc2a5 commit e7d192b

File tree

7 files changed

+170
-5
lines changed

7 files changed

+170
-5
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
package plugins
16+
17+
import (
18+
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
19+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
20+
)
21+
22+
type forwardAuth struct{}
23+
24+
// NewForwardAuthHandler creates a handler to convert
25+
// annotations about forward authentication to APISIX forward-auth plugin.
26+
func NewForwardAuthHandler() PluginAnnotationsHandler {
27+
return &forwardAuth{}
28+
}
29+
30+
func (i *forwardAuth) PluginName() string {
31+
return "forward-auth"
32+
}
33+
34+
func (i *forwardAuth) Handle(e annotations.Extractor) (any, error) {
35+
uri := e.GetStringAnnotation(annotations.AnnotationsForwardAuthURI)
36+
sslVerify := e.GetStringAnnotation(annotations.AnnotationsForwardAuthSSLVerify) != annotations.FalseString
37+
if len(uri) > 0 {
38+
return &adctypes.ForwardAuthConfig{
39+
URI: uri,
40+
SSLVerify: sslVerify,
41+
RequestHeaders: e.GetStringsAnnotation(annotations.AnnotationsForwardAuthRequestHeaders),
42+
UpstreamHeaders: e.GetStringsAnnotation(annotations.AnnotationsForwardAuthUpstreamHeaders),
43+
ClientHeaders: e.GetStringsAnnotation(annotations.AnnotationsForwardAuthClientHeaders),
44+
}, nil
45+
}
46+
47+
return nil, nil
48+
}

internal/adc/translator/annotations/plugins/plugins.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ var (
4545
NewKeyAuthHandler(),
4646
NewResponseRewriteHandler(),
4747
NewIPRestrictionHandler(),
48+
NewForwardAuthHandler(),
4849
}
4950
)
5051

internal/adc/translator/annotations/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ const (
9191
AnnotationsSvcNamespace = AnnotationsPrefix + "svc-namespace"
9292
)
9393

94+
const (
95+
FalseString = "false"
96+
)
97+
9498
// Handler abstracts the behavior so that the apisix-ingress-controller knows
9599
type IngressAnnotationsParser interface {
96100
// Handle parses the target annotation and converts it to the type-agnostic structure.

internal/adc/translator/annotations_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,47 @@ func TestTranslateIngressAnnotations(t *testing.T) {
288288
ServiceNamespace: "custom-namespace",
289289
},
290290
},
291+
{
292+
name: "forward auth",
293+
anno: map[string]string{
294+
annotations.AnnotationsForwardAuthURI: "http://127.0.0.1:9080",
295+
annotations.AnnotationsForwardAuthRequestHeaders: "Authorization",
296+
annotations.AnnotationsForwardAuthClientHeaders: "Location",
297+
annotations.AnnotationsForwardAuthUpstreamHeaders: "X-User-ID",
298+
},
299+
expected: &IngressConfig{
300+
Plugins: adctypes.Plugins{
301+
"forward-auth": &adctypes.ForwardAuthConfig{
302+
URI: "http://127.0.0.1:9080",
303+
SSLVerify: true,
304+
RequestHeaders: []string{"Authorization"},
305+
UpstreamHeaders: []string{"X-User-ID"},
306+
ClientHeaders: []string{"Location"},
307+
},
308+
},
309+
},
310+
},
311+
{
312+
name: "forward auth with ssl-verify false",
313+
anno: map[string]string{
314+
annotations.AnnotationsForwardAuthURI: "http://127.0.0.1:9080",
315+
annotations.AnnotationsForwardAuthSSLVerify: "false",
316+
annotations.AnnotationsForwardAuthRequestHeaders: "Authorization",
317+
annotations.AnnotationsForwardAuthClientHeaders: "Location",
318+
annotations.AnnotationsForwardAuthUpstreamHeaders: "X-User-ID",
319+
},
320+
expected: &IngressConfig{
321+
Plugins: adctypes.Plugins{
322+
"forward-auth": &adctypes.ForwardAuthConfig{
323+
URI: "http://127.0.0.1:9080",
324+
SSLVerify: false,
325+
RequestHeaders: []string{"Authorization"},
326+
UpstreamHeaders: []string{"X-User-ID"},
327+
ClientHeaders: []string{"Location"},
328+
},
329+
},
330+
},
331+
},
291332
}
292333

293334
for _, tt := range tests {

internal/webhook/v1/ingress_webhook.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ var ingresslog = logf.Log.WithName("ingress-resource")
4040
// ref: https://apisix.apache.org/docs/ingress-controller/upgrade-guide/#limited-support-for-ingress-annotations
4141
var unsupportedAnnotations = []string{
4242
"k8s.apisix.apache.org/use-regex",
43-
"k8s.apisix.apache.org/auth-uri",
44-
"k8s.apisix.apache.org/auth-ssl-verify",
45-
"k8s.apisix.apache.org/auth-request-headers",
46-
"k8s.apisix.apache.org/auth-upstream-headers",
47-
"k8s.apisix.apache.org/auth-client-headers",
4843
"k8s.apisix.apache.org/auth-type",
4944
}
5045

test/e2e/framework/manifests/nginx.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ data:
5151
}
5252
}
5353
54+
location /auth {
55+
content_by_lua_block {
56+
local auth = ngx.req.get_headers()["Authorization"]
57+
if auth == "123" then
58+
ngx.header["X-User-ID"] = "user-123"
59+
ngx.exit(200)
60+
else
61+
ngx.header["Location"] = "http://example.com/auth"
62+
ngx.exit(401)
63+
end
64+
}
65+
}
66+
5467
location /ws {
5568
content_by_lua_block {
5669
local server = require "resty.websocket.server"

test/e2e/ingress/annotations.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,30 @@ spec:
573573
name: httpbin-service-e2e-test
574574
port:
575575
number: 80
576+
`
577+
ingressForwardAuth = `
578+
apiVersion: networking.k8s.io/v1
579+
kind: Ingress
580+
metadata:
581+
name: forward-auth
582+
annotations:
583+
k8s.apisix.apache.org/auth-uri: %s
584+
k8s.apisix.apache.org/auth-request-headers: Authorization
585+
k8s.apisix.apache.org/auth-upstream-headers: X-User-ID
586+
k8s.apisix.apache.org/auth-client-headers: Location
587+
spec:
588+
ingressClassName: %s
589+
rules:
590+
- host: httpbin.example
591+
http:
592+
paths:
593+
- path: /get
594+
pathType: Exact
595+
backend:
596+
service:
597+
name: httpbin-service-e2e-test
598+
port:
599+
number: 80
576600
`
577601
)
578602
BeforeEach(func() {
@@ -1017,6 +1041,45 @@ spec:
10171041
Check: scaffold.WithExpectedStatus(http.StatusForbidden),
10181042
})
10191043
})
1044+
It("forward-auth", func() {
1045+
s.DeployNginx(framework.NginxOptions{
1046+
Namespace: s.Namespace(),
1047+
Replicas: ptr.To(int32(1)),
1048+
})
1049+
1050+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressForwardAuth, "http://nginx/auth", s.Namespace()))).
1051+
ShouldNot(HaveOccurred(), "creating ApisixConsumer for forwardAuth")
1052+
1053+
tests := []*scaffold.RequestAssert{
1054+
{
1055+
Method: "GET",
1056+
Path: "/get",
1057+
Host: "httpbin.example",
1058+
Headers: map[string]string{
1059+
"Authorization": "123",
1060+
},
1061+
Checks: []scaffold.ResponseCheckFunc{
1062+
scaffold.WithExpectedStatus(http.StatusOK),
1063+
scaffold.WithExpectedBodyContains(`"X-User-Id": "user-123"`),
1064+
},
1065+
},
1066+
{
1067+
Method: "GET",
1068+
Path: "/get",
1069+
Host: "httpbin.example",
1070+
Headers: map[string]string{
1071+
"Authorization": "456",
1072+
},
1073+
Checks: []scaffold.ResponseCheckFunc{
1074+
scaffold.WithExpectedStatus(http.StatusUnauthorized),
1075+
scaffold.WithExpectedHeader("Location", "http://example.com/auth"),
1076+
},
1077+
},
1078+
}
1079+
for _, test := range tests {
1080+
s.RequestAssert(test)
1081+
}
1082+
})
10201083
})
10211084

10221085
Context("Service Namespace", func() {

0 commit comments

Comments
 (0)