Skip to content

Commit d3bc2a5

Browse files
authored
feat: support ip restriction for ingress annotations (#2642)
1 parent 0c7d37f commit d3bc2a5

File tree

5 files changed

+186
-2
lines changed

5 files changed

+186
-2
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
16+
package plugins
17+
18+
import (
19+
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
20+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
21+
)
22+
23+
type ipRestriction struct{}
24+
25+
// NewIPRestrictionHandler creates a handler to convert
26+
// annotations about client IP control to APISIX ip-restriction plugin.
27+
func NewIPRestrictionHandler() PluginAnnotationsHandler {
28+
return &ipRestriction{}
29+
}
30+
31+
func (i *ipRestriction) PluginName() string {
32+
return "ip-restriction"
33+
}
34+
35+
func (i *ipRestriction) Handle(e annotations.Extractor) (any, error) {
36+
allowlist := e.GetStringsAnnotation(annotations.AnnotationsAllowlistSourceRange)
37+
blocklist := e.GetStringsAnnotation(annotations.AnnotationsBlocklistSourceRange)
38+
39+
if allowlist == nil && blocklist == nil {
40+
return nil, nil
41+
}
42+
43+
return &adctypes.IPRestrictConfig{
44+
Allowlist: allowlist,
45+
Blocklist: blocklist,
46+
}, nil
47+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
16+
package plugins
17+
18+
import (
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
23+
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
24+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
25+
)
26+
27+
func TestIPRestrictionHandler(t *testing.T) {
28+
// Test with allowlist only
29+
anno := map[string]string{
30+
annotations.AnnotationsAllowlistSourceRange: "10.2.2.2,192.168.0.0/16",
31+
}
32+
p := NewIPRestrictionHandler()
33+
out, err := p.Handle(annotations.NewExtractor(anno))
34+
assert.Nil(t, err, "checking given error")
35+
assert.NotNil(t, out, "checking output is not nil")
36+
config := out.(*adctypes.IPRestrictConfig)
37+
assert.Len(t, config.Allowlist, 2, "checking size of allowlist")
38+
assert.Equal(t, "10.2.2.2", config.Allowlist[0])
39+
assert.Equal(t, "192.168.0.0/16", config.Allowlist[1])
40+
assert.Nil(t, config.Blocklist, "checking blocklist is nil")
41+
assert.Equal(t, "ip-restriction", p.PluginName())
42+
43+
// Test with both allowlist and blocklist
44+
anno[annotations.AnnotationsBlocklistSourceRange] = "172.17.0.0/16,127.0.0.1"
45+
out, err = p.Handle(annotations.NewExtractor(anno))
46+
assert.Nil(t, err, "checking given error")
47+
assert.NotNil(t, out, "checking output is not nil")
48+
config = out.(*adctypes.IPRestrictConfig)
49+
assert.Len(t, config.Allowlist, 2, "checking size of allowlist")
50+
assert.Equal(t, "10.2.2.2", config.Allowlist[0])
51+
assert.Equal(t, "192.168.0.0/16", config.Allowlist[1])
52+
assert.Len(t, config.Blocklist, 2, "checking size of blocklist")
53+
assert.Equal(t, "172.17.0.0/16", config.Blocklist[0])
54+
assert.Equal(t, "127.0.0.1", config.Blocklist[1])
55+
56+
// Test with blocklist only
57+
delete(anno, annotations.AnnotationsAllowlistSourceRange)
58+
out, err = p.Handle(annotations.NewExtractor(anno))
59+
assert.Nil(t, err, "checking given error")
60+
assert.NotNil(t, out, "checking output is not nil")
61+
config = out.(*adctypes.IPRestrictConfig)
62+
assert.Nil(t, config.Allowlist, "checking allowlist is nil")
63+
assert.Len(t, config.Blocklist, 2, "checking size of blocklist")
64+
assert.Equal(t, "172.17.0.0/16", config.Blocklist[0])
65+
assert.Equal(t, "127.0.0.1", config.Blocklist[1])
66+
67+
// Test with neither allowlist nor blocklist
68+
delete(anno, annotations.AnnotationsBlocklistSourceRange)
69+
out, err = p.Handle(annotations.NewExtractor(anno))
70+
assert.Nil(t, err, "checking given error")
71+
assert.Nil(t, out, "checking the given ip-restriction plugin config is nil")
72+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ var (
4444
NewBasicAuthHandler(),
4545
NewKeyAuthHandler(),
4646
NewResponseRewriteHandler(),
47+
NewIPRestrictionHandler(),
4748
}
4849
)
4950

internal/webhook/v1/ingress_webhook.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ var unsupportedAnnotations = []string{
4545
"k8s.apisix.apache.org/auth-request-headers",
4646
"k8s.apisix.apache.org/auth-upstream-headers",
4747
"k8s.apisix.apache.org/auth-client-headers",
48-
"k8s.apisix.apache.org/allowlist-source-range",
49-
"k8s.apisix.apache.org/blocklist-source-range",
5048
"k8s.apisix.apache.org/auth-type",
5149
}
5250

test/e2e/ingress/annotations.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,50 @@ spec:
530530
port:
531531
number: 80
532532
`
533+
534+
ingressAllowlist = `
535+
apiVersion: networking.k8s.io/v1
536+
kind: Ingress
537+
metadata:
538+
name: allowlist
539+
annotations:
540+
k8s.apisix.apache.org/allowlist-source-range: "10.0.5.0/16"
541+
spec:
542+
ingressClassName: %s
543+
rules:
544+
- host: httpbin.example
545+
http:
546+
paths:
547+
- path: /ip
548+
pathType: Exact
549+
backend:
550+
service:
551+
name: httpbin-service-e2e-test
552+
port:
553+
number: 80
554+
`
555+
556+
ingressBlocklist = `
557+
apiVersion: networking.k8s.io/v1
558+
kind: Ingress
559+
metadata:
560+
name: blocklist
561+
annotations:
562+
k8s.apisix.apache.org/blocklist-source-range: "127.0.0.1"
563+
spec:
564+
ingressClassName: %s
565+
rules:
566+
- host: httpbin-block.example
567+
http:
568+
paths:
569+
- path: /ip
570+
pathType: Exact
571+
backend:
572+
service:
573+
name: httpbin-service-e2e-test
574+
port:
575+
number: 80
576+
`
533577
)
534578
BeforeEach(func() {
535579
By("create GatewayProxy")
@@ -951,6 +995,28 @@ spec:
951995
Expect(rewriteConfig["status_code"]).To(Equal(float64(400)), "checking status code")
952996
Expect(rewriteConfig["body_base64"]).To(BeTrue(), "checking body_base64")
953997
})
998+
999+
It("ip-restriction", func() {
1000+
By("Test allowlist - create ingress with IP allowlist")
1001+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressAllowlist, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress with allowlist")
1002+
1003+
s.RequestAssert(&scaffold.RequestAssert{
1004+
Method: "GET",
1005+
Path: "/ip",
1006+
Host: "httpbin.example",
1007+
Check: scaffold.WithExpectedStatus(http.StatusForbidden),
1008+
})
1009+
1010+
By("Test blocklist - create ingress with IP blocklist")
1011+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressBlocklist, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress with blocklist")
1012+
1013+
s.RequestAssert(&scaffold.RequestAssert{
1014+
Method: "GET",
1015+
Path: "/ip",
1016+
Host: "httpbin-block.example",
1017+
Check: scaffold.WithExpectedStatus(http.StatusForbidden),
1018+
})
1019+
})
9541020
})
9551021

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

0 commit comments

Comments
 (0)