fix:fix bug in ext-auth wasm plugin (#1152)

This commit is contained in:
韩贤涛
2024-08-05 11:04:31 +08:00
committed by GitHub
parent cc74c0da93
commit 08c64ed467
8 changed files with 275 additions and 122 deletions

View File

@@ -0,0 +1 @@
EXTRA_TAGS=proxy_wasm_version_0_2_100

View File

@@ -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` 插件后:

View File

@@ -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 users 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
}

View File

@@ -3,7 +3,7 @@ package expr
import (
"errors"
"github.com/tidwall/gjson"
"regexp"
regexp "github.com/wasilibs/go-re2"
"strings"
)

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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) {

View File

@@ -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")
}