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 d3bc2a53 feat: support ip restriction for ingress annotations (#2642)
d3bc2a53 is described below
commit d3bc2a53f3787725e357773d7cfde2f9e5243ac6
Author: Ashing Zheng <[email protected]>
AuthorDate: Tue Nov 4 11:42:29 2025 +0800
feat: support ip restriction for ingress annotations (#2642)
---
.../annotations/plugins/ip_restriction.go | 47 ++++++++++++++
.../annotations/plugins/ip_restriction_test.go | 72 ++++++++++++++++++++++
.../adc/translator/annotations/plugins/plugins.go | 1 +
internal/webhook/v1/ingress_webhook.go | 2 -
test/e2e/ingress/annotations.go | 66 ++++++++++++++++++++
5 files changed, 186 insertions(+), 2 deletions(-)
diff --git a/internal/adc/translator/annotations/plugins/ip_restriction.go
b/internal/adc/translator/annotations/plugins/ip_restriction.go
new file mode 100644
index 00000000..2b1e279a
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/ip_restriction.go
@@ -0,0 +1,47 @@
+// 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 ipRestriction struct{}
+
+// NewIPRestrictionHandler creates a handler to convert
+// annotations about client IP control to APISIX ip-restriction plugin.
+func NewIPRestrictionHandler() PluginAnnotationsHandler {
+ return &ipRestriction{}
+}
+
+func (i *ipRestriction) PluginName() string {
+ return "ip-restriction"
+}
+
+func (i *ipRestriction) Handle(e annotations.Extractor) (any, error) {
+ allowlist :=
e.GetStringsAnnotation(annotations.AnnotationsAllowlistSourceRange)
+ blocklist :=
e.GetStringsAnnotation(annotations.AnnotationsBlocklistSourceRange)
+
+ if allowlist == nil && blocklist == nil {
+ return nil, nil
+ }
+
+ return &adctypes.IPRestrictConfig{
+ Allowlist: allowlist,
+ Blocklist: blocklist,
+ }, nil
+}
diff --git a/internal/adc/translator/annotations/plugins/ip_restriction_test.go
b/internal/adc/translator/annotations/plugins/ip_restriction_test.go
new file mode 100644
index 00000000..526850c4
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/ip_restriction_test.go
@@ -0,0 +1,72 @@
+// 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 (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+func TestIPRestrictionHandler(t *testing.T) {
+ // Test with allowlist only
+ anno := map[string]string{
+ annotations.AnnotationsAllowlistSourceRange:
"10.2.2.2,192.168.0.0/16",
+ }
+ p := NewIPRestrictionHandler()
+ out, err := p.Handle(annotations.NewExtractor(anno))
+ assert.Nil(t, err, "checking given error")
+ assert.NotNil(t, out, "checking output is not nil")
+ config := out.(*adctypes.IPRestrictConfig)
+ assert.Len(t, config.Allowlist, 2, "checking size of allowlist")
+ assert.Equal(t, "10.2.2.2", config.Allowlist[0])
+ assert.Equal(t, "192.168.0.0/16", config.Allowlist[1])
+ assert.Nil(t, config.Blocklist, "checking blocklist is nil")
+ assert.Equal(t, "ip-restriction", p.PluginName())
+
+ // Test with both allowlist and blocklist
+ anno[annotations.AnnotationsBlocklistSourceRange] =
"172.17.0.0/16,127.0.0.1"
+ out, err = p.Handle(annotations.NewExtractor(anno))
+ assert.Nil(t, err, "checking given error")
+ assert.NotNil(t, out, "checking output is not nil")
+ config = out.(*adctypes.IPRestrictConfig)
+ assert.Len(t, config.Allowlist, 2, "checking size of allowlist")
+ assert.Equal(t, "10.2.2.2", config.Allowlist[0])
+ assert.Equal(t, "192.168.0.0/16", config.Allowlist[1])
+ assert.Len(t, config.Blocklist, 2, "checking size of blocklist")
+ assert.Equal(t, "172.17.0.0/16", config.Blocklist[0])
+ assert.Equal(t, "127.0.0.1", config.Blocklist[1])
+
+ // Test with blocklist only
+ delete(anno, annotations.AnnotationsAllowlistSourceRange)
+ out, err = p.Handle(annotations.NewExtractor(anno))
+ assert.Nil(t, err, "checking given error")
+ assert.NotNil(t, out, "checking output is not nil")
+ config = out.(*adctypes.IPRestrictConfig)
+ assert.Nil(t, config.Allowlist, "checking allowlist is nil")
+ assert.Len(t, config.Blocklist, 2, "checking size of blocklist")
+ assert.Equal(t, "172.17.0.0/16", config.Blocklist[0])
+ assert.Equal(t, "127.0.0.1", config.Blocklist[1])
+
+ // Test with neither allowlist nor blocklist
+ delete(anno, annotations.AnnotationsBlocklistSourceRange)
+ out, err = p.Handle(annotations.NewExtractor(anno))
+ assert.Nil(t, err, "checking given error")
+ assert.Nil(t, out, "checking the given ip-restriction plugin config is
nil")
+}
diff --git a/internal/adc/translator/annotations/plugins/plugins.go
b/internal/adc/translator/annotations/plugins/plugins.go
index 06bc87f4..b48bfff5 100644
--- a/internal/adc/translator/annotations/plugins/plugins.go
+++ b/internal/adc/translator/annotations/plugins/plugins.go
@@ -44,6 +44,7 @@ var (
NewBasicAuthHandler(),
NewKeyAuthHandler(),
NewResponseRewriteHandler(),
+ NewIPRestrictionHandler(),
}
)
diff --git a/internal/webhook/v1/ingress_webhook.go
b/internal/webhook/v1/ingress_webhook.go
index 2c85c02c..740c25a3 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -45,8 +45,6 @@ var unsupportedAnnotations = []string{
"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/allowlist-source-range",
- "k8s.apisix.apache.org/blocklist-source-range",
"k8s.apisix.apache.org/auth-type",
}
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index 61551015..049d51bb 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -530,6 +530,50 @@ spec:
port:
number: 80
`
+
+ ingressAllowlist = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: allowlist
+ annotations:
+ k8s.apisix.apache.org/allowlist-source-range: "10.0.5.0/16"
+spec:
+ ingressClassName: %s
+ rules:
+ - host: httpbin.example
+ http:
+ paths:
+ - path: /ip
+ pathType: Exact
+ backend:
+ service:
+ name: httpbin-service-e2e-test
+ port:
+ number: 80
+`
+
+ ingressBlocklist = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: blocklist
+ annotations:
+ k8s.apisix.apache.org/blocklist-source-range: "127.0.0.1"
+spec:
+ ingressClassName: %s
+ rules:
+ - host: httpbin-block.example
+ http:
+ paths:
+ - path: /ip
+ pathType: Exact
+ backend:
+ service:
+ name: httpbin-service-e2e-test
+ port:
+ number: 80
+`
)
BeforeEach(func() {
By("create GatewayProxy")
@@ -951,6 +995,28 @@ spec:
Expect(rewriteConfig["status_code"]).To(Equal(float64(400)), "checking status
code")
Expect(rewriteConfig["body_base64"]).To(BeTrue(),
"checking body_base64")
})
+
+ It("ip-restriction", func() {
+ By("Test allowlist - create ingress with IP allowlist")
+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressAllowlist,
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress with allowlist")
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Method: "GET",
+ Path: "/ip",
+ Host: "httpbin.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusForbidden),
+ })
+
+ By("Test blocklist - create ingress with IP blocklist")
+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressBlocklist,
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress with blocklist")
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Method: "GET",
+ Path: "/ip",
+ Host: "httpbin-block.example",
+ Check:
scaffold.WithExpectedStatus(http.StatusForbidden),
+ })
+ })
})
Context("Service Namespace", func() {