nginx

Paddy 2015-06-30 Parent:68478c1bddde

1:ac9c19126939 Go to Latest

nginx/jwt-lib/resty/jwt.lua

Make nginx kubernetes-ready. We had to update to use a ubuntu-based image to build nginx into, because (and I kid you not) alpine linux straight-up ignores your resolv.conf file, meaning any attempt to use it with kubernetes DNS is doomed to fail. Who thought this was a good idea? So we're using a bloated image instead. Oh well. We also are running a wrapper script instead of nginx directly, so we can inject the JWT_SECRET environment variable based on a kubernetes secret file. We define the secret file (using a placeholder secret, obvs) so that future-Paddy can remember what the hell it looks like, when he inevitably loses the file and needs to sin up a new cluster. Or whatever. Finally, we updated the token expiration error message to be in an errors array, as God (and our API conventions) intended.

History
1 local cjson = require "cjson"
2 local hmac = require "resty.hmac"
4 local _M = {_VERSION="0.0.1"}
5 local mt = {__index=_M}
8 local function get_raw_part(part_name, jwt_obj)
9 local raw_part = jwt_obj["raw_" .. part_name]
10 if raw_part == nil then
11 local part = jwt_obj[part_name]
12 if part == nil then
13 error({reason="missing part " .. part_name})
14 end
15 raw_part = _M:jwt_encode(part)
16 end
17 return raw_part
18 end
21 local function parse(token_str, secret, issuer)
22 local basic_jwt = {}
23 local raw_header, raw_payload, signature = string.match(
24 token_str,
25 '([^%.]+)%.([^%.]+)%.([^%.]+)'
26 )
27 local basic_jwt = {
28 raw_header=raw_header,
29 raw_payload=raw_payload,
30 header=_M:jwt_decode(raw_header, true),
31 payload=_M:jwt_decode(raw_payload, true),
32 signature=signature
33 }
34 return basic_jwt
35 end
38 function _M.jwt_encode(self, ori)
39 if type(ori) == "table" then
40 ori = cjson.encode(ori)
41 end
42 return ngx.encode_base64(ori):gsub("+", "-"):gsub("/", "_"):gsub("=", "")
43 end
46 function _M.jwt_decode(self, b64_str, json_decode)
47 local reminder = #b64_str % 4
48 if reminder > 0 then
49 b64_str = b64_str .. string.rep("=", 4 - reminder)
50 end
51 local data = ngx.decode_base64(b64_str)
52 if json_decode then
53 data = cjson.decode(data)
54 end
55 return data
56 end
59 function _M.sign(self, secret_key, jwt_obj)
60 -- header typ check
61 local typ = jwt_obj["header"]["typ"]
62 if typ ~= "JWT" then
63 error({reason="invalid typ: " .. typ})
64 end
65 -- header alg check
66 local alg = jwt_obj["header"]["alg"]
67 local hash_alg = nil
68 if alg == "HS256" then
69 hash_alg = hmac.ALGOS.SHA256
70 elseif alg == "HS512" then
71 hash_alg = hmac.ALGOS.SHA512
72 else
73 error({reason="unsupported alg: " .. alg})
74 end
75 -- assemble jwt parts
76 local raw_header = get_raw_part("header", jwt_obj)
77 local raw_payload = get_raw_part("payload", jwt_obj)
79 local message =raw_header .. "." .. raw_payload
80 -- cal signature
81 local hmac_func = hmac:new(secret_key, hash_alg)
82 local signature = _M:jwt_encode(hmac_func:final(message))
83 -- return full jwt string
84 return message .. "." .. signature
85 end
88 function _M.verify(self, secret, jwt_str, leeway)
89 local success, ret = pcall(parse, jwt_str)
90 local jwt_obj = ret
91 if not success then
92 return {verified=false, reason=ret["reason"] or "invalid jwt string"}
93 end
95 jwt_obj["verified"] = false
96 local success, ret = pcall(_M.sign, nil, secret, jwt_obj)
97 if not success then
98 -- syntax check
99 jwt_obj["reason"] = ret["reason"] or "internal error"
100 elseif jwt_str ~= ret then
101 -- signature check
102 jwt_obj["reason"] = "signature mismatch: " .. jwt_obj["signature"]
103 elseif leeway ~= nil then
104 local exp = jwt_obj["payload"]["exp"]
105 local nbf = jwt_obj["payload"]["nbf"]
106 local now = ngx.now()
108 if type(exp) == "number" and exp < (now - leeway) then
109 jwt_obj["reason"] = "jwt token expired at: " .. ngx.http_time(exp)
110 elseif type(nbf) == "number" and nbf > (now + leeway) then
111 jwt_obj["reason"] = "jwt token not valid until: " .. ngx.http_time(nbf)
112 end
113 end
115 if jwt_obj["reason"] == nil then
116 jwt_obj["verified"] = true
117 jwt_obj["reason"] = "everything is awesome~ :p"
118 end
119 return jwt_obj
120 end
122 return _M