Repository: apache/apisix-ingress-controller

Issue:
docs: invalid  gateway api spec link
### Current State

ref: https://github.com/apache/apisix-ingress-controller/blob/1afb9acea8f55270055e7274e18647731d6150f1/docs/en/latest/concepts/gateway-api.md#L62

https://github.com/kubernetes-sigs/gateway-api/pull/4190/files#diff-e3e3fb94519e304f0297ce58a13bfd7b92548d585781b63b2871836f726f4a56 

Due to the changes in this PR, the link at https://gateway-api.sigs.k8s.io/reference/spec/ is inaccessible, and the corresponding link needs to be modified.

### Desired State

Modify to the correct link.

Discussion:
@kayx23: https://gateway-api.sigs.k8s.io/reference/1.3/spec/ or https://gateway-api.sigs.k8s.io/reference/1.4/spec/?@ronething: > [gateway-api.sigs.k8s.io/reference/1.3/spec](https://gateway-api.sigs.k8s.io/reference/1.3/spec/) or [gateway-api.sigs.k8s.io/reference/1.4/spec](https://gateway-api.sigs.k8s.io/reference/1.4/spec/)?

@kayx23 We currently support gateway api version v1.3, but i think we can also directly use main/spec(https://gateway-api.sigs.k8s.io/reference/main/spec/), as we are just providing an official reference link.@kayx23: > We currently support gateway api version v1.3

@ronething Is this information available anywhere to users? Not sure what implications there may be if someone looks at a different version of the docs. Linking to 1.3 feels more sensible to me.@ronething: > Is this information available anywhere to users?

Currently, there is no clear indication in this repository(only in Makefile/go.mod).

> Linking to 1.3 feels more sensible to me.

OK, we can specify a clear 1.3 version, and we can make modifications in the future if needed.

https://github.com/apache/apisix-ingress-controller/blob/e3c2f816cb06c29455384899cefa2af6e2242b9b/Makefile#L52-L53

Solution patch:
diff --git a/internal/adc/translator/annotations.go b/internal/adc/translator/annotations.go
index 9f2efd0f..9015a088 100644
--- a/internal/adc/translator/annotations.go
+++ b/internal/adc/translator/annotations.go
@@ -25,6 +25,7 @@ import (
 	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
 	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/pluginconfig"
 	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/plugins"
+	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/servicenamespace"
 	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/upstream"
 	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/websocket"
 )
@@ -34,6 +35,7 @@ type IngressConfig struct {
 	Upstream         upstream.Upstream
 	Plugins          adctypes.Plugins
 	EnableWebsocket  bool
+	ServiceNamespace string
 	PluginConfigName string
 }
 
@@ -42,6 +44,7 @@ var ingressAnnotationParsers = map[string]annotations.IngressAnnotationsParser{
 	"plugins":          plugins.NewParser(),
 	"EnableWebsocket":  websocket.NewParser(),
 	"PluginConfigName": pluginconfig.NewParser(),
+	"ServiceNamespace": servicenamespace.NewParser(),
 }
 
 func (t *Translator) TranslateIngressAnnotations(anno map[string]string) *IngressConfig {
diff --git a/internal/adc/translator/annotations/plugins/authorization.go b/internal/adc/translator/annotations/plugins/authorization.go
new file mode 100644
index 00000000..2dee12ea
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/authorization.go
@@ -0,0 +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) (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) (any, error) {
+	if e.GetStringAnnotation(annotations.AnnotationsAuthType) != "keyAuth" {
+		return nil, nil
+	}
+	plugin := adctypes.KeyAuthConfig{}
+	return &plugin, nil
+}
diff --git a/internal/adc/translator/annotations/plugins/plugins.go b/internal/adc/translator/annotations/plugins/plugins.go
index 2dd8e5f8..06bc87f4 100644
--- a/internal/adc/translator/annotations/plugins/plugins.go
+++ b/internal/adc/translator/annotations/plugins/plugins.go
@@ -37,9 +37,13 @@ var (
 
 	handlers = []PluginAnnotationsHandler{
 		NewRedirectHandler(),
+		NewRewriteHandler(),
 		NewCorsHandler(),
 		NewCSRFHandler(),
 		NewFaultInjectionHandler(),
+		NewBasicAuthHandler(),
+		NewKeyAuthHandler(),
+		NewResponseRewriteHandler(),
 	}
 )
 
diff --git a/internal/adc/translator/annotations/plugins/response_rewrite.go b/internal/adc/translator/annotations/plugins/response_rewrite.go
new file mode 100644
index 00000000..7de62238
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/response_rewrite.go
@@ -0,0 +1,89 @@
+// 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 (
+	"strconv"
+
+	adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type responseRewrite struct{}
+
+// NewResponseRewriteHandler creates a handler to convert annotations about
+// ResponseRewrite to APISIX response-rewrite plugin.
+func NewResponseRewriteHandler() PluginAnnotationsHandler {
+	return &responseRewrite{}
+}
+
+func (r *responseRewrite) PluginName() string {
+	return "response-rewrite"
+}
+
+func (r *responseRewrite) Handle(e annotations.Extractor) (any, error) {
+	if !e.GetBoolAnnotation(annotations.AnnotationsEnableResponseRewrite) {
+		return nil, nil
+	}
+
+	plugin := &adctypes.ResponseRewriteConfig{
+		BodyBase64: e.GetBoolAnnotation(annotations.AnnotationsResponseRewriteBodyBase64),
+		Body:       e.GetStringAnnotation(annotations.AnnotationsResponseRewriteBody),
+	}
+
+	// Parse status code, transformation fail defaults to 0
+	if statusCodeStr := e.GetStringAnnotation(annotations.AnnotationsResponseRewriteStatusCode); statusCodeStr != "" {
+		if statusCode, err := strconv.Atoi(statusCodeStr); err == nil {
+			plugin.StatusCode = statusCode
+		}
+	}
+
+	// Handle headers
+	addHeaders := e.GetStringsAnnotation(annotations.AnnotationsResponseRewriteHeaderAdd)
+	setHeaders := e.GetStringsAnnotation(annotations.AnnotationsResponseRewriteHeaderSet)
+	removeHeaders := e.GetStringsAnnotation(annotations.AnnotationsResponseRewriteHeaderRemove)
+
+	if len(addHeaders) > 0 || len(setHeaders) > 0 || len(removeHeaders) > 0 {
+		headers := &adctypes.ResponseHeaders{
+			Add:    addHeaders,
+			Remove: removeHeaders,
+		}
+
+		// Convert set headers from ["key:value", ...] to map[string]string
+		if len(setHeaders) > 0 {
+			headers.Set = make(map[string]string)
+			for _, header := range setHeaders {
+				if key, value, found := parseHeaderKeyValue(header); found {
+					headers.Set[key] = value
+				}
+			}
+		}
+
+		plugin.Headers = headers
+	}
+
+	return plugin, nil
+}
+
+// parseHeaderKeyValue parses a header string in format "key:value" and returns key, value and success flag
+func parseHeaderKeyValue(header string) (string, string, bool) {
+	for i := 0; i < len(header); i++ {
+		if header[i] == ':' {
+			return header[:i], header[i+1:], true
+		}
+	}
+	return "", "", false
+}
diff --git a/internal/adc/translator/annotations/plugins/rewrite.go b/internal/adc/translator/annotations/plugins/rewrite.go
new file mode 100644
index 00000000..30edfd27
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/rewrite.go
@@ -0,0 +1,60 @@
+// 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 (
+	"regexp"
+
+	adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type rewrite struct{}
+
+// NewRewriteHandler creates a handler to convert
+// annotations about request rewrite control to APISIX proxy-rewrite plugin.
+func NewRewriteHandler() PluginAnnotationsHandler {
+	return &rewrite{}
+}
+
+func (r *rewrite) PluginName() string {
+	return "proxy-rewrite"
+}
+
+func (r *rewrite) Handle(e annotations.Extractor) (any, error) {
+	rewriteTarget := e.GetStringAnnotation(annotations.AnnotationsRewriteTarget)
+	rewriteTargetRegex := e.GetStringAnnotation(annotations.AnnotationsRewriteTargetRegex)
+	rewriteTemplate := e.GetStringAnnotation(annotations.AnnotationsRewriteTargetRegexTemplate)
+
+	// If no rewrite annotations are present, return nil
+	if rewriteTarget == "" && rewriteTargetRegex == "" && rewriteTemplate == "" {
+		return nil, nil
+	}
+
+	var plugin adctypes.RewriteConfig
+	plugin.RewriteTarget = rewriteTarget
+
+	// If both regex and template are provided, validate and set regex_uri
+	if rewriteTargetRegex != "" && rewriteTemplate != "" {
+		_, err := regexp.Compile(rewriteTargetRegex)
+		if err != nil {
+			return nil, err
+		}
+		plugin.RewriteTargetRegex = []string{rewriteTargetRegex, rewriteTemplate}
+	}
+
+	return &plugin, nil
+}
diff --git a/internal/adc/translator/annotations/servicenamespace/servicenamespace.go b/internal/adc/translator/annotations/servicenamespace/servicenamespace.go
new file mode 100644
index 00000000..d673654d
--- /dev/null
+++ b/internal/adc/translator/annotations/servicenamespace/servicenamespace.go
@@ -0,0 +1,30 @@
+// 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 servicenamespace
+
+import (
+	"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type servicenamespace struct{}
+
+func NewParser() annotations.IngressAnnotationsParser {
+	return &servicenamespace{}
+}
+
+func (w *servicenamespace) Parse(e annotations.Extractor) (any, error) {
+	return e.GetStringAnnotation(annotations.AnnotationsSvcNamespace), nil
+}
diff --git a/internal/adc/translator/ingress.go b/internal/adc/translator/ingress.go
index 5b6935f5..efb12cf7 100644
--- a/internal/adc/translator/ingress.go
+++ b/internal/adc/translator/ingress.go
@@ -181,7 +181,11 @@ func (t *Translator) resolveIngressUpstream(
 	backendService *networkingv1.IngressServiceBackend,
 	upstream *adctypes.Upstream,
 ) string {
-	backendRef := convertBackendRef(obj.Namespace, backendService.Name, internaltypes.KindService)
+	ns := obj.Namespace
+	if config != nil && config.ServiceNamespace != "" {
+		ns = config.ServiceNamespace
+	}
+	backendRef := convertBackendRef(ns, backendService.Name, internaltypes.KindService)
 	t.AttachBackendTrafficPolicyToUpstream(backendRef, tctx.BackendTrafficPolicies, upstream)
 	if config != nil {
 		upConfig := config.Upstream
@@ -209,7 +213,7 @@ func (t *Translator) resolveIngressUpstream(
 	}
 
 	getService := tctx.Services[types.NamespacedName{
-		Namespace: obj.Namespace,
+		Namespace: ns,
 		Name:      backendService.Name,
 	}]
 	if getService == nil {
@@ -238,7 +242,7 @@ func (t *Translator) resolveIngressUpstream(
 	}
 
 	endpointSlices := tctx.EndpointSlices[types.NamespacedName{
-		Namespace: obj.Namespace,
+		Namespace: ns,
 		Name:      backendService.Name,
 	}]
 	if len(endpointSlices) > 0 {
diff --git a/internal/controller/indexer/indexer.go b/internal/controller/indexer/indexer.go
index f6305791..223147ab 100644
--- a/internal/controller/indexer/indexer.go
+++ b/internal/controller/indexer/indexer.go
@@ -472,6 +472,10 @@ func IngressServiceIndexFunc(rawObj client.Object) []string {
 	ingress := rawObj.(*networkingv1.Ingress)
 	var services []string
 
+	ns := ingress.Namespace
+	if svcNs := ingress.Annotations[annotations.AnnotationsSvcNamespace]; svcNs != "" {
+		ns = svcNs
+	}
 	for _, rule := range ingress.Spec.Rules {
 		if rule.HTTP == nil {
 			continue
@@ -481,7 +485,7 @@ func IngressServiceIndexFunc(rawObj client.Object) []string {
 			if path.Backend.Service == nil {
 				continue
 			}
-			key := GenIndexKey(ingress.Namespace, path.Backend.Service.Name)
+			key := GenIndexKey(ns, path.Backend.Service.Name)
 			services = append(services, key)
 		}
 	}
diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go
index 7d5bfbf7..f8447ee6 100644
--- a/internal/controller/ingress_controller.go
+++ b/internal/controller/ingress_controller.go
@@ -410,6 +410,7 @@ func (r *IngressReconciler) listIngressForBackendTrafficPolicy(ctx context.Conte
 		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to BackendTrafficPolicy")
 		return nil
 	}
+
 	var namespacedNameMap = make(map[types.NamespacedName]struct{})
 	ingresses := []networkingv1.Ingress{}
 	for _, ref := range v.Spec.TargetRefs {
@@ -532,7 +533,11 @@ func (r *IngressReconciler) processBackends(tctx *provider.TranslateContext, ing
 				continue
 			}
 			service := path.Backend.Service
-			if err := r.processBackendService(tctx, ingress.Namespace, service); err != nil {
+			ns := ingress.Namespace
+			if svcNs := ingress.Annotations[annotations.AnnotationsSvcNamespace]; svcNs != "" {
+				ns = ingress.Annotations[annotations.AnnotationsSvcNamespace]
+			}
+			if err := r.processBackendService(tctx, ns, service); err != nil {
 				terr = err
 			}
 		}
diff --git a/internal/webhook/v1/ingress_webhook.go b/internal/webhook/v1/ingress_webhook.go
index 78bb4908..2c85c02c 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -40,16 +40,6 @@ var ingresslog = logf.Log.WithName("ingress-resource")
 // ref: https://apisix.apache.org/docs/ingress-controller/upgrade-guide/#limited-support-for-ingress-annotations
 var unsupportedAnnotations = []string{
 	"k8s.apisix.apache.org/use-regex",
-	"k8s.apisix.apache.org/rewrite-target",
-	"k8s.apisix.apache.org/rewrite-target-regex",
-	"k8s.apisix.apache.org/rewrite-target-regex-template",
-	"k8s.apisix.apache.org/enable-response-rewrite",
-	"k8s.apisix.apache.org/response-rewrite-status-code",
-	"k8s.apisix.apache.org/response-rewrite-body",
-	"k8s.apisix.apache.org/response-rewrite-body-base64",
-	"k8s.apisix.apache.org/response-rewrite-add-header",
-	"k8s.apisix.apache.org/response-rewrite-set-header",
-	"k8s.apisix.apache.org/response-rewrite-remove-header",
 	"k8s.apisix.apache.org/auth-uri",
 	"k8s.apisix.apache.org/auth-ssl-verify",
 	"k8s.apisix.apache.org/auth-request-headers",
@@ -58,7 +48,6 @@ var unsupportedAnnotations = []string{
 	"k8s.apisix.apache.org/allowlist-source-range",
 	"k8s.apisix.apache.org/blocklist-source-range",
 	"k8s.apisix.apache.org/auth-type",
-	"k8s.apisix.apache.org/svc-namespace",
 }
 
 // checkUnsupportedAnnotations checks if the Ingress contains any unsupported annotations



Test patch:
diff --git a/docs/en/latest/concepts/gateway-api.md b/docs/en/latest/concepts/gateway-api.md
index 8a5e5864..48569a0b 100644
--- a/docs/en/latest/concepts/gateway-api.md
+++ b/docs/en/latest/concepts/gateway-api.md
@@ -59,7 +59,7 @@ By supporting Gateway API, the APISIX Ingress controller can realize richer func
 
 For configuration examples, see the Gateway API tabs in [Configuration Examples](../reference/example.md).
 
-For a complete list of configuration options, refer to the [Gateway API Reference](https://gateway-api.sigs.k8s.io/reference/spec/). Be aware that some fields are not supported, or partially supported.
+For a complete list of configuration options, refer to the [Gateway API Reference](https://gateway-api.sigs.k8s.io/reference/1.3/spec/). Be aware that some fields are not supported, or partially supported.
 
 ## Unsupported / Partially Supported Fields
 
diff --git a/internal/adc/translator/annotations/plugins/response_rewrite_test.go b/internal/adc/translator/annotations/plugins/response_rewrite_test.go
new file mode 100644
index 00000000..1f7521b7
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/response_rewrite_test.go
@@ -0,0 +1,139 @@
+// 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 TestResponseRewriteHandler(t *testing.T) {
+	anno := map[string]string{
+		annotations.AnnotationsEnableResponseRewrite:       "true",
+		annotations.AnnotationsResponseRewriteStatusCode:   "200",
+		annotations.AnnotationsResponseRewriteBody:         "bar_body",
+		annotations.AnnotationsResponseRewriteBodyBase64:   "false",
+		annotations.AnnotationsResponseRewriteHeaderAdd:    "testkey1:testval1,testkey2:testval2",
+		annotations.AnnotationsResponseRewriteHeaderRemove: "testkey1,testkey2",
+		annotations.AnnotationsResponseRewriteHeaderSet:    "testkey1:testval1,testkey2:testval2",
+	}
+	p := NewResponseRewriteHandler()
+	out, err := p.Handle(annotations.NewExtractor(anno))
+	assert.Nil(t, err, "checking given error")
+	config := out.(*adctypes.ResponseRewriteConfig)
+	assert.Equal(t, 200, config.StatusCode)
+	assert.Equal(t, "bar_body", config.Body)
+	assert.Equal(t, false, config.BodyBase64)
+	assert.Equal(t, "response-rewrite", p.PluginName())
+	assert.Equal(t, []string{"testkey1:testval1", "testkey2:testval2"}, config.Headers.Add)
+	assert.Equal(t, []string{"testkey1", "testkey2"}, config.Headers.Remove)
+	assert.Equal(t, map[string]string{
+		"testkey1": "testval1",
+		"testkey2": "testval2",
+	}, config.Headers.Set)
+}
+
+func TestResponseRewriteHandlerDisabled(t *testing.T) {
+	anno := map[string]string{
+		annotations.AnnotationsEnableResponseRewrite:     "false",
+		annotations.AnnotationsResponseRewriteStatusCode: "400",
+		annotations.AnnotationsResponseRewriteBody:       "bar_body",
+	}
+	p := NewResponseRewriteHandler()
+	out, err := p.Handle(annotations.NewExtractor(anno))
+	assert.Nil(t, err, "checking given error")
+	assert.Nil(t, out, "checking given output")
+}
+
+func TestResponseRewriteHandlerBase64(t *testing.T) {
+	anno := map[string]string{
+		annotations.AnnotationsEnableResponseRewrite:     "true",
+		annotations.AnnotationsResponseRewriteBody:       "YmFyLWJvZHk=",
+		annotations.AnnotationsResponseRewriteBodyBase64: "true",
+	}
+	p := NewResponseRewriteHandler()
+	out, err := p.Handle(annotations.NewExtractor(anno))
+	assert.Nil(t, err, "checking given error")
+	config := out.(*adctypes.ResponseRewriteConfig)
+	assert.Equal(t, "YmFyLWJvZHk=", config.Body)
+	assert.Equal(t, true, config.BodyBase64)
+}
+
+func TestResponseRewriteHandlerInvalidStatusCode(t *testing.T) {
+	anno := map[string]string{
+		annotations.AnnotationsEnableResponseRewrite:     "true",
+		annotations.AnnotationsResponseRewriteStatusCode: "invalid",
+		annotations.AnnotationsResponseRewriteBody:       "bar_body",
+	}
+	p := NewResponseRewriteHandler()
+	out, err := p.Handle(annotations.NewExtractor(anno))
+	assert.Nil(t, err, "checking given error")
+	config := out.(*adctypes.ResponseRewriteConfig)
+	assert.Equal(t, 0, config.StatusCode, "invalid status code should default to 0")
+	assert.Equal(t, "bar_body", config.Body)
+}
+
+func TestParseHeaderKeyValue(t *testing.T) {
+	tests := []struct {
+		name      string
+		input     string
+		wantKey   string
+		wantValue string
+		wantFound bool
+	}{
+		{
+			name:      "valid header",
+			input:     "Content-Type:application/json",
+			wantKey:   "Content-Type",
+			wantValue: "application/json",
+			wantFound: true,
+		},
+		{
+			name:      "header with colon in value",
+			input:     "X-Custom:value:with:colons",
+			wantKey:   "X-Custom",
+			wantValue: "value:with:colons",
+			wantFound: true,
+		},
+		{
+			name:      "invalid header without colon",
+			input:     "InvalidHeader",
+			wantKey:   "",
+			wantValue: "",
+			wantFound: false,
+		},
+		{
+			name:      "empty value",
+			input:     "X-Empty:",
+			wantKey:   "X-Empty",
+			wantValue: "",
+			wantFound: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			key, value, found := parseHeaderKeyValue(tt.input)
+			assert.Equal(t, tt.wantKey, key)
+			assert.Equal(t, tt.wantValue, value)
+			assert.Equal(t, tt.wantFound, found)
+		})
+	}
+}
diff --git a/internal/adc/translator/annotations/plugins/rewrite_test.go b/internal/adc/translator/annotations/plugins/rewrite_test.go
new file mode 100644
index 00000000..99f89c6c
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/rewrite_test.go
@@ -0,0 +1,87 @@
+// 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 TestRewriteHandler(t *testing.T) {
+	t.Run("rewrite target", func(t *testing.T) {
+		anno := map[string]string{
+			annotations.AnnotationsRewriteTarget: "/new-path",
+		}
+		p := NewRewriteHandler()
+		out, err := p.Handle(annotations.NewExtractor(anno))
+		assert.Nil(t, err, "checking given error")
+		assert.NotNil(t, out, "checking given output")
+		config := out.(*adctypes.RewriteConfig)
+		assert.Equal(t, "/new-path", config.RewriteTarget)
+		assert.Nil(t, config.RewriteTargetRegex)
+		assert.Equal(t, "proxy-rewrite", p.PluginName())
+	})
+
+	t.Run("rewrite target with regex", func(t *testing.T) {
+		anno := map[string]string{
+			annotations.AnnotationsRewriteTargetRegex:         "/sample/(.*)",
+			annotations.AnnotationsRewriteTargetRegexTemplate: "/$1",
+		}
+		p := NewRewriteHandler()
+		out, err := p.Handle(annotations.NewExtractor(anno))
+		assert.Nil(t, err, "checking given error")
+		assert.NotNil(t, out, "checking given output")
+		config := out.(*adctypes.RewriteConfig)
+		assert.Equal(t, "", config.RewriteTarget)
+		assert.NotNil(t, config.RewriteTargetRegex)
+		assert.Equal(t, []string{"/sample/(.*)", "/$1"}, config.RewriteTargetRegex)
+	})
+
+	t.Run("invalid regex", func(t *testing.T) {
+		anno := map[string]string{
+			annotations.AnnotationsRewriteTargetRegex:         "[invalid(regex",
+			annotations.AnnotationsRewriteTargetRegexTemplate: "/$1",
+		}
+		p := NewRewriteHandler()
+		out, err := p.Handle(annotations.NewExtractor(anno))
+		assert.NotNil(t, err, "checking given error")
+		assert.Nil(t, out, "checking given output")
+	})
+
+	t.Run("no annotations", func(t *testing.T) {
+		anno := map[string]string{}
+		p := NewRewriteHandler()
+		out, err := p.Handle(annotations.NewExtractor(anno))
+		assert.Nil(t, err, "checking given error")
+		assert.Nil(t, out, "checking given output")
+	})
+
+	t.Run("only regex without template", func(t *testing.T) {
+		anno := map[string]string{
+			annotations.AnnotationsRewriteTargetRegex: "/sample/(.*)",
+		}
+		p := NewRewriteHandler()
+		out, err := p.Handle(annotations.NewExtractor(anno))
+		assert.Nil(t, err, "checking given error")
+		assert.NotNil(t, out, "checking given output")
+		config := out.(*adctypes.RewriteConfig)
+		assert.Nil(t, config.RewriteTargetRegex, "regex should not be set without template")
+	})
+}
diff --git a/internal/adc/translator/annotations_test.go b/internal/adc/translator/annotations_test.go
index c94416d0..46d4550a 100644
--- a/internal/adc/translator/annotations_test.go
+++ b/internal/adc/translator/annotations_test.go
@@ -257,6 +257,37 @@ func TestTranslateIngressAnnotations(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "auth type basicAuth",
+			anno: map[string]string{
+				annotations.AnnotationsAuthType: "basicAuth",
+			},
+			expected: &IngressConfig{
+				Plugins: adctypes.Plugins{
+					"basic-auth": &adctypes.BasicAuthConfig{},
+				},
+			},
+		},
+		{
+			name: "auth type keyAuth",
+			anno: map[string]string{
+				annotations.AnnotationsAuthType: "keyAuth",
+			},
+			expected: &IngressConfig{
+				Plugins: adctypes.Plugins{
+					"key-auth": &adctypes.KeyAuthConfig{},
+				},
+			},
+		},
+		{
+			name: "service namespace",
+			anno: map[string]string{
+				annotations.AnnotationsSvcNamespace: "custom-namespace",
+			},
+			expected: &IngressConfig{
+				ServiceNamespace: "custom-namespace",
+			},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/test/e2e/framework/k8s.go b/test/e2e/framework/k8s.go
index 3fb71598..a943a1b0 100644
--- a/test/e2e/framework/k8s.go
+++ b/test/e2e/framework/k8s.go
@@ -175,6 +175,16 @@ func (f *Framework) CreateNamespaceWithTestService(name string) {
 	}
 }
 
+func (f *Framework) CreateNamespace(name string) {
+	_, err := f.clientset.CoreV1().
+		Namespaces().
+		Create(f.Context, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}, metav1.CreateOptions{})
+	if err != nil && !errors.IsAlreadyExists(err) {
+		f.GomegaT.Expect(err).ShouldNot(HaveOccurred(), "create namespace")
+		return
+	}
+}
+
 func (f *Framework) DeleteNamespace(name string) {
 	err := f.clientset.CoreV1().Namespaces().Delete(f.Context, name, metav1.DeleteOptions{})
 	if err == nil || errors.IsNotFound(err) {
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index ad559528..61551015 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -266,10 +266,19 @@ spec:
 		It("websocket", func() {
 			Expect(s.CreateResourceFromString(fmt.Sprintf(ingressWebSocket, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
 
-			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].EnableWebsocket).To(Equal(ptr.To(true)), "checking Route EnableWebsocket")
+			Eventually(func() bool {
+				routes, err := s.DefaultDataplaneResource().Route().List(context.Background())
+				if err != nil {
+					return false
+				}
+				if len(routes) != 1 {
+					return false
+				}
+				if routes[0].EnableWebsocket == nil || !*routes[0].EnableWebsocket {
+					return false
+				}
+				return true
+			}).WithTimeout(30 * time.Second).ProbeEvery(2 * time.Second).Should(BeTrue())
 		})
 	})
 
@@ -382,6 +391,144 @@ 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
+`
+
+			ingressRewriteTarget = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: rewrite-target
+  annotations:
+    k8s.apisix.apache.org/rewrite-target: "/get"
+spec:
+  ingressClassName: %s
+  rules:
+  - host: httpbin.example
+    http:
+      paths:
+      - path: /test
+        pathType: Exact
+        backend:
+          service:
+            name: httpbin-service-e2e-test
+            port:
+              number: 80
+`
+
+			ingressRewriteTargetRegex = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: rewrite-target-regex
+  annotations:
+    k8s.apisix.apache.org/rewrite-target-regex: "/sample/(.*)"
+    k8s.apisix.apache.org/rewrite-target-regex-template: "/$1"
+spec:
+  ingressClassName: %s
+  rules:
+  - host: httpbin-regex.example
+    http:
+      paths:
+      - path: /sample
+        pathType: Prefix
+        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() {
@@ -610,5 +757,262 @@ 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("proxy-rewrite with rewrite-target", func() {
+			Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTarget, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+			s.RequestAssert(&scaffold.RequestAssert{
+				Method:  "GET",
+				Path:    "/test",
+				Host:    "httpbin.example",
+				Timeout: 60 * time.Second,
+				Check:   scaffold.WithExpectedStatus(http.StatusOK),
+			})
+
+			routes, err := s.DefaultDataplaneResource().Route().List(context.Background())
+			Expect(err).NotTo(HaveOccurred(), "listing Route")
+			Expect(routes).ToNot(BeEmpty(), "checking Route length")
+			Expect(routes[0].Plugins).To(HaveKey("proxy-rewrite"), "checking Route has proxy-rewrite plugin")
+
+			jsonBytes, err := json.Marshal(routes[0].Plugins["proxy-rewrite"])
+			Expect(err).NotTo(HaveOccurred(), "marshalling proxy-rewrite plugin config")
+			var rewriteConfig map[string]any
+			err = json.Unmarshal(jsonBytes, &rewriteConfig)
+			Expect(err).NotTo(HaveOccurred(), "unmarshalling proxy-rewrite plugin config")
+			Expect(rewriteConfig["uri"]).To(Equal("/get"), "checking proxy-rewrite uri")
+		})
+
+		It("proxy-rewrite with regex", func() {
+			Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRewriteTargetRegex, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+			s.RequestAssert(&scaffold.RequestAssert{
+				Method:  "GET",
+				Path:    "/sample/get",
+				Host:    "httpbin-regex.example",
+				Timeout: 60 * time.Second,
+				Check:   scaffold.WithExpectedStatus(http.StatusOK),
+			})
+
+			s.RequestAssert(&scaffold.RequestAssert{
+				Method: "GET",
+				Path:   "/sample/anything",
+				Host:   "httpbin-regex.example",
+				Check:  scaffold.WithExpectedStatus(http.StatusOK),
+			})
+
+			routes, err := s.DefaultDataplaneResource().Route().List(context.Background())
+			Expect(err).NotTo(HaveOccurred(), "listing Route")
+			Expect(routes).ToNot(BeEmpty(), "checking Route length")
+			Expect(routes[0].Plugins).To(HaveKey("proxy-rewrite"), "checking Route has proxy-rewrite plugin")
+
+			jsonBytes, err := json.Marshal(routes[0].Plugins["proxy-rewrite"])
+			Expect(err).NotTo(HaveOccurred(), "marshalling proxy-rewrite plugin config")
+			var rewriteConfig map[string]any
+			err = json.Unmarshal(jsonBytes, &rewriteConfig)
+			Expect(err).NotTo(HaveOccurred(), "unmarshalling proxy-rewrite plugin config")
+
+			regexUri, ok := rewriteConfig["regex_uri"].([]any)
+			Expect(ok).To(BeTrue(), "checking regex_uri is array")
+			Expect(regexUri).To(HaveLen(2), "checking regex_uri length")
+			Expect(regexUri[0]).To(Equal("/sample/(.*)"), "checking regex pattern")
+			Expect(regexUri[1]).To(Equal("/$1"), "checking regex template")
+		})
+
+		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")
+		})
+	})
+
+	Context("Service Namespace", func() {
+		var (
+			ns  string
+			svc = `
+apiVersion: v1
+kind: Service
+metadata:
+  name: httpbin-external-domain
+spec:
+  type: ExternalName
+  externalName: httpbin-service-e2e-test.%s.svc
+`
+			ingressSvcNamespace = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: retries
+  annotations:
+    k8s.apisix.apache.org/svc-namespace: %s
+spec:
+  ingressClassName: %s
+  rules:
+  - host: httpbin.example
+    http:
+      paths:
+      - path: /get
+        pathType: Exact
+        backend:
+          service:
+            name: httpbin-external-domain
+            port:
+              number: 80
+`
+		)
+		BeforeEach(func() {
+			ns = s.Namespace() + "-v2"
+			s.CreateNamespace(ns)
+			err := s.CreateResourceFromStringWithNamespace(fmt.Sprintf(svc, s.Namespace()), ns)
+			Expect(err).NotTo(HaveOccurred(), "creating Service in custom namespace")
+
+			By("create GatewayProxy")
+			Expect(s.CreateResourceFromString(s.GetGatewayProxySpec())).NotTo(HaveOccurred(), "creating GatewayProxy")
+
+			By("create IngressClass")
+			err = s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
+			Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
+			time.Sleep(5 * time.Second)
+		})
+		AfterEach(func() {
+			s.DeleteNamespace(ns)
+		})
+		It("svc-namespace", func() {
+			Expect(s.CreateResourceFromString(fmt.Sprintf(ingressSvcNamespace, ns, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+			s.RequestAssert(&scaffold.RequestAssert{
+				Method: "GET",
+				Path:   "/get",
+				Host:   "httpbin.example",
+				Check:  scaffold.WithExpectedStatus(http.StatusOK),
+			})
+		})
 	})
 })