This is an automated email from the ASF dual-hosted git repository. ronething pushed a commit to branch feat/response-rewrite in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
commit 2d1c77708778b08532a22614a27638f017493a44 Merge: 908f1688 4cd10056 Author: Ashing Zheng <[email protected]> AuthorDate: Thu Oct 30 12:09:04 2025 +0800 Merge remote-tracking branch 'origin/master' into feat/response-rewrite Signed-off-by: Ashing Zheng <[email protected]> .../annotations/plugins/authorization.go | 61 +++++++++++ .../adc/translator/annotations/plugins/plugins.go | 2 + internal/adc/translator/annotations_test.go | 22 ++++ test/e2e/ingress/annotations.go | 119 +++++++++++++++++++++ 4 files changed, 204 insertions(+) diff --cc internal/adc/translator/annotations/plugins/authorization.go index 00000000,83b64c5b..2dee12ea mode 000000,100644..100644 --- a/internal/adc/translator/annotations/plugins/authorization.go +++ b/internal/adc/translator/annotations/plugins/authorization.go @@@ -1,0 -1,61 +1,61 @@@ + // 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 basicAuth struct{} + + // NewkeyBasicHandler creates a handler to convert + // annotations about basicAuth control to APISIX basic-auth plugin. + func NewBasicAuthHandler() PluginAnnotationsHandler { + return &basicAuth{} + } + + func (b *basicAuth) PluginName() string { + return "basic-auth" + } + -func (b *basicAuth) Handle(e annotations.Extractor) (interface{}, error) { ++func (b *basicAuth) Handle(e annotations.Extractor) (any, error) { + if e.GetStringAnnotation(annotations.AnnotationsAuthType) != "basicAuth" { + return nil, nil + } + plugin := adctypes.BasicAuthConfig{} + return &plugin, nil + } + + type keyAuth struct{} + + // NewkeyAuthHandler creates a handler to convert + // annotations about keyAuth control to APISIX key-auth plugin. + func NewKeyAuthHandler() PluginAnnotationsHandler { + return &keyAuth{} + } + + func (k *keyAuth) PluginName() string { + return "key-auth" + } + -func (k *keyAuth) Handle(e annotations.Extractor) (interface{}, error) { ++func (k *keyAuth) Handle(e annotations.Extractor) (any, error) { + if e.GetStringAnnotation(annotations.AnnotationsAuthType) != "keyAuth" { + return nil, nil + } + plugin := adctypes.KeyAuthConfig{} + return &plugin, nil + } diff --cc internal/adc/translator/annotations/plugins/plugins.go index f49fe8f8,8ebb0fb3..a0cd78f8 --- a/internal/adc/translator/annotations/plugins/plugins.go +++ b/internal/adc/translator/annotations/plugins/plugins.go @@@ -40,7 -40,8 +40,9 @@@ var NewCorsHandler(), NewCSRFHandler(), NewFaultInjectionHandler(), + NewBasicAuthHandler(), + NewKeyAuthHandler(), + NewResponseRewriteHandler(), } ) diff --cc test/e2e/ingress/annotations.go index 8683935e,f7a19a1f..a4e9e10d --- a/test/e2e/ingress/annotations.go +++ b/test/e2e/ingress/annotations.go @@@ -382,57 -382,48 +382,99 @@@ spec name: httpbin-service-e2e-test port: number: 80 + ` + ingressKeyAuth = ` + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: key-auth + annotations: + k8s.apisix.apache.org/auth-type: "keyAuth" + spec: + ingressClassName: %s + rules: + - host: httpbin.example + http: + paths: + - path: /ip + pathType: Exact + backend: + service: + name: httpbin-service-e2e-test + port: + number: 80 + ` + ingressBasicAuth = ` + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: basic-auth + annotations: + k8s.apisix.apache.org/auth-type: "basicAuth" + spec: + ingressClassName: %s + rules: + - host: httpbin.example + http: + paths: + - path: /get + pathType: Exact + backend: + service: + name: httpbin-service-e2e-test + port: + number: 80 +` + responseRewrite = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: response-rewrite + annotations: + k8s.apisix.apache.org/enable-response-rewrite: "true" + k8s.apisix.apache.org/response-rewrite-status-code: "400" + k8s.apisix.apache.org/response-rewrite-body: "custom response body" + k8s.apisix.apache.org/response-rewrite-body-base64: "false" + k8s.apisix.apache.org/response-rewrite-set-header: "X-Custom-Header:custom-value" + k8s.apisix.apache.org/response-rewrite-add-header: "X-Add-Header:added-value" + k8s.apisix.apache.org/response-rewrite-remove-header: "Server" +spec: + ingressClassName: %s + rules: + - host: httpbin.example + http: + paths: + - path: /get + pathType: Exact + backend: + service: + name: httpbin-service-e2e-test + port: + number: 80 +` + responseRewriteBase64 = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: response-rewrite-base64 + annotations: + k8s.apisix.apache.org/enable-response-rewrite: "true" + k8s.apisix.apache.org/response-rewrite-status-code: "400" + k8s.apisix.apache.org/response-rewrite-body: "Y3VzdG9tIHJlc3BvbnNlIGJvZHk=" + k8s.apisix.apache.org/response-rewrite-body-base64: "true" +spec: + ingressClassName: %s + rules: + - host: httpbin-base64.example + http: + paths: + - path: /get + pathType: Exact + backend: + service: + name: httpbin-service-e2e-test + port: + number: 80 ` ) BeforeEach(func() { @@@ -661,62 -652,82 +703,139 @@@ spec s.RequestAssert(test) } }) + It("authentication", func() { + var ( + keyAuth = ` + apiVersion: apisix.apache.org/v2 + kind: ApisixConsumer + metadata: + name: key + spec: + ingressClassName: %s + authParameter: + keyAuth: + value: + key: test-key + ` + basicAuth = ` + apiVersion: apisix.apache.org/v2 + kind: ApisixConsumer + metadata: + name: basic + spec: + ingressClassName: %s + authParameter: + basicAuth: + value: + username: test-user + password: test-password + ` + ) + Expect(s.CreateResourceFromString(fmt.Sprintf(keyAuth, s.Namespace()))).ShouldNot(HaveOccurred(), "creating ApisixConsumer for keyAuth") + Expect(s.CreateResourceFromString(fmt.Sprintf(basicAuth, s.Namespace()))).ShouldNot(HaveOccurred(), "creating ApisixConsumer for basicAuth") + Expect(s.CreateResourceFromString(fmt.Sprintf(ingressKeyAuth, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress") + Expect(s.CreateResourceFromString(fmt.Sprintf(ingressBasicAuth, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress") + + tests := []*scaffold.RequestAssert{ + { + Method: "GET", + Path: "/get", + Host: "httpbin.example", + BasicAuth: &scaffold.BasicAuth{ + Username: "test-user", + Password: "test-password", + }, + Check: scaffold.WithExpectedStatus(http.StatusOK), + }, + { + Method: "GET", + Path: "/get", + Host: "httpbin.example", + BasicAuth: &scaffold.BasicAuth{ + Username: "invalid-user", + Password: "invalid-password", + }, + Check: scaffold.WithExpectedStatus(http.StatusUnauthorized), + }, + { + Method: "GET", + Path: "/ip", + Host: "httpbin.example", + Headers: map[string]string{ + "apikey": "test-key", + }, + Check: scaffold.WithExpectedStatus(http.StatusOK), + }, + { + Method: "GET", + Path: "/ip", + Host: "httpbin.example", + Headers: map[string]string{ + "apikey": "invalid-key", + }, + Check: scaffold.WithExpectedStatus(http.StatusUnauthorized), + }, + } + for _, test := range tests { + s.RequestAssert(test) + } + }) + + It("response-rewrite", func() { + Expect(s.CreateResourceFromString(fmt.Sprintf(responseRewrite, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress") + + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/get", + Host: "httpbin.example", + Checks: []scaffold.ResponseCheckFunc{ + scaffold.WithExpectedStatus(http.StatusBadRequest), + scaffold.WithExpectedBodyContains("custom response body"), + scaffold.WithExpectedHeader("X-Custom-Header", "custom-value"), + scaffold.WithExpectedHeader("X-Add-Header", "added-value"), + }, + }) + + By("Verify response-rewrite plugin is configured in the route") + routes, err := s.DefaultDataplaneResource().Route().List(context.Background()) + Expect(err).NotTo(HaveOccurred(), "listing Route") + Expect(routes).To(HaveLen(1), "checking Route length") + Expect(routes[0].Plugins).To(HaveKey("response-rewrite"), "checking Route plugins") + + jsonBytes, err := json.Marshal(routes[0].Plugins["response-rewrite"]) + Expect(err).NotTo(HaveOccurred(), "marshalling response-rewrite plugin config") + var rewriteConfig map[string]any + err = json.Unmarshal(jsonBytes, &rewriteConfig) + Expect(err).NotTo(HaveOccurred(), "unmarshalling response-rewrite plugin config") + Expect(rewriteConfig["status_code"]).To(Equal(float64(400)), "checking status code") + Expect(rewriteConfig["body"]).To(Equal("custom response body"), "checking body") + }) + + It("response-rewrite with base64", func() { + Expect(s.CreateResourceFromString(fmt.Sprintf(responseRewriteBase64, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress") + + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/get", + Host: "httpbin-base64.example", + Checks: []scaffold.ResponseCheckFunc{ + scaffold.WithExpectedStatus(http.StatusBadRequest), + scaffold.WithExpectedBodyContains("custom response body"), + }, + }) + By("Verify response-rewrite plugin is configured in the route") + routes, err := s.DefaultDataplaneResource().Route().List(context.Background()) + Expect(err).NotTo(HaveOccurred(), "listing Route") + Expect(routes).To(HaveLen(1), "checking Route length") + Expect(routes[0].Plugins).To(HaveKey("response-rewrite"), "checking Route plugins") + + jsonBytes, err := json.Marshal(routes[0].Plugins["response-rewrite"]) + Expect(err).NotTo(HaveOccurred(), "marshalling response-rewrite plugin config") + var rewriteConfig map[string]any + err = json.Unmarshal(jsonBytes, &rewriteConfig) + Expect(err).NotTo(HaveOccurred(), "unmarshalling response-rewrite plugin config") + Expect(rewriteConfig["status_code"]).To(Equal(float64(400)), "checking status code") + Expect(rewriteConfig["body_base64"]).To(BeTrue(), "checking body_base64") + }) }) })
