From 08c64ed467d0c1112a70a71b11ff24878c57286f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E8=B4=A4=E6=B6=9B?= <601803023@qq.com> Date: Mon, 5 Aug 2024 11:04:31 +0800 Subject: [PATCH] fix:fix bug in ext-auth wasm plugin (#1152) --- plugins/wasm-go/extensions/ext-auth/.buildrc | 1 + plugins/wasm-go/extensions/ext-auth/README.md | 165 +++++++++++++++--- plugins/wasm-go/extensions/ext-auth/config.go | 152 ++++++++-------- .../extensions/ext-auth/expr/matcher.go | 2 +- plugins/wasm-go/extensions/ext-auth/go.mod | 5 +- plugins/wasm-go/extensions/ext-auth/go.sum | 11 +- plugins/wasm-go/extensions/ext-auth/main.go | 47 ++--- .../wasm-go/pkg/wrapper/request_wrapper.go | 14 ++ 8 files changed, 275 insertions(+), 122 deletions(-) create mode 100644 plugins/wasm-go/extensions/ext-auth/.buildrc diff --git a/plugins/wasm-go/extensions/ext-auth/.buildrc b/plugins/wasm-go/extensions/ext-auth/.buildrc new file mode 100644 index 000000000..f76a2883a --- /dev/null +++ b/plugins/wasm-go/extensions/ext-auth/.buildrc @@ -0,0 +1 @@ +EXTRA_TAGS=proxy_wasm_version_0_2_100 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/ext-auth/README.md b/plugins/wasm-go/extensions/ext-auth/README.md index 4dc306cd9..ecffb6f6d 100644 --- a/plugins/wasm-go/extensions/ext-auth/README.md +++ b/plugins/wasm-go/extensions/ext-auth/README.md @@ -1,10 +1,16 @@ -# 功能说明 +--- +title: 外部认证 +keywords: [higress, auth] +description: Ext 认证插件实现了调用外部授权服务进行认证鉴权的功能。 +--- + +## 功能说明 `ext-auth` 插件实现了向外部授权服务发送鉴权请求,以检查客户端请求是否得到授权。该插件实现时参考了Envoy原生的[ext_authz filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter),实现了原生filter中对接HTTP服务的部分能力 -# 配置字段 +## 配置字段 | 名称 | 数据类型 | 必填 | 默认值 | 描述 | | ------------------------------- | -------- | ---- | ------ |------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -17,29 +23,30 @@ | 名称 | 数据类型 | 必填 | 默认值 | 描述 | | ------------------------ | -------- | ---- | ------ | ------------------------------------- | +| `endpoint_mode` | string | 否 | envoy | `envoy` , `forward_auth` 中选填一项 | | `endpoint` | object | 是 | - | 发送鉴权请求的 HTTP 服务信息 | -| `timeout` | int | 否 | 200 | `ext-auth` 服务连接超时时间,单位毫秒 | +| `timeout` | int | 否 | 1000 | `ext-auth` 服务连接超时时间,单位毫秒 | | `authorization_request` | object | 否 | - | 发送鉴权请求配置 | -| `authorization_response` | object | 否 | - | 处理鉴权响应配置 | +| `authorization_response` | object | 否 | - | 处理鉴权响应配置 | `endpoint`中每一项的配置字段说明 -| 名称 | 数据类型 | 必填 | 默认值 | 描述 | -| ---------------- | -------- | ---- | ------ | --------------------------------------------------- | -| `service_source` | string | 是 | - | 类型为固定 ip 或者 dns,输入授权服务的注册来源 | -| `service_name` | string | 是 | - | 输入授权服务的注册名称 | -| `service_port` | string | 是 | - | 输入授权服务的服务端口 | -| `service_domain` | string | 否 | - | 当类型为dns时必须填写,输入 `ext-auth` 服务的domain | -| `request_method` | string | 否 | GET | 客户端向授权服务发送请求的HTTP Method | -| `path` | string | 是 | - | 输入授权服务的请求路径 | +| 名称 | 数据类型 | 必填 | 默认值 | 描述 | +| -------- | -------- | -- | ------ |-----------------------------------------------------------------------------------------| +| `service_name` | string | 必填 | - | 输入授权服务名称,带服务类型的完整 FQDN 名称,例如 `ext-auth.dns` 、`ext-auth.my-ns.svc.cluster.local` | +| `service_port` | int | 否 | 80 | 输入授权服务的服务端口 | +| `path_prefix` | string | `endpoint_mode` 为`envoy`时必填 | | `endpoint_mode` 为`envoy` 时,客户端向授权服务发送请求的请求路径前缀 | +| `request_method` | string | 否 | GET | `endpoint_mode` 为`forward_auth` 时,客户端向授权服务发送请求的HTTP Method | +| `path` | string | `endpoint_mode` 为`forward_auth`时必填 | - | `endpoint_mode` 为`forward_auth` 时,客户端向授权服务发送请求的请求路径 | `authorization_request`中每一项的配置字段说明 -| 名称 | 数据类型 | 必填 | 默认值 | 描述 | -| ------------------- | ---------------------- | ---- | ------ | ------------------------------------------------------------ | -| `allowed_headers` | array of StringMatcher | 否 | - | 当设置后,具有相应匹配项的客户端请求头将添加到授权服务请求中的请求头中。除了用户自定义的头部匹配规则外,授权服务请求中会自动包含`Host`, `Method`, `Path`, `Content-Length` 和 `Authorization`这几个关键的HTTP头 | -| `headers_to_add` | `map[string]string` | 否 | - | 设置将包含在授权服务请求中的请求头列表。请注意,同名的客户端请求头将被覆盖 | -| `with_request_body` | bool | 否 | false | 缓冲客户端请求体,并将其发送至鉴权请求中(HTTP Method为GET、OPTIONS、HEAD请求时不生效) | +| 名称 | 数据类型 | 必填 | 默认值 | 描述 | +| ------------------------ | ---------------------- | ---- | ------ | ------------------------------------------------------------ | +| `allowed_headers` | array of StringMatcher | 否 | - | 当设置后,具有相应匹配项的客户端请求头将添加到授权服务请求中的请求头中。除了用户自定义的头部匹配规则外,授权服务请求中会自动包含`Host`, `Method`, `Path`, `Content-Length` 和 `Authorization`这几个关键的HTTP头 | +| `headers_to_add` | `map[string]string` | 否 | - | 设置将包含在授权服务请求中的请求头列表。请注意,同名的客户端请求头将被覆盖 | +| `with_request_body` | bool | 否 | false | 缓冲客户端请求体,并将其发送至鉴权请求中(HTTP Method为GET、OPTIONS、HEAD请求时不生效) | +| `max_request_body_bytes` | int | 否 | 10MB | 设置在内存中保存客户端请求体的最大尺寸。当客户端请求体达到在此字段中设置的数值时,将会返回HTTP 413状态码,并且不会启动授权过程。注意,这个设置会优先于 `failure_mode_allow` 的配置 | `authorization_response`中每一项的配置字段说明 @@ -60,24 +67,126 @@ -# 配置示例 +## 配置示例 下面假设 `ext-auth` 服务在Kubernetes中serviceName为 `ext-auth`,端口 `8090`,路径为 `/auth`,命名空间为 `backend` -## 示例1 +支持两种 `endpoint_mode`: + +- `endpoint_mode` 为 `envoy` 时,鉴权请求会使用原始请求的HTTP Method,和配置的 `path_prefix` 作为请求路径前缀拼接上原始的请求路径 +- `endpoint_mode` 为 `forward_auth` 时,鉴权请求会使用配置的 `request_method` 作为HTTP Method,和配置的 `path` 作为请求路径 + +### endpoint_mode为envoy时 + +#### 示例1 `ext-auth` 插件的配置: ```yaml http_service: + endpoint_mode: envoy endpoint: - service_name: ext-auth - namespace: backend + service_name: ext-auth.backend.svc.cluster.local + service_port: 8090 + path_prefix: /auth + timeout: 1000 +``` + +使用如下请求网关,当开启 `ext-auth` 插件后: + +```shell +curl -X POST http://localhost:8082/users?apikey=9a342114-ba8a-11ec-b1bf-00163e1250b5 -X GET -H "foo: bar" -H "Authorization: xxx" +``` + +**请求 `ext-auth` 服务成功:** + +`ext-auth` 服务将接收到如下的鉴权请求: + +``` +POST /auth/users?apikey=9a342114-ba8a-11ec-b1bf-00163e1250b5 HTTP/1.1 +Host: ext-auth +Authorization: xxx +Content-Length: 0 +``` + +**请求 `ext-auth` 服务失败:** + +当调用 `ext-auth` 服务响应为 5xx 时,客户端将接收到HTTP响应码403和 `ext-auth` 服务返回的全量响应头 + +假如 `ext-auth` 服务返回了 `x-auth-version: 1.0` 和 `x-auth-failed: true` 的响应头,会传递给客户端 + +``` +HTTP/1.1 403 Forbidden +x-auth-version: 1.0 +x-auth-failed: true +date: Tue, 16 Jul 2024 00:19:41 GMT +server: istio-envoy +content-length: 0 +``` + +当 `ext-auth` 无法访问或状态码为 5xx 时,将以 `status_on_error` 配置的状态码拒绝客户端请求 + +当 `ext-auth` 服务返回其他 HTTP 状态码时,将以返回的状态码拒绝客户端请求。如果配置了 `allowed_client_headers`,具有相应匹配项的响应头将添加到客户端的响应中 + +#### 示例2 + +`ext-auth` 插件的配置: + +```yaml +http_service: + authorization_request: + allowed_headers: + - exact: x-auth-version + headers_to_add: + x-envoy-header: true + authorization_response: + allowed_upstream_headers: + - exact: x-user-id + - exact: x-auth-version + endpoint_mode: envoy + endpoint: + service_name: ext-auth.backend.svc.cluster.local + service_port: 8090 + path_prefix: /auth + timeout: 1000 +``` + +使用如下请求网关,当开启 `ext-auth` 插件后: + +```shell +curl -X POST http://localhost:8082/users?apikey=9a342114-ba8a-11ec-b1bf-00163e1250b5 -X GET -H "foo: bar" -H "Authorization: xxx" +``` + +`ext-auth` 服务将接收到如下的鉴权请求: + +``` +POST /auth/users?apikey=9a342114-ba8a-11ec-b1bf-00163e1250b5 HTTP/1.1 +Host: ext-auth +Authorization: xxx +X-Auth-Version: 1.0 +x-envoy-header: true +Content-Length: 0 +``` + +`ext-auth` 服务返回响应头中如果包含 `x-user-id` 和 `x-auth-version`,网关调用upstream时的请求中会带上这两个请求头 + + + +### endpoint_mode为forward_auth时 + +#### 示例1 + +`ext-auth` 插件的配置: + +```yaml +http_service: + endpoint_mode: forward_auth + endpoint: + service_name: ext-auth.backend.svc.cluster.local service_port: 8090 - service_source: k8s path: /auth request_method: POST - timeout: 500 + timeout: 1000 ``` 使用如下请求网关,当开启 `ext-auth` 插件后: @@ -116,8 +225,7 @@ content-length: 0 当 `ext-auth` 服务返回其他 HTTP 状态码时,将以返回的状态码拒绝客户端请求。如果配置了 `allowed_client_headers`,具有相应匹配项的响应头将添加到客户端的响应中 - -## 示例2 +#### 示例2 `ext-auth` 插件的配置: @@ -132,14 +240,13 @@ http_service: allowed_upstream_headers: - exact: x-user-id - exact: x-auth-version + endpoint_mode: forward_auth endpoint: - service_name: ext-auth - namespace: backend + service_name: ext-auth.backend.svc.cluster.local service_port: 8090 - service_source: k8s path: /auth request_method: POST - timeout: 500 + timeout: 1000 ``` 使用如下请求网关,当开启 `ext-auth` 插件后: diff --git a/plugins/wasm-go/extensions/ext-auth/config.go b/plugins/wasm-go/extensions/ext-auth/config.go index 13aff15de..4b60007be 100644 --- a/plugins/wasm-go/extensions/ext-auth/config.go +++ b/plugins/wasm-go/extensions/ext-auth/config.go @@ -13,7 +13,13 @@ import ( const ( DefaultStatusOnError uint32 = http.StatusForbidden - DefaultHttpServiceTimeout uint32 = 200 + DefaultHttpServiceTimeout uint32 = 1000 + + DefaultMaxRequestBodyBytes uint32 = 10 * 1024 * 1024 + + EndpointModeEnvoy = "envoy" + + EndpointModeForwardAuth = "forward_auth" ) type ExtAuthConfig struct { @@ -24,8 +30,13 @@ type ExtAuthConfig struct { } type HttpService struct { - client wrapper.HttpClient - requestMethod string + endpointMode string + client wrapper.HttpClient + // pathPrefix is only used when endpoint_mode is envoy + pathPrefix string + // requestMethod is only used when endpoint_mode is forward_auth + requestMethod string + // path is only used when endpoint_mode is forward_auth path string timeout uint32 authorizationRequest AuthorizationRequest @@ -35,9 +46,10 @@ type HttpService struct { type AuthorizationRequest struct { // allowedHeaders In addition to the user’s supplied matchers, // Host, Method, Path, Content-Length, and Authorization are automatically included to the list. - allowedHeaders expr.Matcher - headersToAdd map[string]string - withRequestBody bool + allowedHeaders expr.Matcher + headersToAdd map[string]string + withRequestBody bool + maxRequestBodyBytes uint32 } type AuthorizationResponse struct { @@ -50,7 +62,7 @@ func parseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) erro if !httpServiceConfig.Exists() { return errors.New("missing http_service in config") } - err := parseHttpServiceConfig(httpServiceConfig, config) + err := parseHttpServiceConfig(httpServiceConfig, config, log) if err != nil { return err } @@ -65,20 +77,19 @@ func parseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) erro config.failureModeAllowHeaderAdd = failureModeAllowHeaderAdd.Bool() } - statusOnError := json.Get("status_on_error") - if statusOnError.Exists() { - config.statusOnError = uint32(statusOnError.Uint()) - } else { - config.statusOnError = DefaultStatusOnError + statusOnError := uint32(json.Get("status_on_error").Uint()) + if statusOnError == 0 { + statusOnError = DefaultStatusOnError } + config.statusOnError = statusOnError return nil } -func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig) error { +func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) error { var httpService HttpService - if err := parseEndpointConfig(json, &httpService); err != nil { + if err := parseEndpointConfig(json, &httpService, log); err != nil { return err } @@ -101,64 +112,63 @@ func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig) error { return nil } -func parseEndpointConfig(json gjson.Result, httpService *HttpService) error { +func parseEndpointConfig(json gjson.Result, httpService *HttpService, log wrapper.Log) error { + endpointMode := json.Get("endpoint_mode").String() + if endpointMode == "" { + endpointMode = EndpointModeEnvoy + } else if endpointMode != EndpointModeEnvoy && endpointMode != EndpointModeForwardAuth { + return errors.New(fmt.Sprintf("endpoint_mode %s is not supported", endpointMode)) + } + httpService.endpointMode = endpointMode + endpointConfig := json.Get("endpoint") if !endpointConfig.Exists() { return errors.New("missing endpoint in config") } - serviceSource := endpointConfig.Get("service_source").String() serviceName := endpointConfig.Get("service_name").String() + if serviceName == "" { + return errors.New("endpoint service name must not be empty") + } servicePort := endpointConfig.Get("service_port").Int() - if serviceName == "" || servicePort == 0 { - return errors.New("invalid service config") - } - switch serviceSource { - case "k8s": - namespace := json.Get("namespace").String() - httpService.client = wrapper.NewClusterClient(wrapper.K8sCluster{ - ServiceName: serviceName, - Namespace: namespace, - Port: servicePort, - }) - return nil - case "nacos": - namespace := json.Get("namespace").String() - httpService.client = wrapper.NewClusterClient(wrapper.NacosCluster{ - ServiceName: serviceName, - NamespaceID: namespace, - Port: servicePort, - }) - return nil - case "ip": - httpService.client = wrapper.NewClusterClient(wrapper.StaticIpCluster{ - ServiceName: serviceName, - Port: servicePort, - }) - case "dns": - domain := endpointConfig.Get("domain").String() - httpService.client = wrapper.NewClusterClient(wrapper.DnsCluster{ - ServiceName: serviceName, - Port: servicePort, - Domain: domain, - }) - default: - return errors.New("unknown service source: " + serviceSource) + if servicePort == 0 { + servicePort = 80 } - requestMethodConfig := endpointConfig.Get("request_method") - if !requestMethodConfig.Exists() { - httpService.requestMethod = http.MethodGet - } else { - httpService.requestMethod = strings.ToUpper(requestMethodConfig.String()) - } + httpService.client = wrapper.NewClusterClient(wrapper.FQDNCluster{ + FQDN: serviceName, + Port: servicePort, + }) - pathConfig := endpointConfig.Get("path") - if !pathConfig.Exists() { - return errors.New("missing path in config") - } - httpService.path = pathConfig.String() + switch endpointMode { + case EndpointModeEnvoy: + pathPrefixConfig := endpointConfig.Get("path_prefix") + if !pathPrefixConfig.Exists() { + return errors.New("when endpoint_mode is envoy, endpoint path_prefix must not be empty") + } + httpService.pathPrefix = pathPrefixConfig.String() + if endpointConfig.Get("request_method").Exists() || endpointConfig.Get("path").Exists() { + log.Warn("when endpoint_mode is envoy, endpoint request_method and path will be ignored") + } + case EndpointModeForwardAuth: + requestMethodConfig := endpointConfig.Get("request_method") + if !requestMethodConfig.Exists() { + httpService.requestMethod = http.MethodGet + } else { + httpService.requestMethod = strings.ToUpper(requestMethodConfig.String()) + } + + pathConfig := endpointConfig.Get("path") + if !pathConfig.Exists() { + return errors.New("when endpoint_mode is forward_auth, endpoint path must not be empty") + } + httpService.path = pathConfig.String() + + if endpointConfig.Get("path_prefix").Exists() { + log.Warn("when endpoint_mode is forward_auth, endpoint path_prefix will be ignored") + } + } return nil } @@ -167,6 +177,15 @@ func parseAuthorizationRequestConfig(json gjson.Result, httpService *HttpService if authorizationRequestConfig.Exists() { var authorizationRequest AuthorizationRequest + allowedHeaders := authorizationRequestConfig.Get("allowed_headers") + if allowedHeaders.Exists() { + result, err := expr.BuildRepeatedStringMatcherIgnoreCase(allowedHeaders.Array()) + if err != nil { + return err + } + authorizationRequest.allowedHeaders = result + } + headersToAdd := map[string]string{} headersToAddConfig := authorizationRequestConfig.Get("headers_to_add") if headersToAddConfig.Exists() { @@ -186,14 +205,11 @@ func parseAuthorizationRequestConfig(json gjson.Result, httpService *HttpService authorizationRequest.withRequestBody = withRequestBody.Bool() } - allowedHeaders := authorizationRequestConfig.Get("allowed_headers") - if allowedHeaders.Exists() { - result, err := expr.BuildRepeatedStringMatcherIgnoreCase(allowedHeaders.Array()) - if err != nil { - return err - } - authorizationRequest.allowedHeaders = result + maxRequestBodyBytes := uint32(authorizationRequestConfig.Get("max_request_body_bytes").Uint()) + if maxRequestBodyBytes == 0 { + maxRequestBodyBytes = DefaultMaxRequestBodyBytes } + authorizationRequest.maxRequestBodyBytes = maxRequestBodyBytes httpService.authorizationRequest = authorizationRequest } diff --git a/plugins/wasm-go/extensions/ext-auth/expr/matcher.go b/plugins/wasm-go/extensions/ext-auth/expr/matcher.go index d00fdf462..47625dbd8 100644 --- a/plugins/wasm-go/extensions/ext-auth/expr/matcher.go +++ b/plugins/wasm-go/extensions/ext-auth/expr/matcher.go @@ -3,7 +3,7 @@ package expr import ( "errors" "github.com/tidwall/gjson" - "regexp" + regexp "github.com/wasilibs/go-re2" "strings" ) diff --git a/plugins/wasm-go/extensions/ext-auth/go.mod b/plugins/wasm-go/extensions/ext-auth/go.mod index 7fb275971..61cfd99ac 100644 --- a/plugins/wasm-go/extensions/ext-auth/go.mod +++ b/plugins/wasm-go/extensions/ext-auth/go.mod @@ -9,16 +9,19 @@ require ( github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.14.3 + github.com/wasilibs/go-re2 v1.6.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect 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/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/tetratelabs/wazero v1.7.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/resp v0.1.1 // indirect + golang.org/x/sys v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/plugins/wasm-go/extensions/ext-auth/go.sum b/plugins/wasm-go/extensions/ext-auth/go.sum index e726b100a..97bf6469a 100644 --- a/plugins/wasm-go/extensions/ext-auth/go.sum +++ b/plugins/wasm-go/extensions/ext-auth/go.sum @@ -6,12 +6,14 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew= github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg= github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= -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/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho= +github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= +github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 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= @@ -20,6 +22,11 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE= github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0= +github.com/wasilibs/go-re2 v1.6.0 h1:CLlhDebt38wtl/zz4ww+hkXBMcxjrKFvTDXzFW2VOz8= +github.com/wasilibs/go-re2 v1.6.0/go.mod h1:prArCyErsypRBI/jFAFJEbzyHzjABKqkzlidF0SNA04= +github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/plugins/wasm-go/extensions/ext-auth/main.go b/plugins/wasm-go/extensions/ext-auth/main.go index 8ab18fd87..073e1342a 100644 --- a/plugins/wasm-go/extensions/ext-auth/main.go +++ b/plugins/wasm-go/extensions/ext-auth/main.go @@ -19,7 +19,7 @@ import ( "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" "net/http" - "strconv" + "net/url" ) func main() { @@ -32,39 +32,37 @@ func main() { } const ( - HeaderContentLength string = "content-length" HeaderAuthorization string = "authorization" HeaderFailureModeAllow string = "x-envoy-auth-failure-mode-allowed" ) func onHttpRequestHeaders(ctx wrapper.HttpContext, config ExtAuthConfig, log wrapper.Log) types.Action { - contentLengthStr, _ := proxywasm.GetHttpRequestHeader(HeaderContentLength) - hasRequestBody := false - if contentLengthStr != "" { - contentLength, err := strconv.Atoi(contentLengthStr) - hasRequestBody = err == nil && contentLength > 0 - } - // If withRequestBody is true AND the HTTP request contains a request body, - // it will be handled in the onHttpRequestBody phase. - if config.httpService.authorizationRequest.withRequestBody && hasRequestBody { - // Disable the route re-calculation since the plugin may modify some headers related to the chosen route. - ctx.DisableReroute() - // The request has a body and requires delaying the header transmission until a cache miss occurs, - // at which point the header should be sent. - return types.HeaderStopIteration + if wrapper.HasRequestBody() { + ctx.SetRequestBodyBufferLimit(config.httpService.authorizationRequest.maxRequestBodyBytes) + + // If withRequestBody is true AND the HTTP request contains a request body, + // it will be handled in the onHttpRequestBody phase. + if config.httpService.authorizationRequest.withRequestBody { + // Disable the route re-calculation since the plugin may modify some headers related to the chosen route. + ctx.DisableReroute() + // The request has a body and requires delaying the header transmission until a cache miss occurs, + // at which point the header should be sent. + return types.HeaderStopIteration + } } + ctx.DontReadRequestBody() - return checkExtAuth(ctx, config, nil, log) + return checkExtAuth(ctx, config, nil, log, types.HeaderStopAllIterationAndWatermark) } func onHttpRequestBody(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, log wrapper.Log) types.Action { if config.httpService.authorizationRequest.withRequestBody { - return checkExtAuth(ctx, config, body, log) + return checkExtAuth(ctx, config, body, log, types.ActionPause) } return types.ActionContinue } -func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, log wrapper.Log) types.Action { +func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, log wrapper.Log, pauseAction types.Action) types.Action { // build extAuth request headers extAuthReqHeaders := http.Header{} @@ -90,8 +88,15 @@ func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, lo extAuthReqHeaders.Set(HeaderAuthorization, authorization) } + requestMethod := httpServiceConfig.requestMethod + requestPath := httpServiceConfig.path + if httpServiceConfig.endpointMode == EndpointModeEnvoy { + requestMethod = ctx.Method() + requestPath, _ = url.JoinPath(httpServiceConfig.pathPrefix, ctx.Path()) + } + // call ext auth server - err := httpServiceConfig.client.Post(httpServiceConfig.path, reconvertHeaders(extAuthReqHeaders), body, + err := httpServiceConfig.client.Call(requestMethod, requestPath, reconvertHeaders(extAuthReqHeaders), body, func(statusCode int, responseHeaders http.Header, responseBody []byte) { defer proxywasm.ResumeHttpRequest() if statusCode != http.StatusOK { @@ -116,7 +121,7 @@ func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, lo callExtAuthServerErrorHandler(config, http.StatusInternalServerError, nil) return types.ActionContinue } - return types.ActionPause + return pauseAction } func callExtAuthServerErrorHandler(config ExtAuthConfig, statusCode int, extAuthRespHeaders http.Header) { diff --git a/plugins/wasm-go/pkg/wrapper/request_wrapper.go b/plugins/wasm-go/pkg/wrapper/request_wrapper.go index 0a7886946..9a33f04d0 100644 --- a/plugins/wasm-go/pkg/wrapper/request_wrapper.go +++ b/plugins/wasm-go/pkg/wrapper/request_wrapper.go @@ -15,6 +15,7 @@ package wrapper import ( + "strconv" "strings" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" @@ -81,3 +82,16 @@ func IsBinaryResponseBody() bool { } return false } + +func HasRequestBody() bool { + contentLengthStr, _ := proxywasm.GetHttpRequestHeader("content-length") + if contentLengthStr != "" { + contentLength, err := strconv.Atoi(contentLengthStr) + if err == nil && contentLength > 0 { + return true + } + } + + transferEncodingStr, _ := proxywasm.GetHttpRequestHeader("transfer-encoding") + return strings.Contains(transferEncodingStr, "chunked") +}