@kumori.systems/components-apisix 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/kumori.systems/components/apisix/@0.0.1/README.adoc +130 -0
- package/kumori.systems/components/apisix/@0.0.1/assets/images/routing_diagram.png +0 -0
- package/kumori.systems/components/apisix/@0.0.1/componentref.cue +26 -0
- package/kumori.systems/components/apisix/@0.0.1/cue.mod/module.cue +1 -0
- package/kumori.systems/components/apisix/@0.0.1/kmodule.cue +30 -0
- package/kumori.systems/components/apisix/@0.0.1/manifest.cue +159 -0
- package/kumori.systems/components/apisix/@0.0.1/openid-connect.cue +587 -0
- package/kumori.systems/components/apisix/@0.0.1/serviceref.cue +26 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/apisix_settings.cue +33 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/entrypoint.cue +38 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/connections.cue +131 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/consumer.cue +6 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/globalrule.cue +6 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/healthcheck.cue +39 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/plugin.cue +20 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/pluginmetadata.cue +6 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/route.cue +34 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/service.cue +6 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/ssl.cue +11 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/streamroute.cue +6 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/upstream.cue +35 -0
- package/kumori.systems/components/apisix/@0.0.1/settings/types/utils.cue +11 -0
- package/package.json +1 -0
@@ -0,0 +1,587 @@
|
|
1
|
+
package component
|
2
|
+
|
3
|
+
#openid_connect_lua: ###"""
|
4
|
+
--
|
5
|
+
-- Licensed to the Apache Software Foundation (ASF) under one or more
|
6
|
+
-- contributor license agreements. See the NOTICE file distributed with
|
7
|
+
-- this work for additional information regarding copyright ownership.
|
8
|
+
-- The ASF licenses this file to You under the Apache License, Version 2.0
|
9
|
+
-- (the "License"); you may not use this file except in compliance with
|
10
|
+
-- the License. You may obtain a copy of the License at
|
11
|
+
--
|
12
|
+
-- http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
--
|
14
|
+
-- Unless required by applicable law or agreed to in writing, software
|
15
|
+
-- distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
-- See the License for the specific language governing permissions and
|
18
|
+
-- limitations under the License.
|
19
|
+
--
|
20
|
+
|
21
|
+
local core = require("apisix.core")
|
22
|
+
local ngx_re = require("ngx.re")
|
23
|
+
local openidc = require("resty.openidc")
|
24
|
+
local random = require("resty.random")
|
25
|
+
local string = string
|
26
|
+
local ngx = ngx
|
27
|
+
local ipairs = ipairs
|
28
|
+
local concat = table.concat
|
29
|
+
|
30
|
+
local ngx_encode_base64 = ngx.encode_base64
|
31
|
+
|
32
|
+
local plugin_name = "openid-connect"
|
33
|
+
|
34
|
+
|
35
|
+
local schema = {
|
36
|
+
type = "object",
|
37
|
+
properties = {
|
38
|
+
client_id = {type = "string"},
|
39
|
+
client_secret = {type = "string"},
|
40
|
+
discovery = {type = "string"},
|
41
|
+
scope = {
|
42
|
+
type = "string",
|
43
|
+
default = "openid",
|
44
|
+
},
|
45
|
+
ssl_verify = {
|
46
|
+
type = "boolean",
|
47
|
+
default = false,
|
48
|
+
},
|
49
|
+
timeout = {
|
50
|
+
type = "integer",
|
51
|
+
minimum = 1,
|
52
|
+
default = 3,
|
53
|
+
description = "timeout in seconds",
|
54
|
+
},
|
55
|
+
introspection_endpoint = {
|
56
|
+
type = "string"
|
57
|
+
},
|
58
|
+
introspection_endpoint_auth_method = {
|
59
|
+
type = "string",
|
60
|
+
default = "client_secret_basic"
|
61
|
+
},
|
62
|
+
token_endpoint_auth_method = {
|
63
|
+
type = "string",
|
64
|
+
default = "client_secret_basic"
|
65
|
+
},
|
66
|
+
bearer_only = {
|
67
|
+
type = "boolean",
|
68
|
+
default = false,
|
69
|
+
},
|
70
|
+
session = {
|
71
|
+
type = "object",
|
72
|
+
properties = {
|
73
|
+
secret = {
|
74
|
+
type = "string",
|
75
|
+
description = "the key used for the encrypt and HMAC calculation",
|
76
|
+
minLength = 16,
|
77
|
+
},
|
78
|
+
},
|
79
|
+
required = {"secret"},
|
80
|
+
additionalProperties = false,
|
81
|
+
},
|
82
|
+
realm = {
|
83
|
+
type = "string",
|
84
|
+
default = "apisix",
|
85
|
+
},
|
86
|
+
logout_path = {
|
87
|
+
type = "string",
|
88
|
+
default = "/logout",
|
89
|
+
},
|
90
|
+
redirect_uri = {
|
91
|
+
type = "string",
|
92
|
+
description = "auto append '.apisix/redirect' to ngx.var.uri if not configured"
|
93
|
+
},
|
94
|
+
post_logout_redirect_uri = {
|
95
|
+
type = "string",
|
96
|
+
description = "the URI will be redirect when request logout_path",
|
97
|
+
},
|
98
|
+
unauth_action = {
|
99
|
+
type = "string",
|
100
|
+
default = "auth",
|
101
|
+
enum = {"auth", "deny", "pass"},
|
102
|
+
description = "The action performed when client is not authorized. Use auth to " ..
|
103
|
+
"redirect user to identity provider, deny to respond with 401 Unauthorized, and " ..
|
104
|
+
"pass to allow the request regardless."
|
105
|
+
},
|
106
|
+
public_key = {type = "string"},
|
107
|
+
token_signing_alg_values_expected = {type = "string"},
|
108
|
+
use_pkce = {
|
109
|
+
description = "when set to true the PKEC(Proof Key for Code Exchange) will be used.",
|
110
|
+
type = "boolean",
|
111
|
+
default = false
|
112
|
+
},
|
113
|
+
set_access_token_header = {
|
114
|
+
description = "Whether the access token should be added as a header to the request " ..
|
115
|
+
"for downstream",
|
116
|
+
type = "boolean",
|
117
|
+
default = true
|
118
|
+
},
|
119
|
+
access_token_in_authorization_header = {
|
120
|
+
description = "Whether the access token should be added in the Authorization " ..
|
121
|
+
"header as opposed to the X-Access-Token header.",
|
122
|
+
type = "boolean",
|
123
|
+
default = false
|
124
|
+
},
|
125
|
+
set_id_token_header = {
|
126
|
+
description = "Whether the ID token should be added in the X-ID-Token header to " ..
|
127
|
+
"the request for downstream.",
|
128
|
+
type = "boolean",
|
129
|
+
default = true
|
130
|
+
},
|
131
|
+
set_userinfo_header = {
|
132
|
+
description = "Whether the user info token should be added in the X-Userinfo " ..
|
133
|
+
"header to the request for downstream.",
|
134
|
+
type = "boolean",
|
135
|
+
default = true
|
136
|
+
},
|
137
|
+
set_refresh_token_header = {
|
138
|
+
description = "Whether the refresh token should be added in the X-Refresh-Token " ..
|
139
|
+
"header to the request for downstream.",
|
140
|
+
type = "boolean",
|
141
|
+
default = false
|
142
|
+
},
|
143
|
+
proxy_opts = {
|
144
|
+
description = "HTTP proxy server be used to access identity server.",
|
145
|
+
type = "object",
|
146
|
+
properties = {
|
147
|
+
http_proxy = {
|
148
|
+
type = "string",
|
149
|
+
description = "HTTP proxy like: http://proxy-server:80.",
|
150
|
+
},
|
151
|
+
https_proxy = {
|
152
|
+
type = "string",
|
153
|
+
description = "HTTPS proxy like: http://proxy-server:80.",
|
154
|
+
},
|
155
|
+
http_proxy_authorization = {
|
156
|
+
type = "string",
|
157
|
+
description = "Basic [base64 username:password].",
|
158
|
+
},
|
159
|
+
https_proxy_authorization = {
|
160
|
+
type = "string",
|
161
|
+
description = "Basic [base64 username:password].",
|
162
|
+
},
|
163
|
+
no_proxy = {
|
164
|
+
type = "string",
|
165
|
+
description = "Comma separated list of hosts that should not be proxied.",
|
166
|
+
}
|
167
|
+
},
|
168
|
+
},
|
169
|
+
authorization_params = {
|
170
|
+
description = "Extra authorization params to the authorize endpoint",
|
171
|
+
type = "object"
|
172
|
+
},
|
173
|
+
client_rsa_private_key = {
|
174
|
+
description = "Client RSA private key used to sign JWT.",
|
175
|
+
type = "string"
|
176
|
+
},
|
177
|
+
client_rsa_private_key_id = {
|
178
|
+
description = "Client RSA private key ID used to compute a signed JWT.",
|
179
|
+
type = "string"
|
180
|
+
},
|
181
|
+
client_jwt_assertion_expires_in = {
|
182
|
+
description = "Life duration of the signed JWT in seconds.",
|
183
|
+
type = "integer",
|
184
|
+
default = 60
|
185
|
+
},
|
186
|
+
renew_access_token_on_expiry = {
|
187
|
+
description = "Whether to attempt silently renewing the access token.",
|
188
|
+
type = "boolean",
|
189
|
+
default = true
|
190
|
+
},
|
191
|
+
access_token_expires_in = {
|
192
|
+
description = "Lifetime of the access token in seconds if expires_in is not present.",
|
193
|
+
type = "integer"
|
194
|
+
},
|
195
|
+
refresh_session_interval = {
|
196
|
+
description = "Time interval to refresh user ID token without re-authentication.",
|
197
|
+
type = "integer"
|
198
|
+
},
|
199
|
+
iat_slack = {
|
200
|
+
description = "Tolerance of clock skew in seconds with the iat claim in an ID token.",
|
201
|
+
type = "integer",
|
202
|
+
default = 120
|
203
|
+
},
|
204
|
+
accept_none_alg = {
|
205
|
+
description = "Set to true if the OpenID provider does not sign its ID token.",
|
206
|
+
type = "boolean",
|
207
|
+
default = false
|
208
|
+
},
|
209
|
+
accept_unsupported_alg = {
|
210
|
+
description = "Ignore ID token signature to accept unsupported signature algorithm.",
|
211
|
+
type = "boolean",
|
212
|
+
default = true
|
213
|
+
},
|
214
|
+
access_token_expires_leeway = {
|
215
|
+
description = "Expiration leeway in seconds for access token renewal.",
|
216
|
+
type = "integer",
|
217
|
+
default = 0
|
218
|
+
},
|
219
|
+
force_reauthorize = {
|
220
|
+
description = "Whether to execute the authorization flow when a token has been cached.",
|
221
|
+
type = "boolean",
|
222
|
+
default = false
|
223
|
+
},
|
224
|
+
use_nonce = {
|
225
|
+
description = "Whether to include nonce parameter in authorization request.",
|
226
|
+
type = "boolean",
|
227
|
+
default = false
|
228
|
+
},
|
229
|
+
revoke_tokens_on_logout = {
|
230
|
+
description = "Notify authorization server a previous token is no longer needed.",
|
231
|
+
type = "boolean",
|
232
|
+
default = false
|
233
|
+
},
|
234
|
+
jwk_expires_in = {
|
235
|
+
description = "Expiration time for JWK cache in seconds.",
|
236
|
+
type = "integer",
|
237
|
+
default = 86400
|
238
|
+
},
|
239
|
+
jwt_verification_cache_ignore = {
|
240
|
+
description = "Whether to ignore cached verification and re-verify.",
|
241
|
+
type = "boolean",
|
242
|
+
default = false
|
243
|
+
},
|
244
|
+
cache_segment = {
|
245
|
+
description = "Name of a cache segment to differentiate caches.",
|
246
|
+
type = "string"
|
247
|
+
},
|
248
|
+
introspection_interval = {
|
249
|
+
description = "TTL of the cached and introspected access token in seconds.",
|
250
|
+
type = "integer",
|
251
|
+
default = 0
|
252
|
+
},
|
253
|
+
introspection_expiry_claim = {
|
254
|
+
description = "Name of the expiry claim that controls the cached access token TTL.",
|
255
|
+
type = "string"
|
256
|
+
},
|
257
|
+
required_scopes = {
|
258
|
+
description = "List of scopes that are required to be granted to the access token",
|
259
|
+
type = "array",
|
260
|
+
items = {
|
261
|
+
type = "string"
|
262
|
+
}
|
263
|
+
}
|
264
|
+
},
|
265
|
+
encrypt_fields = {"client_secret"},
|
266
|
+
required = {"client_id", "client_secret", "discovery"}
|
267
|
+
}
|
268
|
+
|
269
|
+
|
270
|
+
local _M = {
|
271
|
+
version = 0.2,
|
272
|
+
priority = 2599,
|
273
|
+
name = plugin_name,
|
274
|
+
schema = schema,
|
275
|
+
}
|
276
|
+
|
277
|
+
|
278
|
+
function _M.check_schema(conf)
|
279
|
+
if conf.ssl_verify == "no" then
|
280
|
+
-- we used to set 'ssl_verify' to "no"
|
281
|
+
conf.ssl_verify = false
|
282
|
+
end
|
283
|
+
|
284
|
+
if not conf.bearer_only and not conf.session then
|
285
|
+
core.log.warn("when bearer_only = false, " ..
|
286
|
+
"you'd better complete the session configuration manually")
|
287
|
+
conf.session = {
|
288
|
+
-- generate a secret when bearer_only = false and no secret is configured
|
289
|
+
secret = ngx_encode_base64(random.bytes(32, true) or random.bytes(32))
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
local ok, err = core.schema.check(schema, conf)
|
294
|
+
if not ok then
|
295
|
+
return false, err
|
296
|
+
end
|
297
|
+
|
298
|
+
return true
|
299
|
+
end
|
300
|
+
|
301
|
+
|
302
|
+
local function get_bearer_access_token(ctx)
|
303
|
+
-- Get Authorization header, maybe.
|
304
|
+
local auth_header = core.request.header(ctx, "Authorization")
|
305
|
+
if not auth_header then
|
306
|
+
-- No Authorization header, get X-Access-Token header, maybe.
|
307
|
+
local access_token_header = core.request.header(ctx, "X-Access-Token")
|
308
|
+
if not access_token_header then
|
309
|
+
-- No X-Access-Token header neither.
|
310
|
+
return false, nil, nil
|
311
|
+
end
|
312
|
+
|
313
|
+
-- Return extracted header value.
|
314
|
+
return true, access_token_header, nil
|
315
|
+
end
|
316
|
+
|
317
|
+
-- Check format of Authorization header.
|
318
|
+
local res, err = ngx_re.split(auth_header, " ", nil, nil, 2)
|
319
|
+
|
320
|
+
if not res then
|
321
|
+
-- No result was returned.
|
322
|
+
return false, nil, err
|
323
|
+
elseif #res < 2 then
|
324
|
+
-- Header doesn't split into enough tokens.
|
325
|
+
return false, nil, "Invalid Authorization header format."
|
326
|
+
end
|
327
|
+
|
328
|
+
if string.lower(res[1]) == "bearer" then
|
329
|
+
-- Return extracted token.
|
330
|
+
return true, res[2], nil
|
331
|
+
end
|
332
|
+
|
333
|
+
return false, nil, nil
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
local function introspect(ctx, conf)
|
338
|
+
-- Extract token, maybe.
|
339
|
+
local has_token, token, err = get_bearer_access_token(ctx)
|
340
|
+
|
341
|
+
if err then
|
342
|
+
return ngx.HTTP_BAD_REQUEST, err, nil, nil
|
343
|
+
end
|
344
|
+
|
345
|
+
if not has_token then
|
346
|
+
-- Could not find token.
|
347
|
+
|
348
|
+
if conf.bearer_only then
|
349
|
+
-- Token strictly required in request.
|
350
|
+
ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm .. '"'
|
351
|
+
return ngx.HTTP_UNAUTHORIZED, "No bearer token found in request.", nil, nil
|
352
|
+
else
|
353
|
+
-- Return empty result.
|
354
|
+
return nil, nil, nil, nil
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
-- If we get here, token was found in request.
|
359
|
+
|
360
|
+
if conf.public_key or conf.use_jwks then
|
361
|
+
-- Validate token against public key or jwks document of the oidc provider.
|
362
|
+
-- TODO: In the called method, the openidc module will try to extract
|
363
|
+
-- the token by itself again -- from a request header or session cookie.
|
364
|
+
-- It is inefficient that we also need to extract it (just from headers)
|
365
|
+
-- so we can add it in the configured header. Find a way to use openidc
|
366
|
+
-- module's internal methods to extract the token.
|
367
|
+
local res, err = openidc.bearer_jwt_verify(conf)
|
368
|
+
|
369
|
+
if err then
|
370
|
+
-- Error while validating or token invalid.
|
371
|
+
ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm ..
|
372
|
+
'", error="invalid_token", error_description="' .. err .. '"'
|
373
|
+
return ngx.HTTP_UNAUTHORIZED, err, nil, nil
|
374
|
+
end
|
375
|
+
|
376
|
+
-- Token successfully validated.
|
377
|
+
local method = (conf.public_key and "public_key") or (conf.use_jwks and "jwks")
|
378
|
+
core.log.debug("token validate successfully by ", method)
|
379
|
+
return res, err, token, res
|
380
|
+
else
|
381
|
+
-- Validate token against introspection endpoint.
|
382
|
+
-- TODO: Same as above for public key validation.
|
383
|
+
local res, err = openidc.introspect(conf)
|
384
|
+
|
385
|
+
if err then
|
386
|
+
ngx.header["WWW-Authenticate"] = 'Bearer realm="' .. conf.realm ..
|
387
|
+
'", error="invalid_token", error_description="' .. err .. '"'
|
388
|
+
return ngx.HTTP_UNAUTHORIZED, err, nil, nil
|
389
|
+
end
|
390
|
+
|
391
|
+
-- Token successfully validated and response from the introspection
|
392
|
+
-- endpoint contains the userinfo.
|
393
|
+
core.log.debug("token validate successfully by introspection")
|
394
|
+
return res, err, token, res
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
local function add_access_token_header(ctx, conf, token)
|
400
|
+
if token then
|
401
|
+
-- Add Authorization or X-Access-Token header, respectively, if not already set.
|
402
|
+
if conf.set_access_token_header then
|
403
|
+
if conf.access_token_in_authorization_header then
|
404
|
+
if not core.request.header(ctx, "Authorization") then
|
405
|
+
-- Add Authorization header.
|
406
|
+
core.request.set_header(ctx, "Authorization", "Bearer " .. token)
|
407
|
+
end
|
408
|
+
else
|
409
|
+
if not core.request.header(ctx, "X-Access-Token") then
|
410
|
+
-- Add X-Access-Token header.
|
411
|
+
core.request.set_header(ctx, "X-Access-Token", token)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
-- Function to split the scope string into a table
|
419
|
+
local function split_scopes_by_space(scope_string)
|
420
|
+
local scopes = {}
|
421
|
+
for scope in string.gmatch(scope_string, "%S+") do
|
422
|
+
scopes[scope] = true
|
423
|
+
end
|
424
|
+
return scopes
|
425
|
+
end
|
426
|
+
|
427
|
+
-- Function to check if all required scopes are present
|
428
|
+
local function required_scopes_present(required_scopes, http_scopes)
|
429
|
+
for _, scope in ipairs(required_scopes) do
|
430
|
+
if not http_scopes[scope] then
|
431
|
+
return false
|
432
|
+
end
|
433
|
+
end
|
434
|
+
return true
|
435
|
+
end
|
436
|
+
|
437
|
+
function _M.rewrite(plugin_conf, ctx)
|
438
|
+
local conf = core.table.clone(plugin_conf)
|
439
|
+
|
440
|
+
-- Previously, we multiply conf.timeout before storing it in etcd.
|
441
|
+
-- If the timeout is too large, we should not multiply it again.
|
442
|
+
if not (conf.timeout >= 1000 and conf.timeout % 1000 == 0) then
|
443
|
+
conf.timeout = conf.timeout * 1000
|
444
|
+
end
|
445
|
+
|
446
|
+
local path = ctx.var.request_uri
|
447
|
+
|
448
|
+
if not conf.redirect_uri then
|
449
|
+
-- NOTE: 'lua-resty-openidc' requires that 'redirect_uri' be
|
450
|
+
-- different from 'uri'. So default to append the
|
451
|
+
-- '.apisix/redirect' suffix if not configured.
|
452
|
+
local suffix = "/.apisix/redirect"
|
453
|
+
local uri = ctx.var.uri
|
454
|
+
if core.string.has_suffix(uri, suffix) then
|
455
|
+
-- This is the redirection response from the OIDC provider.
|
456
|
+
conf.redirect_uri = uri
|
457
|
+
else
|
458
|
+
if string.sub(uri, -1, -1) == "/" then
|
459
|
+
conf.redirect_uri = string.sub(uri, 1, -2) .. suffix
|
460
|
+
else
|
461
|
+
conf.redirect_uri = uri .. suffix
|
462
|
+
end
|
463
|
+
end
|
464
|
+
core.log.debug("auto set redirect_uri: ", conf.redirect_uri)
|
465
|
+
end
|
466
|
+
|
467
|
+
if not conf.ssl_verify then
|
468
|
+
-- openidc use "no" to disable ssl verification
|
469
|
+
conf.ssl_verify = "no"
|
470
|
+
end
|
471
|
+
|
472
|
+
if path == (conf.logout_path or "/logout") then
|
473
|
+
local discovery, discovery_err = openidc.get_discovery_doc(conf)
|
474
|
+
if discovery_err then
|
475
|
+
core.log.error("OIDC access discovery url failed : ", discovery_err)
|
476
|
+
return 503
|
477
|
+
end
|
478
|
+
if conf.post_logout_redirect_uri and not discovery.end_session_endpoint then
|
479
|
+
-- If the end_session_endpoint field does not exist in the OpenID Provider Discovery
|
480
|
+
-- Metadata, the redirect_after_logout_uri field is used for redirection.
|
481
|
+
conf.redirect_after_logout_uri = conf.post_logout_redirect_uri
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
local response, err, session, _
|
486
|
+
|
487
|
+
if conf.bearer_only or conf.introspection_endpoint or conf.public_key or conf.use_jwks then
|
488
|
+
-- An introspection endpoint or a public key has been configured. Try to
|
489
|
+
-- validate the access token from the request, if it is present in a
|
490
|
+
-- request header. Otherwise, return a nil response. See below for
|
491
|
+
-- handling of the case where the access token is stored in a session cookie.
|
492
|
+
local access_token, userinfo
|
493
|
+
response, err, access_token, userinfo = introspect(ctx, conf)
|
494
|
+
|
495
|
+
if err then
|
496
|
+
-- Error while validating token or invalid token.
|
497
|
+
core.log.error("OIDC introspection failed: ", err)
|
498
|
+
return response
|
499
|
+
end
|
500
|
+
|
501
|
+
if response then
|
502
|
+
if conf.required_scopes then
|
503
|
+
local http_scopes = response.scope and split_scopes_by_space(response.scope) or {}
|
504
|
+
local is_authorized = required_scopes_present(conf.required_scopes, http_scopes)
|
505
|
+
if not is_authorized then
|
506
|
+
core.log.error("OIDC introspection failed: ", "required scopes not present")
|
507
|
+
local error_response = {
|
508
|
+
error = "required scopes " .. concat(conf.required_scopes, ", ") ..
|
509
|
+
" not present"
|
510
|
+
}
|
511
|
+
return 403, core.json.encode(error_response)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
-- Add configured access token header, maybe.
|
515
|
+
add_access_token_header(ctx, conf, access_token)
|
516
|
+
|
517
|
+
if userinfo and conf.set_userinfo_header then
|
518
|
+
-- Set X-Userinfo header to introspection endpoint response.
|
519
|
+
core.request.set_header(ctx, "X-Userinfo",
|
520
|
+
ngx_encode_base64(core.json.encode(userinfo)))
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
if not response then
|
526
|
+
-- Either token validation via introspection endpoint or public key is
|
527
|
+
-- not configured, and/or token could not be extracted from the request.
|
528
|
+
|
529
|
+
local unauth_action = conf.unauth_action
|
530
|
+
if unauth_action ~= "auth" then
|
531
|
+
unauth_action = "deny"
|
532
|
+
end
|
533
|
+
|
534
|
+
-- Authenticate the request. This will validate the access token if it
|
535
|
+
-- is stored in a session cookie, and also renew the token if required.
|
536
|
+
-- If no token can be extracted, the response will redirect to the ID
|
537
|
+
-- provider's authorization endpoint to initiate the Relying Party flow.
|
538
|
+
-- This code path also handles when the ID provider then redirects to
|
539
|
+
-- the configured redirect URI after successful authentication.
|
540
|
+
response, err, _, session = openidc.authenticate(conf, nil, unauth_action, conf.session)
|
541
|
+
|
542
|
+
if err then
|
543
|
+
if err == "unauthorized request" then
|
544
|
+
if conf.unauth_action == "pass" then
|
545
|
+
return nil
|
546
|
+
end
|
547
|
+
return 401
|
548
|
+
end
|
549
|
+
core.log.error("OIDC authentication failed: ", err)
|
550
|
+
return 500
|
551
|
+
end
|
552
|
+
|
553
|
+
if response then
|
554
|
+
-- If the openidc module has returned a response, it may contain,
|
555
|
+
-- respectively, the access token, the ID token, the refresh token,
|
556
|
+
-- and the userinfo.
|
557
|
+
-- Add respective headers to the request, if so configured.
|
558
|
+
|
559
|
+
-- Add configured access token header, maybe.
|
560
|
+
add_access_token_header(ctx, conf, response.access_token)
|
561
|
+
|
562
|
+
-- Add X-ID-Token header, maybe.
|
563
|
+
if response.id_token and conf.set_id_token_header then
|
564
|
+
local token = core.json.encode(response.id_token)
|
565
|
+
core.request.set_header(ctx, "X-ID-Token", ngx.encode_base64(token))
|
566
|
+
end
|
567
|
+
|
568
|
+
-- Add X-Userinfo header, maybe.
|
569
|
+
if response.user and conf.set_userinfo_header then
|
570
|
+
core.request.set_header(ctx, "X-Userinfo",
|
571
|
+
ngx_encode_base64(core.json.encode(response.user)))
|
572
|
+
end
|
573
|
+
|
574
|
+
-- Add X-Refresh-Token header, maybe.
|
575
|
+
if session.data.refresh_token and conf.set_refresh_token_header then
|
576
|
+
core.request.set_header(ctx, "X-Refresh-Token", session.data.refresh_token)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
if session then
|
581
|
+
session:close()
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
|
586
|
+
return _M
|
587
|
+
"""###
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
// Automatically generated file. Do not edit.
|
3
|
+
package service
|
4
|
+
|
5
|
+
import (
|
6
|
+
k "kumori.systems/kumori/@1.1.6:kumori"
|
7
|
+
m "...:kmodule"
|
8
|
+
)
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
#Artifact: k.#Artifact & {
|
13
|
+
spec: m.spec
|
14
|
+
ref: {
|
15
|
+
version: m.version
|
16
|
+
if m.prerelease != _|_ {
|
17
|
+
prerelease: m.prerelease
|
18
|
+
}
|
19
|
+
if m.buildmetadata != _|_ {
|
20
|
+
buildmetadata: m.buildmetadata
|
21
|
+
}
|
22
|
+
domain: m.domain
|
23
|
+
module: m.module
|
24
|
+
kind: "service"
|
25
|
+
}
|
26
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
package settings
|
2
|
+
|
3
|
+
import (
|
4
|
+
Types ".../settings/types"
|
5
|
+
)
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
// https://apisix.apache.org/docs/apisix/deployment-modes/#standalone
|
10
|
+
#ApisixConfig: {
|
11
|
+
deployment: {
|
12
|
+
role: string | *"data_plane"
|
13
|
+
role_data_plane: config_provider: string | *"yaml"
|
14
|
+
}
|
15
|
+
apisix: {
|
16
|
+
enable_admin: false
|
17
|
+
config_center: "yaml"
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
#ApisixStandalone: {
|
22
|
+
routes?: Types.#Routes
|
23
|
+
services?: Types.#Services
|
24
|
+
upstreams?: Types.#Upstreams
|
25
|
+
plugins?: Types.#Plugins // List of plugins to load
|
26
|
+
plugin_configs?: Types.#PluginConfigs
|
27
|
+
ssls?: Types.#Ssls
|
28
|
+
stream_routes?: Types.#StreamRoutes
|
29
|
+
global_rules?: Types.#GlobalRules
|
30
|
+
consumers?: Types.#Consumers
|
31
|
+
plugin_metadata?: Types.#PluginsMetadata
|
32
|
+
...
|
33
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
package settings
|
2
|
+
|
3
|
+
#Entrypoint: ##"""
|
4
|
+
#!/usr/bin/env sh
|
5
|
+
|
6
|
+
set -x
|
7
|
+
|
8
|
+
apt update
|
9
|
+
apt-get install -y gettext-base
|
10
|
+
apt-get install -y inotify-tools
|
11
|
+
|
12
|
+
path="/tmp/apisix.yaml"
|
13
|
+
|
14
|
+
processApisixConfig() {
|
15
|
+
envsubst < /tmp/apisix.yaml > /tmp/apisix.yaml.tmp
|
16
|
+
echo "#END" >> /tmp/apisix.yaml.tmp
|
17
|
+
|
18
|
+
mv /tmp/apisix.yaml.tmp /usr/local/apisix/conf/apisix.yaml
|
19
|
+
}
|
20
|
+
|
21
|
+
processApisixConfig
|
22
|
+
|
23
|
+
# Wait for changes to the Apisix config file.
|
24
|
+
# This helps to laverage hot reloading in APISIX
|
25
|
+
while true; do
|
26
|
+
inotifywait -e modify "$path"
|
27
|
+
echo "$path was modified"
|
28
|
+
processApisixConfig
|
29
|
+
done &
|
30
|
+
|
31
|
+
set +x
|
32
|
+
|
33
|
+
if [ "$#" -eq 0 ]; then
|
34
|
+
exec apisix start
|
35
|
+
else
|
36
|
+
exec "$@"
|
37
|
+
fi
|
38
|
+
"""##
|