nginx

Paddy 2015-06-30 Parent:68478c1bddde

1:ac9c19126939 Go to Latest

nginx/nginx-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
paddy@0 1 local jwt = require "resty.jwt"
paddy@0 2 local cjson = require "cjson"
paddy@0 3 local basexx = require "basexx"
paddy@0 4 local secret = os.getenv("JWT_SECRET")
paddy@0 5
paddy@0 6 assert(secret ~= nil, "Environment variable JWT_SECRET not set")
paddy@0 7
paddy@0 8 if os.getenv("JWT_SECRET_IS_BASE64_ENCODED") == 'true' then
paddy@0 9 -- convert from URL-safe Base64 to Base64
paddy@0 10 local r = #secret % 4
paddy@0 11 if r == 2 then
paddy@0 12 secret = secret .. "=="
paddy@0 13 elseif r == 3 then
paddy@0 14 secret = secret .. "="
paddy@0 15 end
paddy@0 16 secret = string.gsub(secret, "-", "+")
paddy@0 17 secret = string.gsub(secret, "_", "/")
paddy@0 18
paddy@0 19 -- convert from Base64 to UTF-8 string
paddy@0 20 secret = basexx.from_base64(secret)
paddy@0 21 end
paddy@0 22
paddy@0 23 local M = {}
paddy@0 24
paddy@0 25 function M.auth(claim_specs)
paddy@0 26 -- strip our headers to avoid spoofing
paddy@0 27 ngx.req.clear_header("User-Id")
paddy@0 28 ngx.req.clear_header("Scopes")
paddy@0 29 ngx.req.clear_header("JWT-Scope")
paddy@0 30 ngx.req.clear_header("JWT-Issuer")
paddy@0 31 ngx.req.clear_header("JWT-Subject")
paddy@0 32 ngx.req.clear_header("JWT-Expiration-Time")
paddy@0 33 ngx.req.clear_header("JWT-Not-Before")
paddy@0 34 ngx.req.clear_header("JWT-Issued-At")
paddy@0 35
paddy@0 36 -- require Authorization request header
paddy@0 37 local auth_header = ngx.var.http_Authorization
paddy@0 38
paddy@0 39 if auth_header == nil then
paddy@0 40 ngx.log(ngx.INFO, "No Authorization header")
paddy@0 41 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 42 return
paddy@0 43 else
paddy@0 44 ngx.log(ngx.INFO, "Authorization: " .. auth_header)
paddy@0 45
paddy@0 46 -- require Bearer token
paddy@0 47 local _, _, token = string.find(auth_header, "Bearer%s+(.+)")
paddy@0 48
paddy@0 49 if token == nil then
paddy@0 50 ngx.log(ngx.INFO, "Missing token")
paddy@0 51 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 52 return
paddy@0 53 else
paddy@0 54 ngx.log(ngx.INFO, "Token: " .. token)
paddy@0 55
paddy@0 56 -- require valid JWT
paddy@0 57 local jwt_obj = jwt:verify(secret, token, 60)
paddy@0 58 if jwt_obj.verified == false then
paddy@0 59 if string.find(jwt_obj.reason, "expired at") ~= nil then
paddy@0 60 ngx.status = ngx.HTTP_UNAUTHORIZED
paddy@1 61 ngx.say('{"errors": [{"error": "access_denied", "header": "authorization"}]}')
paddy@0 62 return ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 63 else
paddy@0 64 ngx.log(ngx.WARN, "Invalid token: ".. jwt_obj.reason)
paddy@0 65 -- ngx.exit(ngx.HTTP_UNAUTHORIZED)
paddy@0 66 end
paddy@0 67 return
paddy@0 68 else
paddy@0 69 ngx.log(ngx.INFO, "JWT: " .. cjson.encode(jwt_obj))
paddy@0 70
paddy@0 71 -- write the headers
paddy@0 72 ngx.req.set_header("User-Id", jwt_obj.payload.sub)
paddy@0 73 ngx.req.set_header("Scopes", jwt_obj.payload.scope)
paddy@0 74 ngx.req.set_header("JWT-Scope", jwt_obj.payload.scope)
paddy@0 75 ngx.req.set_header("JWT-Issuer", jwt_obj.payload.iss)
paddy@0 76 ngx.req.set_header("JWT-Subject", jwt_obj.payload.sub)
paddy@0 77 ngx.req.set_header("JWT-Expiration-Time", jwt_obj.payload.exp)
paddy@0 78 ngx.req.set_header("JWT-Not-Before", jwt_obj.payload.nbf)
paddy@0 79 ngx.req.set_header("JWT-Issued-At", jwt_obj.payload.iat)
paddy@0 80 return
paddy@0 81 end
paddy@0 82 end
paddy@0 83 end
paddy@0 84 end
paddy@0 85
paddy@0 86 return M