From a138a037ad7c92e54a89ca89a7eb9ec6a15de6a0 Mon Sep 17 00:00:00 2001 From: Se7en <40051120+cr7258@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:39:41 +0800 Subject: [PATCH] feat: implement custom-response plugin in the golang version (#689) --- .../extensions/custom-response/README.md | 84 +++++++++ .../extensions/custom-response/README_EN.md | 84 +++++++++ .../extensions/custom-response/envoy.yaml | 71 ++++++++ .../wasm-go/extensions/custom-response/go.mod | 20 +++ .../wasm-go/extensions/custom-response/go.sum | 15 ++ .../extensions/custom-response/main.go | 135 +++++++++++++++ .../tests/go-wasm-custom-response.go | 159 ++++++++++++++++++ .../tests/go-wasm-custom-response.yaml | 118 +++++++++++++ 8 files changed, 686 insertions(+) create mode 100644 plugins/wasm-go/extensions/custom-response/README.md create mode 100644 plugins/wasm-go/extensions/custom-response/README_EN.md create mode 100644 plugins/wasm-go/extensions/custom-response/envoy.yaml create mode 100644 plugins/wasm-go/extensions/custom-response/go.mod create mode 100644 plugins/wasm-go/extensions/custom-response/go.sum create mode 100644 plugins/wasm-go/extensions/custom-response/main.go create mode 100644 test/e2e/conformance/tests/go-wasm-custom-response.go create mode 100644 test/e2e/conformance/tests/go-wasm-custom-response.yaml diff --git a/plugins/wasm-go/extensions/custom-response/README.md b/plugins/wasm-go/extensions/custom-response/README.md new file mode 100644 index 000000000..ffeb98166 --- /dev/null +++ b/plugins/wasm-go/extensions/custom-response/README.md @@ -0,0 +1,84 @@ +
+ English | 中文 +
+ +# 功能说明 +`custom-response`插件支持配置自定义的响应,包括自定义 HTTP 应答状态码、HTTP 应答头,以及 HTTP 应答 Body。可以用于 Mock 响应,也可以用于判断特定状态码后给出自定义应答,例如在触发网关限流策略时实现自定义响应。 + +# 配置字段 + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +| -------- | -------- | -------- | -------- | -------- | +| status_code | number | 选填 | 200 | 自定义 HTTP 应答状态码 | +| headers | array of string | 选填 | - | 自定义 HTTP 应答头,key 和 value 用`=`分隔 | +| body | string | 选填 | - | 自定义 HTTP 应答 Body | +| enable_on_status | array of number | 选填 | - | 匹配原始状态码,生成自定义响应,不填写时,不判断原始状态码 | + +# 配置示例 + +## Mock 应答场景 + +```yaml +status_code: 200 +headers: +- Content-Type=application/json +- Hello=World +body: "{\"hello\":\"world\"}" + +``` + +根据该配置,请求将返回自定义应答如下: + +```text +HTTP/1.1 200 OK +Content-Type: application/json +Hello: World +Content-Length: 17 + +{"hello":"world"} +``` + +## 触发限流时自定义响应 + +```yaml +enable_on_status: +- 429 +status_code: 302 +headers: +- Location=https://example.com +``` + +触发网关限流时一般会返回 `429` 状态码,这时请求将返回自定义应答如下: + +```text +HTTP/1.1 302 Found +Location: https://example.com +``` + +从而实现基于浏览器 302 重定向机制,将限流后的用户引导到其他页面,比如可以是一个 CDN 上的静态页面。 + +如果希望触发限流时,正常返回其他应答,参考 Mock 应答场景配置相应的字段即可。 + +## 对特定路由或域名开启 +```yaml +# 使用 matchRules 字段进行细粒度规则配置 +matchRules: +# 规则一:按 Ingress 名称匹配生效 +- ingress: + - default/foo + - default/bar + body: "{\"hello\":\"world\"}" +# 规则二:按域名匹配生效 +- domain: + - "*.example.com" + - test.com + enable_on_status: + - 429 + status_code: 200 + headers: + - Content-Type=application/json + body: "{\"errmsg\": \"rate limited\"}" +``` +此例 `ingress` 中指定的 `default/foo` 和 `default/bar` 对应 default 命名空间下名为 foo 和 bar 的 Ingress,当匹配到这两个 Ingress 时,将使用此段配置; +此例 `domain` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置; +配置的匹配生效顺序,将按照 `matchRules` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。 diff --git a/plugins/wasm-go/extensions/custom-response/README_EN.md b/plugins/wasm-go/extensions/custom-response/README_EN.md new file mode 100644 index 000000000..d8df25153 --- /dev/null +++ b/plugins/wasm-go/extensions/custom-response/README_EN.md @@ -0,0 +1,84 @@ ++ English | 中文 +
+ +# Description +`custom-response` plugin implements a function of sending custom responses, including custom HTTP response status codes, HTTP response headers and HTTP response body, which can be used in the scenarios of response mocking and sending a custom response for specific status codes, such as customizing the response for rate-limited requests. + +# Configuration Fields + +| Name | Type | Requirement | Default Value | Description | +| -------- | -------- | -------- | -------- | -------- | +| status_code | number | Optional | 200 | Custom HTTP response status code | +| headers | array of string | Optional | - | Custom HTTP response header. Key and value shall be separated using `=`. | +| body | string | Optional | - | Custom HTTP response body | +| enable_on_status | array of number | Optional | - | The original response status code to match. Generate the custom response only the actual status code matches the configuration. Ignore the status code match if left unconfigured. | + +# Configuration Samples + +## Mock Responses + +```yaml +status_code: 200 +headers: +- Content-Type=application/json +- Hello=World +body: "{\"hello\":\"world\"}" + +``` + +According to the configuration above, all the requests will get the following custom response: + +```text +HTTP/1.1 200 OK +Content-Type: application/json +Hello: World +Content-Length: 17 + +{"hello":"world"} +``` + +## Send a Custom Response when Rate-Limited + +```yaml +enable_on_status: +- 429 +status_code: 302 +headers: +- Location=https://example.com +``` + +When rate-limited, normally gateway will return a status code of `429` . Now, rate-limited requests will get the following custom response: + +```text +HTTP/1.1 302 Found +Location: https://example.com +``` + +So based on the 302 redirecting mechanism provided by browsers, this can redirect rate-limited users to other pages, for example, a static page hosted on CDN. + +If you'd like to send other responses when rate-limited, please add other fields into the configuration, referring to the Mock Responses scenario. + +## Only Enabled for Specific Routes or Domains +```yaml +# Use matchRules field for fine-grained rule configurations +matchRules: +# Rule 1: Match by Ingress name +- ingress: + - default/foo + - default/bar + body: "{\"hello\":\"world\"}" +# Rule 2: Match by domain +- domain: + - "*.example.com" + - test.com + enable_on_status: + - 429 + status_code: 200 + headers: + - Content-Type=application/json + body: "{\"errmsg\": \"rate limited\"}" +``` +In the rule sample of `ingress`, `default/foo` and `default/bar` are the Ingresses named foo and bar in the default namespace. When the current Ingress names matches the configuration, the rule following shall be applied. +In the rule sample of `domain`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied. +All rules shall be checked following the order of items in the `matchRules` field, The first matched rule will be applied. All remained will be ignored. diff --git a/plugins/wasm-go/extensions/custom-response/envoy.yaml b/plugins/wasm-go/extensions/custom-response/envoy.yaml new file mode 100644 index 000000000..ff27982d4 --- /dev/null +++ b/plugins/wasm-go/extensions/custom-response/envoy.yaml @@ -0,0 +1,71 @@ +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + scheme_header_transformation: + scheme_to_overwrite: https + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: httpbin + http_filters: + - name: wasmdemo + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + name: wasmdemo + vm_config: + runtime: envoy.wasm.runtime.v8 + code: + local: + filename: /etc/envoy/main.wasm + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: | + { + "headers": ["key1=value1", "key2=value2"], + "status_code": 200, + "enable_on_status": [200, 201], + "body": "{\"hello\":\"world\"}" + } + - name: envoy.filters.http.router + clusters: + - name: httpbin + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: httpbin + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: httpbin + port_value: 80 diff --git a/plugins/wasm-go/extensions/custom-response/go.mod b/plugins/wasm-go/extensions/custom-response/go.mod new file mode 100644 index 000000000..7ccd8a332 --- /dev/null +++ b/plugins/wasm-go/extensions/custom-response/go.mod @@ -0,0 +1,20 @@ +module github.com/alibaba/higress/plugins/wasm-go/extensions/basic-auth + +go 1.19 + +replace github.com/alibaba/higress/plugins/wasm-go => ../.. + +require ( + github.com/alibaba/higress/plugins/wasm-go v0.0.0 + github.com/pkg/errors v0.9.1 + github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 + github.com/tidwall/gjson v1.14.3 +) + +require ( + github.com/google/uuid v1.3.0 // indirect + github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect + github.com/magefile/mage v1.14.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect +) diff --git a/plugins/wasm-go/extensions/custom-response/go.sum b/plugins/wasm-go/extensions/custom-response/go.sum new file mode 100644 index 000000000..06846f3b3 --- /dev/null +++ b/plugins/wasm-go/extensions/custom-response/go.sum @@ -0,0 +1,15 @@ +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA= +github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= +github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M= +github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/plugins/wasm-go/extensions/custom-response/main.go b/plugins/wasm-go/extensions/custom-response/main.go new file mode 100644 index 000000000..825efcc7c --- /dev/null +++ b/plugins/wasm-go/extensions/custom-response/main.go @@ -0,0 +1,135 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed 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 main + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" + "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" + "github.com/tidwall/gjson" +) + +func main() { + wrapper.SetCtx( + "custom-response", + wrapper.ParseConfigBy(parseConfig), + wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), + wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders), + ) +} + +type CustomResponseConfig struct { + statusCode uint32 + headers [][2]string + body string + enableOnStatus []uint32 + contentType string +} + +func parseConfig(gjson gjson.Result, config *CustomResponseConfig, log wrapper.Log) error { + headersArray := gjson.Get("headers").Array() + config.headers = make([][2]string, 0, len(headersArray)) + for _, v := range headersArray { + kv := strings.SplitN(v.String(), "=", 2) + if len(kv) == 2 { + key := strings.TrimSpace(kv[0]) + value := strings.TrimSpace(kv[1]) + if strings.EqualFold(key, "content-type") { + config.contentType = value + } else if strings.EqualFold(key, "content-length") { + continue + } else { + config.headers = append(config.headers, [2]string{key, value}) + } + } else { + return fmt.Errorf("invalid header pair format: %s", v.String()) + } + } + + config.body = gjson.Get("body").String() + if config.contentType == "" && config.body != "" { + if json.Valid([]byte(config.body)) { + config.contentType = "application/json; charset=utf-8" + } else { + config.contentType = "text/plain; charset=utf-8" + } + } + config.headers = append(config.headers, [2]string{"content-type", config.contentType}) + + config.statusCode = 200 + if gjson.Get("status_code").Exists() { + statusCode := gjson.Get("status_code") + parsedStatusCode, err := strconv.Atoi(statusCode.String()) + if err != nil { + return fmt.Errorf("invalid status code value: %s", statusCode.String()) + } + config.statusCode = uint32(parsedStatusCode) + } + + enableOnStatusArray := gjson.Get("enable_on_status").Array() + config.enableOnStatus = make([]uint32, 0, len(enableOnStatusArray)) + for _, v := range enableOnStatusArray { + parsedEnableOnStatus, err := strconv.Atoi(v.String()) + if err != nil { + return fmt.Errorf("invalid enable_on_status value: %s", v.String()) + } + config.enableOnStatus = append(config.enableOnStatus, uint32(parsedEnableOnStatus)) + } + + return nil +} + +func onHttpRequestHeaders(ctx wrapper.HttpContext, config CustomResponseConfig, log wrapper.Log) types.Action { + if len(config.enableOnStatus) != 0 { + return types.ActionContinue + } + err := proxywasm.SendHttpResponse(config.statusCode, config.headers, []byte(config.body), -1) + if err != nil { + log.Errorf("send http response failed: %v", err) + } + + return types.ActionPause +} + +func onHttpResponseHeaders(ctx wrapper.HttpContext, config CustomResponseConfig, log wrapper.Log) types.Action { + // enableOnStatus is not empty, compare the status code. + // if match the status code, mock the response. + statusCodeStr, err := proxywasm.GetHttpResponseHeader(":status") + if err != nil { + log.Errorf("get http response status code failed: %v", err) + return types.ActionContinue + } + statusCode, err := strconv.ParseUint(statusCodeStr, 10, 32) + if err != nil { + log.Errorf("parse http response status code failed: %v", err) + return types.ActionContinue + } + + for _, v := range config.enableOnStatus { + if uint32(statusCode) == v { + err = proxywasm.SendHttpResponse(config.statusCode, config.headers, []byte(config.body), -1) + if err != nil { + log.Errorf("send http response failed: %v", err) + } + } + } + + return types.ActionContinue +} diff --git a/test/e2e/conformance/tests/go-wasm-custom-response.go b/test/e2e/conformance/tests/go-wasm-custom-response.go new file mode 100644 index 000000000..c6c7ba7d5 --- /dev/null +++ b/test/e2e/conformance/tests/go-wasm-custom-response.go @@ -0,0 +1,159 @@ +// Copyright (c) 2022 Alibaba Group Holding Ltd. +// +// Licensed 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 tests + +import ( + "testing" + + "github.com/alibaba/higress/test/e2e/conformance/utils/http" + "github.com/alibaba/higress/test/e2e/conformance/utils/suite" +) + +func init() { + Register(WasmPluginsCustomResponse) +} + +var WasmPluginsCustomResponse = suite.ConformanceTest{ + ShortName: "WasmPluginsCustomResponse", + Description: "The Ingress in the higress-conformance-infra namespace test the custom-response WASM plugin.", + Manifests: []string{"tests/go-wasm-custom-response.yaml"}, + Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testcases := []http.Assertion{ + { + Meta: http.AssertionMeta{ + TestCaseName: "case 1: Match global config", + CompareTarget: http.CompareTargetResponse, + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo.com", + Path: "/", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + Headers: map[string]string{ + "key1": "value1", + }, + ContentType: http.ContentTypeApplicationJson, + Body: []byte("{\"hello\":\"foo\"}"), + }, + }, + }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 2: Match rule config", + CompareTarget: http.CompareTargetResponse, + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "bar.com", + Path: "/", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + Headers: map[string]string{ + "key2": "value2", + }, + ContentType: http.ContentTypeApplicationJson, + Body: []byte("{\"hello\":\"bar\"}"), + }, + }, + }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 3: Match enable_on_status", + CompareTarget: http.CompareTargetResponse, + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "baz.com", + Path: "/", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + Headers: map[string]string{ + "key3": "value3", + }, + ContentType: http.ContentTypeApplicationJson, + Body: []byte("{\"hello\":\"baz\"}"), + }, + }, + }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 4: Not match enable_on_status", + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + CompareTarget: http.CompareTargetRequest, + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "baz.com", + Path: "/", + Headers: map[string]string{"Authorization": "Basic YWRtaW46MTIzNDU2"}, // base64("admin:123456") + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Host: "baz.com", + Path: "/", + Headers: map[string]string{ + "X-Mse-Consumer": "consumer1", + }, + }, + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + { + Meta: http.AssertionMeta{ + TestCaseName: "case 5: Change status code", + CompareTarget: http.CompareTargetResponse, + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "qux.com", + Path: "/", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 201, + Headers: map[string]string{ + "key5": "value5", + }, + ContentType: http.ContentTypeApplicationJson, + Body: []byte("{\"hello\":\"qux\"}"), + }, + }, + }, + } + t.Run("WasmPlugins custom-response", func(t *testing.T) { + for _, testcase := range testcases { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase) + } + }) + }, +} diff --git a/test/e2e/conformance/tests/go-wasm-custom-response.yaml b/test/e2e/conformance/tests/go-wasm-custom-response.yaml new file mode 100644 index 000000000..6c0ab6ecf --- /dev/null +++ b/test/e2e/conformance/tests/go-wasm-custom-response.yaml @@ -0,0 +1,118 @@ +# Copyright (c) 2022 Alibaba Group Holding Ltd. +# +# Licensed 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. + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wasmplugin-custom-response + namespace: higress-conformance-infra +spec: + ingressClassName: higress + rules: + - host: "foo.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 + - host: "bar.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 + - host: "baz.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 + - host: "qux.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: custom-response + namespace: higress-system +spec: + priority: 200 # ensure custom-response plugin is applied before basic-auth plugin + defaultConfig: + headers: + - key1=value1 + "body": "{\"hello\":\"foo\"}" + matchRules: + - domain: + - bar.com + config: + headers: + - key2=value2 + "body": "{\"hello\":\"bar\"}" + - domain: + - baz.com + config: + enable_on_status: + - 401 + headers: + - key3=value3 + "body": "{\"hello\":\"baz\"}" + - domain: + - qux.com + config: + status_code: 201 + headers: + - key5=value5 + "body": "{\"hello\":\"qux\"}" + url: file:///opt/plugins/wasm-go/extensions/custom-response/plugin.wasm +--- +# test enable_on_status 401 +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: basic-auth + namespace: higress-system +spec: + priority: 100 + defaultConfig: + consumers: + - credential: admin:123456 + name: consumer1 + global_auth: false + matchRules: + - config: + allow: + - consumer1 + domain: + - baz.com + url: file:///opt/plugins/wasm-go/extensions/basic-auth/plugin.wasm