Skip to content

Commit 0259ead

Browse files
authored
feat: Add ext-auth plugin support for authentication blacklists/whitelists (#1694)
1 parent cfa3bad commit 0259ead

File tree

14 files changed

+1129
-428
lines changed

14 files changed

+1129
-428
lines changed

plugins/wasm-go/extensions/ext-auth/README.md

Lines changed: 93 additions & 56 deletions
Large diffs are not rendered by default.

plugins/wasm-go/extensions/ext-auth/README_EN.md

Lines changed: 187 additions & 103 deletions
Large diffs are not rendered by default.
Lines changed: 106 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package main
1+
package config
22

33
import (
44
"errors"
@@ -12,80 +12,78 @@ import (
1212
)
1313

1414
const (
15-
DefaultStatusOnError uint32 = http.StatusForbidden
15+
DefaultStatusOnError = http.StatusForbidden
1616

17-
DefaultHttpServiceTimeout uint32 = 1000
17+
DefaultHttpServiceTimeout = 1000
1818

19-
DefaultMaxRequestBodyBytes uint32 = 10 * 1024 * 1024
20-
21-
EndpointModeEnvoy = "envoy"
19+
DefaultMaxRequestBodyBytes = 10 * 1024 * 1024
2220

21+
EndpointModeEnvoy = "envoy"
2322
EndpointModeForwardAuth = "forward_auth"
2423
)
2524

2625
type ExtAuthConfig struct {
27-
httpService HttpService
28-
failureModeAllow bool
29-
failureModeAllowHeaderAdd bool
30-
statusOnError uint32
26+
HttpService HttpService
27+
MatchRules expr.MatchRules
28+
FailureModeAllow bool
29+
FailureModeAllowHeaderAdd bool
30+
StatusOnError uint32
3131
}
3232

3333
type HttpService struct {
34-
endpointMode string
35-
client wrapper.HttpClient
36-
// pathPrefix is only used when endpoint_mode is envoy
37-
pathPrefix string
38-
// requestMethod is only used when endpoint_mode is forward_auth
39-
requestMethod string
40-
// path is only used when endpoint_mode is forward_auth
41-
path string
42-
timeout uint32
43-
authorizationRequest AuthorizationRequest
44-
authorizationResponse AuthorizationResponse
34+
EndpointMode string
35+
Client wrapper.HttpClient
36+
// PathPrefix is only used when endpoint_mode is envoy
37+
PathPrefix string
38+
// RequestMethod is only used when endpoint_mode is forward_auth
39+
RequestMethod string
40+
// Path is only used when endpoint_mode is forward_auth
41+
Path string
42+
Timeout uint32
43+
AuthorizationRequest AuthorizationRequest
44+
AuthorizationResponse AuthorizationResponse
4545
}
4646

4747
type AuthorizationRequest struct {
48-
// allowedHeaders In addition to the user’s supplied matchers,
49-
// Authorization are automatically included to the list.
50-
// When the endpoint_mode is set to forward_auth,
51-
// the original request's path is set in the X-Original-Uri header,
52-
// and the original request's HTTP method is set in the X-Original-Method header.
53-
allowedHeaders expr.Matcher
54-
headersToAdd map[string]string
55-
withRequestBody bool
56-
maxRequestBodyBytes uint32
48+
AllowedHeaders expr.Matcher
49+
HeadersToAdd map[string]string
50+
WithRequestBody bool
51+
MaxRequestBodyBytes uint32
5752
}
5853

5954
type AuthorizationResponse struct {
60-
allowedUpstreamHeaders expr.Matcher
61-
allowedClientHeaders expr.Matcher
55+
AllowedUpstreamHeaders expr.Matcher
56+
AllowedClientHeaders expr.Matcher
6257
}
6358

64-
func parseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) error {
59+
func ParseConfig(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) error {
6560
httpServiceConfig := json.Get("http_service")
6661
if !httpServiceConfig.Exists() {
6762
return errors.New("missing http_service in config")
6863
}
69-
err := parseHttpServiceConfig(httpServiceConfig, config, log)
70-
if err != nil {
64+
if err := parseHttpServiceConfig(httpServiceConfig, config, log); err != nil {
65+
return err
66+
}
67+
68+
if err := parseMatchRules(json, config, log); err != nil {
7169
return err
7270
}
7371

7472
failureModeAllow := json.Get("failure_mode_allow")
7573
if failureModeAllow.Exists() {
76-
config.failureModeAllow = failureModeAllow.Bool()
74+
config.FailureModeAllow = failureModeAllow.Bool()
7775
}
7876

7977
failureModeAllowHeaderAdd := json.Get("failure_mode_allow_header_add")
8078
if failureModeAllowHeaderAdd.Exists() {
81-
config.failureModeAllowHeaderAdd = failureModeAllowHeaderAdd.Bool()
79+
config.FailureModeAllowHeaderAdd = failureModeAllowHeaderAdd.Bool()
8280
}
8381

8482
statusOnError := uint32(json.Get("status_on_error").Uint())
8583
if statusOnError == 0 {
8684
statusOnError = DefaultStatusOnError
8785
}
88-
config.statusOnError = statusOnError
86+
config.StatusOnError = statusOnError
8987

9088
return nil
9189
}
@@ -101,7 +99,7 @@ func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig, log wrappe
10199
if timeout == 0 {
102100
timeout = DefaultHttpServiceTimeout
103101
}
104-
httpService.timeout = timeout
102+
httpService.Timeout = timeout
105103

106104
if err := parseAuthorizationRequestConfig(json, &httpService); err != nil {
107105
return err
@@ -111,7 +109,7 @@ func parseHttpServiceConfig(json gjson.Result, config *ExtAuthConfig, log wrappe
111109
return err
112110
}
113111

114-
config.httpService = httpService
112+
config.HttpService = httpService
115113

116114
return nil
117115
}
@@ -123,7 +121,7 @@ func parseEndpointConfig(json gjson.Result, httpService *HttpService, log wrappe
123121
} else if endpointMode != EndpointModeEnvoy && endpointMode != EndpointModeForwardAuth {
124122
return errors.New(fmt.Sprintf("endpoint_mode %s is not supported", endpointMode))
125123
}
126-
httpService.endpointMode = endpointMode
124+
httpService.EndpointMode = endpointMode
127125

128126
endpointConfig := json.Get("endpoint")
129127
if !endpointConfig.Exists() {
@@ -140,7 +138,7 @@ func parseEndpointConfig(json gjson.Result, httpService *HttpService, log wrappe
140138
}
141139
serviceHost := endpointConfig.Get("service_host").String()
142140

143-
httpService.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
141+
httpService.Client = wrapper.NewClusterClient(wrapper.FQDNCluster{
144142
FQDN: serviceName,
145143
Port: servicePort,
146144
Host: serviceHost,
@@ -152,24 +150,24 @@ func parseEndpointConfig(json gjson.Result, httpService *HttpService, log wrappe
152150
if !pathPrefixConfig.Exists() {
153151
return errors.New("when endpoint_mode is envoy, endpoint path_prefix must not be empty")
154152
}
155-
httpService.pathPrefix = pathPrefixConfig.String()
153+
httpService.PathPrefix = pathPrefixConfig.String()
156154

157155
if endpointConfig.Get("request_method").Exists() || endpointConfig.Get("path").Exists() {
158156
log.Warn("when endpoint_mode is envoy, endpoint request_method and path will be ignored")
159157
}
160158
case EndpointModeForwardAuth:
161159
requestMethodConfig := endpointConfig.Get("request_method")
162160
if !requestMethodConfig.Exists() {
163-
httpService.requestMethod = http.MethodGet
161+
httpService.RequestMethod = http.MethodGet
164162
} else {
165-
httpService.requestMethod = strings.ToUpper(requestMethodConfig.String())
163+
httpService.RequestMethod = strings.ToUpper(requestMethodConfig.String())
166164
}
167165

168166
pathConfig := endpointConfig.Get("path")
169167
if !pathConfig.Exists() {
170168
return errors.New("when endpoint_mode is forward_auth, endpoint path must not be empty")
171169
}
172-
httpService.path = pathConfig.String()
170+
httpService.Path = pathConfig.String()
173171

174172
if endpointConfig.Get("path_prefix").Exists() {
175173
log.Warn("when endpoint_mode is forward_auth, endpoint path_prefix will be ignored")
@@ -189,35 +187,28 @@ func parseAuthorizationRequestConfig(json gjson.Result, httpService *HttpService
189187
if err != nil {
190188
return err
191189
}
192-
authorizationRequest.allowedHeaders = result
190+
authorizationRequest.AllowedHeaders = result
193191
}
194192

195-
headersToAdd := map[string]string{}
196-
headersToAddConfig := authorizationRequestConfig.Get("headers_to_add")
197-
if headersToAddConfig.Exists() {
198-
for key, value := range headersToAddConfig.Map() {
199-
headersToAdd[key] = value.Str
200-
}
201-
}
202-
authorizationRequest.headersToAdd = headersToAdd
193+
authorizationRequest.HeadersToAdd = convertToStringMap(authorizationRequestConfig.Get("headers_to_add"))
203194

204195
withRequestBody := authorizationRequestConfig.Get("with_request_body")
205196
if withRequestBody.Exists() {
206197
// withRequestBody is true and the request method is GET, OPTIONS or HEAD
207198
if withRequestBody.Bool() &&
208-
(httpService.requestMethod == http.MethodGet || httpService.requestMethod == http.MethodOptions || httpService.requestMethod == http.MethodHead) {
209-
return errors.New(fmt.Sprintf("requestMethod %s does not support with_request_body set to true", httpService.requestMethod))
199+
(httpService.RequestMethod == http.MethodGet || httpService.RequestMethod == http.MethodOptions || httpService.RequestMethod == http.MethodHead) {
200+
return errors.New(fmt.Sprintf("requestMethod %s does not support with_request_body set to true", httpService.RequestMethod))
210201
}
211-
authorizationRequest.withRequestBody = withRequestBody.Bool()
202+
authorizationRequest.WithRequestBody = withRequestBody.Bool()
212203
}
213204

214205
maxRequestBodyBytes := uint32(authorizationRequestConfig.Get("max_request_body_bytes").Uint())
215206
if maxRequestBodyBytes == 0 {
216207
maxRequestBodyBytes = DefaultMaxRequestBodyBytes
217208
}
218-
authorizationRequest.maxRequestBodyBytes = maxRequestBodyBytes
209+
authorizationRequest.MaxRequestBodyBytes = maxRequestBodyBytes
219210

220-
httpService.authorizationRequest = authorizationRequest
211+
httpService.AuthorizationRequest = authorizationRequest
221212
}
222213
return nil
223214
}
@@ -233,7 +224,7 @@ func parseAuthorizationResponseConfig(json gjson.Result, httpService *HttpServic
233224
if err != nil {
234225
return err
235226
}
236-
authorizationResponse.allowedUpstreamHeaders = result
227+
authorizationResponse.AllowedUpstreamHeaders = result
237228
}
238229

239230
allowedClientHeaders := authorizationResponseConfig.Get("allowed_client_headers")
@@ -242,10 +233,62 @@ func parseAuthorizationResponseConfig(json gjson.Result, httpService *HttpServic
242233
if err != nil {
243234
return err
244235
}
245-
authorizationResponse.allowedClientHeaders = result
236+
authorizationResponse.AllowedClientHeaders = result
237+
}
238+
239+
httpService.AuthorizationResponse = authorizationResponse
240+
}
241+
return nil
242+
}
243+
244+
func parseMatchRules(json gjson.Result, config *ExtAuthConfig, log wrapper.Log) error {
245+
matchListConfig := json.Get("match_list")
246+
if !matchListConfig.Exists() {
247+
config.MatchRules = expr.MatchRulesDefaults()
248+
return nil
249+
}
250+
251+
matchType := json.Get("match_type")
252+
if !matchType.Exists() {
253+
return errors.New("missing match_type in config")
254+
}
255+
if matchType.Str != expr.ModeWhitelist && matchType.Str != expr.ModeBlacklist {
256+
return errors.New("invalid match_type in config, must be 'whitelist' or 'blacklist'")
257+
}
258+
259+
ruleList := make([]expr.Rule, 0)
260+
var err error
261+
262+
matchListConfig.ForEach(func(key, value gjson.Result) bool {
263+
pathMatcher, err := expr.BuildStringMatcher(
264+
value.Get("match_rule_type").Str,
265+
value.Get("match_rule_path").Str, false)
266+
if err != nil {
267+
return false // stop iterating
246268
}
269+
ruleList = append(ruleList, expr.Rule{
270+
Domain: value.Get("match_rule_domain").Str,
271+
Path: pathMatcher,
272+
})
273+
return true // keep iterating
274+
})
247275

248-
httpService.authorizationResponse = authorizationResponse
276+
if err != nil {
277+
return fmt.Errorf("failed to build string matcher for rule %v: %w", matchListConfig, err)
278+
}
279+
280+
config.MatchRules = expr.MatchRules{
281+
Mode: matchType.Str,
282+
RuleList: ruleList,
249283
}
250284
return nil
251285
}
286+
287+
func convertToStringMap(result gjson.Result) map[string]string {
288+
m := make(map[string]string)
289+
result.ForEach(func(key, value gjson.Result) bool {
290+
m[key.String()] = value.String()
291+
return true // keep iterating
292+
})
293+
return m
294+
}

0 commit comments

Comments
 (0)