mirror of
https://github.com/alibaba/higress.git
synced 2026-06-10 05:07:30 +08:00
fix:fix bug in ext-auth wasm plugin (#1152)
This commit is contained in:
1
plugins/wasm-go/extensions/ext-auth/.buildrc
Normal file
1
plugins/wasm-go/extensions/ext-auth/.buildrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
EXTRA_TAGS=proxy_wasm_version_0_2_100
|
||||||
@@ -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服务的部分能力
|
`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 服务信息 |
|
| `endpoint` | object | 是 | - | 发送鉴权请求的 HTTP 服务信息 |
|
||||||
| `timeout` | int | 否 | 200 | `ext-auth` 服务连接超时时间,单位毫秒 |
|
| `timeout` | int | 否 | 1000 | `ext-auth` 服务连接超时时间,单位毫秒 |
|
||||||
| `authorization_request` | object | 否 | - | 发送鉴权请求配置 |
|
| `authorization_request` | object | 否 | - | 发送鉴权请求配置 |
|
||||||
| `authorization_response` | object | 否 | - | 处理鉴权响应配置 |
|
| `authorization_response` | object | 否 | - | 处理鉴权响应配置 |
|
||||||
|
|
||||||
`endpoint`中每一项的配置字段说明
|
`endpoint`中每一项的配置字段说明
|
||||||
|
|
||||||
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
|
||||||
| ---------------- | -------- | ---- | ------ | --------------------------------------------------- |
|
| -------- | -------- | -- | ------ |-----------------------------------------------------------------------------------------|
|
||||||
| `service_source` | string | 是 | - | 类型为固定 ip 或者 dns,输入授权服务的注册来源 |
|
| `service_name` | string | 必填 | - | 输入授权服务名称,带服务类型的完整 FQDN 名称,例如 `ext-auth.dns` 、`ext-auth.my-ns.svc.cluster.local` |
|
||||||
| `service_name` | string | 是 | - | 输入授权服务的注册名称 |
|
| `service_port` | int | 否 | 80 | 输入授权服务的服务端口 |
|
||||||
| `service_port` | string | 是 | - | 输入授权服务的服务端口 |
|
| `path_prefix` | string | `endpoint_mode` 为`envoy`时必填 | | `endpoint_mode` 为`envoy` 时,客户端向授权服务发送请求的请求路径前缀 |
|
||||||
| `service_domain` | string | 否 | - | 当类型为dns时必须填写,输入 `ext-auth` 服务的domain |
|
| `request_method` | string | 否 | GET | `endpoint_mode` 为`forward_auth` 时,客户端向授权服务发送请求的HTTP Method |
|
||||||
| `request_method` | string | 否 | GET | 客户端向授权服务发送请求的HTTP Method |
|
| `path` | string | `endpoint_mode` 为`forward_auth`时必填 | - | `endpoint_mode` 为`forward_auth` 时,客户端向授权服务发送请求的请求路径 |
|
||||||
| `path` | string | 是 | - | 输入授权服务的请求路径 |
|
|
||||||
|
|
||||||
`authorization_request`中每一项的配置字段说明
|
`authorization_request`中每一项的配置字段说明
|
||||||
|
|
||||||
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
|
| 名称 | 数据类型 | 必填 | 默认值 | 描述 |
|
||||||
| ------------------- | ---------------------- | ---- | ------ | ------------------------------------------------------------ |
|
| ------------------------ | ---------------------- | ---- | ------ | ------------------------------------------------------------ |
|
||||||
| `allowed_headers` | array of StringMatcher | 否 | - | 当设置后,具有相应匹配项的客户端请求头将添加到授权服务请求中的请求头中。除了用户自定义的头部匹配规则外,授权服务请求中会自动包含`Host`, `Method`, `Path`, `Content-Length` 和 `Authorization`这几个关键的HTTP头 |
|
| `allowed_headers` | array of StringMatcher | 否 | - | 当设置后,具有相应匹配项的客户端请求头将添加到授权服务请求中的请求头中。除了用户自定义的头部匹配规则外,授权服务请求中会自动包含`Host`, `Method`, `Path`, `Content-Length` 和 `Authorization`这几个关键的HTTP头 |
|
||||||
| `headers_to_add` | `map[string]string` | 否 | - | 设置将包含在授权服务请求中的请求头列表。请注意,同名的客户端请求头将被覆盖 |
|
| `headers_to_add` | `map[string]string` | 否 | - | 设置将包含在授权服务请求中的请求头列表。请注意,同名的客户端请求头将被覆盖 |
|
||||||
| `with_request_body` | bool | 否 | false | 缓冲客户端请求体,并将其发送至鉴权请求中(HTTP Method为GET、OPTIONS、HEAD请求时不生效) |
|
| `with_request_body` | bool | 否 | false | 缓冲客户端请求体,并将其发送至鉴权请求中(HTTP Method为GET、OPTIONS、HEAD请求时不生效) |
|
||||||
|
| `max_request_body_bytes` | int | 否 | 10MB | 设置在内存中保存客户端请求体的最大尺寸。当客户端请求体达到在此字段中设置的数值时,将会返回HTTP 413状态码,并且不会启动授权过程。注意,这个设置会优先于 `failure_mode_allow` 的配置 |
|
||||||
|
|
||||||
`authorization_response`中每一项的配置字段说明
|
`authorization_response`中每一项的配置字段说明
|
||||||
|
|
||||||
@@ -60,24 +67,126 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 配置示例
|
## 配置示例
|
||||||
|
|
||||||
下面假设 `ext-auth` 服务在Kubernetes中serviceName为 `ext-auth`,端口 `8090`,路径为 `/auth`,命名空间为 `backend`
|
下面假设 `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` 插件的配置:
|
`ext-auth` 插件的配置:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
http_service:
|
http_service:
|
||||||
|
endpoint_mode: envoy
|
||||||
endpoint:
|
endpoint:
|
||||||
service_name: ext-auth
|
service_name: ext-auth.backend.svc.cluster.local
|
||||||
namespace: backend
|
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_port: 8090
|
||||||
service_source: k8s
|
|
||||||
path: /auth
|
path: /auth
|
||||||
request_method: POST
|
request_method: POST
|
||||||
timeout: 500
|
timeout: 1000
|
||||||
```
|
```
|
||||||
|
|
||||||
使用如下请求网关,当开启 `ext-auth` 插件后:
|
使用如下请求网关,当开启 `ext-auth` 插件后:
|
||||||
@@ -116,8 +225,7 @@ content-length: 0
|
|||||||
|
|
||||||
当 `ext-auth` 服务返回其他 HTTP 状态码时,将以返回的状态码拒绝客户端请求。如果配置了 `allowed_client_headers`,具有相应匹配项的响应头将添加到客户端的响应中
|
当 `ext-auth` 服务返回其他 HTTP 状态码时,将以返回的状态码拒绝客户端请求。如果配置了 `allowed_client_headers`,具有相应匹配项的响应头将添加到客户端的响应中
|
||||||
|
|
||||||
|
#### 示例2
|
||||||
## 示例2
|
|
||||||
|
|
||||||
`ext-auth` 插件的配置:
|
`ext-auth` 插件的配置:
|
||||||
|
|
||||||
@@ -132,14 +240,13 @@ http_service:
|
|||||||
allowed_upstream_headers:
|
allowed_upstream_headers:
|
||||||
- exact: x-user-id
|
- exact: x-user-id
|
||||||
- exact: x-auth-version
|
- exact: x-auth-version
|
||||||
|
endpoint_mode: forward_auth
|
||||||
endpoint:
|
endpoint:
|
||||||
service_name: ext-auth
|
service_name: ext-auth.backend.svc.cluster.local
|
||||||
namespace: backend
|
|
||||||
service_port: 8090
|
service_port: 8090
|
||||||
service_source: k8s
|
|
||||||
path: /auth
|
path: /auth
|
||||||
request_method: POST
|
request_method: POST
|
||||||
timeout: 500
|
timeout: 1000
|
||||||
```
|
```
|
||||||
|
|
||||||
使用如下请求网关,当开启 `ext-auth` 插件后:
|
使用如下请求网关,当开启 `ext-auth` 插件后:
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
DefaultStatusOnError uint32 = http.StatusForbidden
|
DefaultStatusOnError uint32 = http.StatusForbidden
|
||||||
|
|
||||||
DefaultHttpServiceTimeout uint32 = 200
|
DefaultHttpServiceTimeout uint32 = 1000
|
||||||
|
|
||||||
|
DefaultMaxRequestBodyBytes uint32 = 10 * 1024 * 1024
|
||||||
|
|
||||||
|
EndpointModeEnvoy = "envoy"
|
||||||
|
|
||||||
|
EndpointModeForwardAuth = "forward_auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExtAuthConfig struct {
|
type ExtAuthConfig struct {
|
||||||
@@ -24,8 +30,13 @@ type ExtAuthConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HttpService struct {
|
type HttpService struct {
|
||||||
client wrapper.HttpClient
|
endpointMode string
|
||||||
requestMethod 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
|
path string
|
||||||
timeout uint32
|
timeout uint32
|
||||||
authorizationRequest AuthorizationRequest
|
authorizationRequest AuthorizationRequest
|
||||||
@@ -35,9 +46,10 @@ type HttpService struct {
|
|||||||
type AuthorizationRequest struct {
|
type AuthorizationRequest struct {
|
||||||
// allowedHeaders In addition to the user’s supplied matchers,
|
// allowedHeaders In addition to the user’s supplied matchers,
|
||||||
// Host, Method, Path, Content-Length, and Authorization are automatically included to the list.
|
// Host, Method, Path, Content-Length, and Authorization are automatically included to the list.
|
||||||
allowedHeaders expr.Matcher
|
allowedHeaders expr.Matcher
|
||||||
headersToAdd map[string]string
|
headersToAdd map[string]string
|
||||||
withRequestBody bool
|
withRequestBody bool
|
||||||
|
maxRequestBodyBytes uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizationResponse struct {
|
type AuthorizationResponse struct {
|
||||||
@@ -50,7 +62,7 @@ func parseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) erro
|
|||||||
if !httpServiceConfig.Exists() {
|
if !httpServiceConfig.Exists() {
|
||||||
return errors.New("missing http_service in config")
|
return errors.New("missing http_service in config")
|
||||||
}
|
}
|
||||||
err := parseHttpServiceConfig(httpServiceConfig, config)
|
err := parseHttpServiceConfig(httpServiceConfig, config, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -65,20 +77,19 @@ func parseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) erro
|
|||||||
config.failureModeAllowHeaderAdd = failureModeAllowHeaderAdd.Bool()
|
config.failureModeAllowHeaderAdd = failureModeAllowHeaderAdd.Bool()
|
||||||
}
|
}
|
||||||
|
|
||||||
statusOnError := json.Get("status_on_error")
|
statusOnError := uint32(json.Get("status_on_error").Uint())
|
||||||
if statusOnError.Exists() {
|
if statusOnError == 0 {
|
||||||
config.statusOnError = uint32(statusOnError.Uint())
|
statusOnError = DefaultStatusOnError
|
||||||
} else {
|
|
||||||
config.statusOnError = DefaultStatusOnError
|
|
||||||
}
|
}
|
||||||
|
config.statusOnError = statusOnError
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig) error {
|
func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) error {
|
||||||
var httpService HttpService
|
var httpService HttpService
|
||||||
|
|
||||||
if err := parseEndpointConfig(json, &httpService); err != nil {
|
if err := parseEndpointConfig(json, &httpService, log); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,64 +112,63 @@ func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig) error {
|
|||||||
return nil
|
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")
|
endpointConfig := json.Get("endpoint")
|
||||||
if !endpointConfig.Exists() {
|
if !endpointConfig.Exists() {
|
||||||
return errors.New("missing endpoint in config")
|
return errors.New("missing endpoint in config")
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceSource := endpointConfig.Get("service_source").String()
|
|
||||||
serviceName := endpointConfig.Get("service_name").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()
|
servicePort := endpointConfig.Get("service_port").Int()
|
||||||
if serviceName == "" || servicePort == 0 {
|
if servicePort == 0 {
|
||||||
return errors.New("invalid service config")
|
servicePort = 80
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestMethodConfig := endpointConfig.Get("request_method")
|
httpService.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
|
||||||
if !requestMethodConfig.Exists() {
|
FQDN: serviceName,
|
||||||
httpService.requestMethod = http.MethodGet
|
Port: servicePort,
|
||||||
} else {
|
})
|
||||||
httpService.requestMethod = strings.ToUpper(requestMethodConfig.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
pathConfig := endpointConfig.Get("path")
|
switch endpointMode {
|
||||||
if !pathConfig.Exists() {
|
case EndpointModeEnvoy:
|
||||||
return errors.New("missing path in config")
|
pathPrefixConfig := endpointConfig.Get("path_prefix")
|
||||||
}
|
if !pathPrefixConfig.Exists() {
|
||||||
httpService.path = pathConfig.String()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +177,15 @@ func parseAuthorizationRequestConfig(json gjson.Result, httpService *HttpService
|
|||||||
if authorizationRequestConfig.Exists() {
|
if authorizationRequestConfig.Exists() {
|
||||||
var authorizationRequest AuthorizationRequest
|
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{}
|
headersToAdd := map[string]string{}
|
||||||
headersToAddConfig := authorizationRequestConfig.Get("headers_to_add")
|
headersToAddConfig := authorizationRequestConfig.Get("headers_to_add")
|
||||||
if headersToAddConfig.Exists() {
|
if headersToAddConfig.Exists() {
|
||||||
@@ -186,14 +205,11 @@ func parseAuthorizationRequestConfig(json gjson.Result, httpService *HttpService
|
|||||||
authorizationRequest.withRequestBody = withRequestBody.Bool()
|
authorizationRequest.withRequestBody = withRequestBody.Bool()
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedHeaders := authorizationRequestConfig.Get("allowed_headers")
|
maxRequestBodyBytes := uint32(authorizationRequestConfig.Get("max_request_body_bytes").Uint())
|
||||||
if allowedHeaders.Exists() {
|
if maxRequestBodyBytes == 0 {
|
||||||
result, err := expr.BuildRepeatedStringMatcherIgnoreCase(allowedHeaders.Array())
|
maxRequestBodyBytes = DefaultMaxRequestBodyBytes
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authorizationRequest.allowedHeaders = result
|
|
||||||
}
|
}
|
||||||
|
authorizationRequest.maxRequestBodyBytes = maxRequestBodyBytes
|
||||||
|
|
||||||
httpService.authorizationRequest = authorizationRequest
|
httpService.authorizationRequest = authorizationRequest
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package expr
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
"regexp"
|
regexp "github.com/wasilibs/go-re2"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,16 +9,19 @@ require (
|
|||||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
|
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/tidwall/gjson v1.14.3
|
github.com/tidwall/gjson v1.14.3
|
||||||
|
github.com/wasilibs/go-re2 v1.6.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // 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/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/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tidwall/resp v0.1.1 // 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
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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/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 h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
|
||||||
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
|
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.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho=
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
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 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
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 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/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 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
|
||||||
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -32,39 +32,37 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HeaderContentLength string = "content-length"
|
|
||||||
HeaderAuthorization string = "authorization"
|
HeaderAuthorization string = "authorization"
|
||||||
HeaderFailureModeAllow string = "x-envoy-auth-failure-mode-allowed"
|
HeaderFailureModeAllow string = "x-envoy-auth-failure-mode-allowed"
|
||||||
)
|
)
|
||||||
|
|
||||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config ExtAuthConfig, log wrapper.Log) types.Action {
|
func onHttpRequestHeaders(ctx wrapper.HttpContext, config ExtAuthConfig, log wrapper.Log) types.Action {
|
||||||
contentLengthStr, _ := proxywasm.GetHttpRequestHeader(HeaderContentLength)
|
if wrapper.HasRequestBody() {
|
||||||
hasRequestBody := false
|
ctx.SetRequestBodyBufferLimit(config.httpService.authorizationRequest.maxRequestBodyBytes)
|
||||||
if contentLengthStr != "" {
|
|
||||||
contentLength, err := strconv.Atoi(contentLengthStr)
|
// If withRequestBody is true AND the HTTP request contains a request body,
|
||||||
hasRequestBody = err == nil && contentLength > 0
|
// it will be handled in the onHttpRequestBody phase.
|
||||||
}
|
if config.httpService.authorizationRequest.withRequestBody {
|
||||||
// If withRequestBody is true AND the HTTP request contains a request body,
|
// Disable the route re-calculation since the plugin may modify some headers related to the chosen route.
|
||||||
// it will be handled in the onHttpRequestBody phase.
|
ctx.DisableReroute()
|
||||||
if config.httpService.authorizationRequest.withRequestBody && hasRequestBody {
|
// The request has a body and requires delaying the header transmission until a cache miss occurs,
|
||||||
// Disable the route re-calculation since the plugin may modify some headers related to the chosen route.
|
// at which point the header should be sent.
|
||||||
ctx.DisableReroute()
|
return types.HeaderStopIteration
|
||||||
// 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()
|
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 {
|
func onHttpRequestBody(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, log wrapper.Log) types.Action {
|
||||||
if config.httpService.authorizationRequest.withRequestBody {
|
if config.httpService.authorizationRequest.withRequestBody {
|
||||||
return checkExtAuth(ctx, config, body, log)
|
return checkExtAuth(ctx, config, body, log, types.ActionPause)
|
||||||
}
|
}
|
||||||
return types.ActionContinue
|
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
|
// build extAuth request headers
|
||||||
extAuthReqHeaders := http.Header{}
|
extAuthReqHeaders := http.Header{}
|
||||||
|
|
||||||
@@ -90,8 +88,15 @@ func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, lo
|
|||||||
extAuthReqHeaders.Set(HeaderAuthorization, authorization)
|
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
|
// 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) {
|
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
|
||||||
defer proxywasm.ResumeHttpRequest()
|
defer proxywasm.ResumeHttpRequest()
|
||||||
if statusCode != http.StatusOK {
|
if statusCode != http.StatusOK {
|
||||||
@@ -116,7 +121,7 @@ func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, lo
|
|||||||
callExtAuthServerErrorHandler(config, http.StatusInternalServerError, nil)
|
callExtAuthServerErrorHandler(config, http.StatusInternalServerError, nil)
|
||||||
return types.ActionContinue
|
return types.ActionContinue
|
||||||
}
|
}
|
||||||
return types.ActionPause
|
return pauseAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func callExtAuthServerErrorHandler(config ExtAuthConfig, statusCode int, extAuthRespHeaders http.Header) {
|
func callExtAuthServerErrorHandler(config ExtAuthConfig, statusCode int, extAuthRespHeaders http.Header) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package wrapper
|
package wrapper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||||
@@ -81,3 +82,16 @@ func IsBinaryResponseBody() bool {
|
|||||||
}
|
}
|
||||||
return false
|
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")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user