This is an automated email from the ASF dual-hosted git repository. ronething pushed a commit to branch feat/ip-restrition in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
commit fea8f564fdce1e33f12ef511aa914d72c5121c34 Author: Ashing Zheng <[email protected]> AuthorDate: Fri Oct 31 14:42:40 2025 +0800 feat: support ip restriction annotations for ingress Signed-off-by: Ashing Zheng <[email protected]> --- .../annotations/plugins/ip_restriction.go | 47 ++++++++++++++ .../annotations/plugins/ip_restriction_test.go | 72 ++++++++++++++++++++++ .../adc/translator/annotations/plugins/plugins.go | 1 + test/e2e/ingress/annotations.go | 66 ++++++++++++++++++++ 4 files changed, 186 insertions(+) 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/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go index 98a0bcaf..36a51321 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,5 +995,27 @@ 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), + }) + }) }) })
