forked from hpsony94/kong-plugin-jwt-firebase
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathconfigmap-kong-plugin-jwt-firebase.yaml
327 lines (255 loc) · 11.2 KB
/
configmap-kong-plugin-jwt-firebase.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
apiVersion: v1
data:
constants.lua: |
return {
HEADERS = {
TOKEN_USER_ID = "X-Token-User-Id"
}
}
handler.lua: >
local constants = require "kong.constants"
local local_constants = require "kong.plugins.jwt-firebase.constants"
local jwt_decoder = require "kong.plugins.jwt.jwt_parser"
local openssl_pkey = require "resty.openssl.pkey"
local http = require "resty.http"
local shm = "/dev/shm/kong.jwt-firebase.pubkey"
local fmt = string.format
local kong = kong
local type = type
local ipairs = ipairs
local tostring = tostring
local re_gmatch = ngx.re.gmatch
local re_match = ngx.re.match
local ngx_set_header = ngx.req.set_header
local JwtHandler = {}
JwtHandler.PRIORITY = 70
JwtHandler.VERSION = "1.0.0"
--- Grab a public key from google api by the kid value
-- Grab the public key from
https://www.googleapis.com/robot/v1/metadata/x509/[email protected]
-- and use a JWT library to verify the signature. Use the value of max-age
in the Cache-Control header of the response
-- from that endpoint to know when to refresh the public keys.
local function grab_public_key_bykid(t_kid)
kong.log.debug("### grab_public_key_bykid() " .. t_kid)
kong.log.debug("### Grabbing pubkey from google ..")
local google_url = "https://www.googleapis.com/robot/v1/metadata/x509/[email protected]"
local magic = " | cut -d \"\\\"\" -f4- | sed 's/\\\\n/\\n/g\' | sed 's/\"//g' | openssl x509 -pubkey -noout"
local httpc = http:new()
local res, err = httpc:request_uri(google_url, {
method = "GET",
ssl_verify = false
})
local resWithoutNL = string.gsub(string.gsub(res.body, "\n", ""), "\r", "")
kong.log.debug("### Response:" .. resWithoutNL)
local cmd = "echo '" .. res.body .. "' | grep -i " .. t_kid .. magic
kong.log.debug("### cmd: " .. cmd)
local cmd_handle = io.popen(cmd)
local public_key = cmd_handle:read("*a")
cmd_handle:close()
kong.log.debug ("### public_key : " .. public_key)
return public_key
end
--- Push public key into /dev/shm
local function push_public_key_into_file(publickey, dir)
kong.log.debug("### push_public_key_into_file() - " .. publickey .. " - " .. dir)
local cmd = "echo -n \"" .. publickey .. "\" > " .. shm
kong.log.debug("### cmd: " .. cmd)
local cmd_handle, err = io.popen(cmd)
if not cmd_handle then
cmd_handlel:close()
return false
end
cmd_handle:close()
return true
end
--- Get the public key from /dev/shm
local function get_public_key_from_file(dir)
kong.log.debug("### get_public_key_from_file(): " .. dir)
local file, err = io.open(dir, "r")
if not file then
return nil
end
io.input(file)
content = io.read("*a")
io.close(file)
return content
end
--- Retrieve a JWT in a request.
-- Checks for the JWT in URI parameters, then in cookies, and finally
-- in the `Authorization` header.
-- @param request ngx request object
-- @param conf Plugin configuration
-- @return token JWT token contained in request (can be a table) or nil
-- @return err
local function retrieve_token(conf)
local args = kong.request.get_query()
for _, v in ipairs(conf.uri_param_names) do
if args[v] then
return args[v]
end
end
local var = ngx.var
for _, v in ipairs(conf.cookie_names) do
local cookie = var["cookie_" .. v]
if cookie and cookie ~= "" then
return cookie
end
end
local authorization_header = kong.request.get_header("authorization")
if authorization_header then
local m, err = re_match(authorization_header, "\\s*[Bb]earer\\s+(.+)")
if not m then
return authorization_header
end
local iterator, iter_err = re_gmatch(authorization_header, "\\s*[Bb]earer\\s+(.+)")
if not iterator then
return nil, iter_err
end
local m, err = iterator()
if err then
return nil, err
end
if m and #m > 0 then
return m[1]
end
end
end
--- do_authentication is to verify JWT firebase token
--- ref to: https://firebase.google.com/docs/auth/admin/verify-id-tokens
local function do_authentication(conf)
local token, err = retrieve_token(conf)
if err then
kong.log.err(err)
return kong.response.exit(500, { message = "An unexpected error occurred" })
end
local token_type = type(token)
if token_type ~= "string" then
if token_type == "nil" then
return false, { status = 401, message = "Unauthorized" }
elseif token_type == "table" then
return false, { status = 401, message = "Multiple tokens provided" }
else
return false, { status = 401, message = "Unrecognizable token" }
end
end
-- Decode token
local jwt, err = jwt_decoder:new(token)
if err then
return false, { status = 401, message = "Bad token; " .. tostring(err) }
end
local claims = jwt.claims
local header = jwt.header
-- Verify Header
-- -- Verify "alg"
local hd_alg = jwt.header.alg
kong.log.debug("### header.alg: " .. hd_alg)
if not hd_alg or hd_alg ~= "RS256" then
return false, { status = 401, message = "Invalid algorithm" }
end
-- Verify Payload
-- -- Verify "iss"
local pl_iss = jwt.claims.iss
kong.log.debug("### payload.iss : " .. pl_iss)
local conf_iss = "https://securetoken.google.com/" .. conf.project_id
kong.log.debug("### conf_iss: " .. conf_iss)
if not pl_iss or pl_iss ~= conf_iss then
return false, { status = 401, message = "Invalid iss in the header" }
end
-- -- Verify the "aud"
local pl_aud = jwt.claims.aud
kong.log.debug("### payload.aud: " .. pl_aud)
kong.log.debug("### conf.project_id: " .. conf.project_id)
if not pl_aud or pl_aud ~= conf.project_id then
return false, { status = 401, message = "Invalid aud in the header"}
end
-- -- Verify the "exp"
kong.log.debug("### Checking exp ... ")
local ok_claims, errors = jwt:verify_registered_claims(conf.claims_to_verify)
if not ok_claims then
return false, { status = 401, errors = errors }
end
-- -- Verify the "exp" with "maximum_expiration" value
kong.log.debug("### Checking additional maximum expiration ...")
if conf.maximum_expiration ~= nil and conf.maximum_expiration > 0 then
local ok, errors = jwt:check_maximum_expiration(conf.maximum_expiration)
if not ok then
return false, { status = 401, errors = errors }
end
end
-- -- Verify the "sub" must be non-empty
local pl_sub = jwt.claims.sub
kong.log.debug("### payload.sub: " .. pl_sub)
if not pl_sub then
return false, { status = 401, message = "the sub must be non-empty in the header" }
end
-- -- Pud user-id into request header
if conf.uid_inreq_header then
ngx_set_header(local_constants.HEADERS.TOKEN_USER_ID, pl_sub)
kong.log.debug("### Set " .. local_constants.HEADERS.TOKEN_USER_ID .. ": " .. pl_sub .. "in the request header")
end
-- Finally -- Verify the signature
-- Finally, ensure that the ID token was signed by the private key corresponding to the token's kid claim.
-- Grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/[email protected]
-- and use a JWT library to verify the signature. Use the value of max-age in the Cache-Control header of the response
-- from that endpoint to know when to refresh the public keys.
-- Now verify the JWT signature
local kid = jwt.header.kid
-- -- Get public key in memory file
-- -- -- if it is invalied or empty
-- -- -- -- grabs a new public key from google api
-- -- -- -- push this key into memory file
-- -- -- -- assign this key to public_key
local public_key = get_public_key_from_file(shm)
kong.log.debug(public_key)
if not pcall(openssl_pkey.new, public_key) or public_key == nil then
kong.log.info("Public key in a file is empty or invalid")
--local t_public_key = grab_1st_public_key()
local t_public_key = grab_public_key_bykid(kid)
local ok, err = push_public_key_into_file(t_public_key, shm)
if not ok then
kong.log.err("### ERROR: Failed to push a new publish key into SHM dir! FIX IT NOW")
end
public_key = t_public_key
end
-- -- By using jwt lib to verify signature
-- -- If failed
-- -- -- grab a new public key from the google api
-- -- -- store this public key into memory file if it verifies successful at 2nd time
if not jwt:verify_signature(public_key) then
kong.log.debug("### Grabbing pubkey from google URL ...")
local t_public_key = grab_public_key_bykid(kid)
if jwt:verify_signature(t_public_key) then
local ok, err = push_public_key_into_file(t_public_key, shm)
if not ok then
kong.log.err("### ERROR: Failed to push a new publish key into SHM dir! FIX IT NOW")
end
return true
end
return false, { status = 401, message = "Invalid signature" }
end
return true
end
function JwtHandler:access(conf)
local ok, err = do_authentication(conf)
if not ok then
return kong.response.exit(err.status, err.errors or { message = err.message })
end
end
return JwtHandler
schema.lua: "local typedefs = require \"kong.db.schema.typedefs\"\n\n\nreturn {\n name = \"jwt-firebase\",\n fields = {\n { protocols = typedefs.protocols_http },\n { config = {\n type = \"record\",\n fields = {\n { uri_param_names = {\n type = \"set\",\n elements = { type = \"string\" },\n default = { \"jwt\" },\n }, },\n { cookie_names = {\n type = \"set\",\n elements = { type = \"string\" },\n default = {}\n }, },\n\t { uid_inreq_header = { type = \"boolean\", default = true }, },\n { claims_to_verify = {\n type = \"set\",\n\t default = { \"exp\" },\n elements = {\n type = \"string\",\n one_of = { \"exp\", \"nbf\" },\n }, }, },\n { maximum_expiration = {\n type = \"number\",\n default = 0,\n between = { 0, 31536000 },\n }, },\n\t {project_id = { type = \"string\", default = \"fb-project\" }, },\n },\n },\n },\n },\n}\n"
kind: ConfigMap
metadata:
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
'f:data':
.: {}
'f:constants.lua': {}
'f:handler.lua': {}
'f:schema.lua': {}
manager: kubectl-create
operation: Update
time: '2021-04-18T09:38:15Z'
name: kong-plugin-jwt-firebase