├── Dockerfile ├── LICENSE ├── README.md ├── config └── kong.yml ├── docker-compose.yml └── plugins ├── advokit-jwt ├── api.lua ├── daos.lua ├── handler.lua ├── hooks.lua ├── jwt_parser.lua ├── migrations │ ├── cassandra.lua │ └── postgres.lua └── schema.lua ├── helloworld ├── handler.lua └── schema.lua ├── pluginbundle-1.0-0.rockspec └── primeauth ├── handler.lua └── schema.lua /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mashape/kong:0.8.3 2 | 3 | MAINTAINER Aaron Signorelli aaron@superpixel.com 4 | 5 | RUN yum -y install nodejs npm 6 | RUN npm install -g nodemon 7 | 8 | CMD nodemon -L --watch /plugins --watch /etc/kong --ext lua,rockspec,yml --exec 'cd /plugins && luarocks make && kong reload' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SuperPixel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kong Plugin Development 2 | An environment for iterating quickly when developing Kong plugins. Lua, Kong + livereload. 3 | 4 | Uses Docker to bring up a Casandra, Kong and sets up Nodemon to watch for changes to kong.yml and any plugins. If changes are detected ```luarocks make``` installs the plugin(s) and Kong is reloaded. 5 | 6 | ## Start up 7 | Startup Docker on OSX: 8 | ``` 9 | docker-machine start default 10 | eval $(docker-machine env) 11 | ``` 12 | 13 | Bring up the Casandra and Kong containers 14 | ``` 15 | docker-compose up 16 | ``` 17 | 18 | Kong is not started right away (as during development an invalid plugin can bring it down and it's handy to see the logs + restart). 19 | To bring up Kong you need to connect to the container and execute the command manually: 20 | 21 | ``` 22 | docker exec -i -t $(docker ps --filter ancestor=superpixel/kong-plugin-development -q) kong start 23 | ``` 24 | If this is the first run it will take a minute to bootstrap. 25 | 26 | Once finished whenever the files in ```./plugins``` or ```./config``` are changed Kong will be reloaded and the updated plugins loaded in. -------------------------------------------------------------------------------- /config/kong.yml: -------------------------------------------------------------------------------- 1 | cassandra: 2 | contact_points: 3 | - "kong-database:9042" 4 | custom_plugins: 5 | - helloworld 6 | - primeauth 7 | - advokit-jwt 8 | nginx: | 9 | {{user}} 10 | worker_processes auto; 11 | error_log logs/error.log error; 12 | daemon off; 13 | worker_rlimit_nofile {{auto_worker_rlimit_nofile}}; 14 | env KONG_CONF; 15 | env PATH; 16 | events { 17 | worker_connections {{auto_worker_connections}}; 18 | multi_accept on; 19 | } 20 | http { 21 | resolver {{dns_resolver}} ipv6=off; 22 | charset UTF-8; 23 | access_log logs/access.log; 24 | access_log off; 25 | # Timeouts 26 | keepalive_timeout 60s; 27 | client_header_timeout 60s; 28 | client_body_timeout 60s; 29 | send_timeout 60s; 30 | # Proxy Settings 31 | proxy_buffer_size 128k; 32 | proxy_buffers 4 256k; 33 | proxy_busy_buffers_size 256k; 34 | proxy_ssl_server_name on; 35 | # IP Address 36 | real_ip_header X-Forwarded-For; 37 | set_real_ip_from 0.0.0.0/0; 38 | real_ip_recursive on; 39 | # Other Settings 40 | client_max_body_size 0; 41 | underscores_in_headers on; 42 | reset_timedout_connection on; 43 | tcp_nopush on; 44 | ################################################ 45 | # The following code is required to run Kong # 46 | # Please be careful if you'd like to change it # 47 | ################################################ 48 | # Lua Settings 49 | lua_package_path ';;'; 50 | lua_code_cache on; 51 | lua_max_running_timers 4096; 52 | lua_max_pending_timers 16384; 53 | lua_shared_dict reports_locks 100k; 54 | lua_shared_dict cluster_locks 100k; 55 | lua_shared_dict cluster_autojoin_locks 100k; 56 | lua_shared_dict cache {{memory_cache_size}}m; 57 | lua_shared_dict cassandra 1m; 58 | lua_shared_dict cassandra_prepared 5m; 59 | lua_socket_log_errors off; 60 | {{lua_ssl_trusted_certificate}} 61 | init_by_lua_block { 62 | kong = require "kong" 63 | kong.init() 64 | } 65 | init_worker_by_lua_block { 66 | kong.init_worker() 67 | } 68 | server { 69 | server_name _; 70 | listen {{proxy_listen}}; 71 | listen {{proxy_listen_ssl}} ssl; 72 | ssl_certificate_by_lua_block { 73 | kong.ssl_certificate() 74 | } 75 | ssl_certificate {{ssl_cert}}; 76 | ssl_certificate_key {{ssl_key}}; 77 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2;# omit SSLv3 because of POODLE (CVE-2014-3566) 78 | location / { 79 | default_type 'text/plain'; 80 | # These properties will be used later by proxy_pass 81 | set $upstream_host nil; 82 | set $upstream_url nil; 83 | # Authenticate the user and load the API info 84 | access_by_lua_block { 85 | kong.access() 86 | } 87 | # Proxy the request 88 | proxy_set_header X-Real-IP $remote_addr; 89 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 90 | proxy_set_header X-Forwarded-Proto $scheme; 91 | proxy_set_header Host $upstream_host; 92 | proxy_pass $upstream_url; 93 | proxy_pass_header Server; 94 | # Add additional response headers 95 | header_filter_by_lua_block { 96 | kong.header_filter() 97 | } 98 | # Change the response body 99 | body_filter_by_lua_block { 100 | kong.body_filter() 101 | } 102 | # Log the request 103 | log_by_lua_block { 104 | kong.log() 105 | } 106 | } 107 | location /robots.txt { 108 | return 200 'User-agent: *\nDisallow: /'; 109 | } 110 | error_page 500 502 503 504 /50x; 111 | location = /50x { 112 | internal; 113 | content_by_lua_block { 114 | require("kong.core.error_handlers")(ngx) 115 | } 116 | } 117 | } 118 | server { 119 | listen {{admin_api_listen}}; 120 | client_max_body_size 10m; 121 | client_body_buffer_size 10m; 122 | location / { 123 | default_type application/json; 124 | content_by_lua_block { 125 | ngx.header["Access-Control-Allow-Origin"] = "*" 126 | if ngx.req.get_method() == "OPTIONS" then 127 | ngx.header["Access-Control-Allow-Methods"] = "GET,HEAD,PUT,PATCH,POST,DELETE" 128 | ngx.header["Access-Control-Allow-Headers"] = "Content-Type" 129 | ngx.exit(204) 130 | end 131 | local lapis = require "lapis" 132 | lapis.serve "kong.api.app" 133 | } 134 | } 135 | location /nginx_status { 136 | internal; 137 | access_log off; 138 | stub_status; 139 | } 140 | location /robots.txt { 141 | return 200 'User-agent: *\nDisallow: /'; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | cassandra: 5 | image: cassandra:2.2.6 6 | container_name: kong-database 7 | restart: always 8 | ports: 9 | - 9042:9042 10 | kong: 11 | image: superpixel/kong-plugin-development 12 | container_name: kong 13 | restart: always 14 | ports: 15 | - 8000:8000 16 | - 8001:8001 17 | - 8443:8443 18 | - 7946:7946 19 | - 7946:7946/udp 20 | volumes: 21 | - ./config:/etc/kong/ 22 | - ./plugins:/plugins 23 | links: 24 | - cassandra:kong-database 25 | security_opt: 26 | - label:seccomp:unconfined 27 | depends_on: 28 | - cassandra -------------------------------------------------------------------------------- /plugins/advokit-jwt/api.lua: -------------------------------------------------------------------------------- 1 | local crud = require "kong.api.crud_helpers" 2 | 3 | return { 4 | ["/consumers/:username_or_id/jwt/"] = { 5 | before = function(self, dao_factory, helpers) 6 | crud.find_consumer_by_username_or_id(self, dao_factory, helpers) 7 | self.params.consumer_id = self.consumer.id 8 | end, 9 | 10 | GET = function(self, dao_factory) 11 | crud.paginated_set(self, dao_factory.jwt_secrets) 12 | end, 13 | 14 | PUT = function(self, dao_factory) 15 | crud.put(self.params, dao_factory.jwt_secrets) 16 | end, 17 | 18 | POST = function(self, dao_factory) 19 | crud.post(self.params, dao_factory.jwt_secrets) 20 | end 21 | }, 22 | 23 | ["/consumers/:username_or_id/jwt/:id"] = { 24 | before = function(self, dao_factory, helpers) 25 | crud.find_consumer_by_username_or_id(self, dao_factory, helpers) 26 | self.params.consumer_id = self.consumer.id 27 | 28 | local err 29 | self.jwt_secret, err = dao_factory.jwt_secrets:find(self.params) 30 | if err then 31 | return helpers.yield_error(err) 32 | elseif self.jwt_secret == nil then 33 | return helpers.responses.send_HTTP_NOT_FOUND() 34 | end 35 | end, 36 | 37 | GET = function(self, dao_factory, helpers) 38 | return helpers.responses.send_HTTP_OK(self.jwt_secret) 39 | end, 40 | 41 | PATCH = function(self, dao_factory) 42 | crud.patch(self.params, dao_factory.jwt_secrets, self.jwt_secret) 43 | end, 44 | 45 | DELETE = function(self, dao_factory) 46 | crud.delete(self.jwt_secret, dao_factory.jwt_secrets) 47 | end 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plugins/advokit-jwt/daos.lua: -------------------------------------------------------------------------------- 1 | local utils = require "kong.tools.utils" 2 | 3 | local SCHEMA = { 4 | primary_key = {"id"}, 5 | table = "jwt_secrets", 6 | fields = { 7 | id = {type = "id", dao_insert_value = true}, 8 | created_at = {type = "timestamp", immutable = true, dao_insert_value = true}, 9 | consumer_id = {type = "id", required = true, foreign = "consumers:id"}, 10 | key = {type = "string", unique = true, default = utils.random_string}, 11 | secret = {type = "string", unique = true, default = utils.random_string}, 12 | rsa_public_key = {type = "string"}, 13 | algorithm = {type = "string", enum = {"HS256", "RS256"}, default = 'HS256'} 14 | }, 15 | marshall_event = function(self, t) 16 | return {id = t.id, consumer_id = t.consumer_id, key = t.key} 17 | end 18 | } 19 | 20 | return {jwt_secrets = SCHEMA} 21 | -------------------------------------------------------------------------------- /plugins/advokit-jwt/handler.lua: -------------------------------------------------------------------------------- 1 | local singletons = require "kong.singletons" 2 | local BasePlugin = require "kong.plugins.base_plugin" 3 | local cache = require "kong.tools.database_cache" 4 | local responses = require "kong.tools.responses" 5 | local constants = require "kong.constants" 6 | local jwt_decoder = require "kong.plugins.advokit-jwt.jwt_parser" 7 | local inspect = require "inspect" 8 | local string_format = string.format 9 | local ngx_re_gmatch = ngx.re.gmatch 10 | 11 | local JwtHandler = BasePlugin:extend() 12 | 13 | JwtHandler.PRIORITY = 1000 14 | 15 | --- Retrieve a JWT in a request. 16 | -- Checks for the JWT in URI parameters, then in the `Authorization` header. 17 | -- @param request ngx request object 18 | -- @param conf Plugin configuration 19 | -- @return token JWT token contained in request or nil 20 | -- @return err 21 | local function retrieve_token(request, conf) 22 | local uri_parameters = request.get_uri_args() 23 | 24 | for _, v in ipairs(conf.uri_param_names) do 25 | if uri_parameters[v] then 26 | return uri_parameters[v] 27 | end 28 | end 29 | 30 | local authorization_header = request.get_headers()["authorization"] 31 | if authorization_header then 32 | local iterator, iter_err = ngx_re_gmatch(authorization_header, "\\s*[Bb]earer\\s+(.+)") 33 | if not iterator then 34 | return nil, iter_err 35 | end 36 | 37 | local m, err = iterator() 38 | if err then 39 | return nil, err 40 | end 41 | 42 | if m and #m > 0 then 43 | return m[1] 44 | end 45 | end 46 | end 47 | 48 | function JwtHandler:new() 49 | JwtHandler.super.new(self, "jwt") 50 | end 51 | 52 | function JwtHandler:access(conf) 53 | JwtHandler.super.access(self) 54 | 55 | local token, err = retrieve_token(ngx.req, conf) 56 | if err then 57 | return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) 58 | end 59 | 60 | if not token then 61 | return responses.send_HTTP_UNAUTHORIZED() 62 | end 63 | 64 | -- Decode token to find out who the consumer is 65 | local jwt, err = jwt_decoder:new(token) 66 | 67 | ngx.log(ngx.ERR, err) 68 | 69 | if err then 70 | return responses.send_HTTP_INTERNAL_SERVER_ERROR() 71 | end 72 | 73 | local claims = jwt.claims 74 | 75 | local jwt_secret_key = claims[conf.key_claim_name] 76 | if not jwt_secret_key then 77 | return responses.send_HTTP_UNAUTHORIZED("No mandatory '"..conf.key_claim_name.."' in claims") 78 | end 79 | 80 | -- Retrieve the secret 81 | local jwt_secret = cache.get_or_set(cache.jwtauth_credential_key(jwt_secret_key), function() 82 | local rows, err = singletons.dao.jwt_secrets:find_all {key = jwt_secret_key} 83 | if err then 84 | return responses.send_HTTP_INTERNAL_SERVER_ERROR() 85 | elseif #rows > 0 then 86 | return rows[1] 87 | end 88 | end) 89 | 90 | if not jwt_secret then 91 | return responses.send_HTTP_FORBIDDEN("No credentials found for given '"..conf.key_claim_name.."'") 92 | end 93 | 94 | local algorithm = jwt_secret.algorithm or "HS256" 95 | 96 | -- Verify "alg" 97 | if jwt.header.alg ~= algorithm then 98 | return responses.send_HTTP_FORBIDDEN("Invalid algorithm") 99 | end 100 | 101 | local jwt_secret_value = algorithm == "HS256" and jwt_secret.secret or jwt_secret.rsa_public_key 102 | if conf.secret_is_base64 then 103 | jwt_secret_value = jwt:b64_decode(jwt_secret_value) 104 | end 105 | 106 | -- Now verify the JWT signature 107 | if not jwt:verify_signature(jwt_secret_value) then 108 | return responses.send_HTTP_FORBIDDEN("Invalid signature") 109 | end 110 | 111 | -- Verify the JWT registered claims 112 | local ok_claims, errors = jwt:verify_registered_claims(conf.claims_to_verify) 113 | if not ok_claims then 114 | return responses.send_HTTP_FORBIDDEN(errors) 115 | end 116 | 117 | -- Retrieve the consumer 118 | local consumer = cache.get_or_set(cache.consumer_key(jwt_secret_key), function() 119 | local consumer, err = singletons.dao.consumers:find {id = jwt_secret.consumer_id} 120 | if err then 121 | return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) 122 | end 123 | return consumer 124 | end) 125 | 126 | -- However this should not happen 127 | if not consumer then 128 | return responses.send_HTTP_FORBIDDEN(string_format("Could not find consumer for '%s=%s'", conf.key_claim_name, jwt_secret_key)) 129 | end 130 | 131 | --ngx.req.set_header(constants.HEADERS.CONSUMER_ID, consumer.id) 132 | --ngx.req.set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id) 133 | --ngx.req.set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username) 134 | ngx.ctx.authenticated_credential = jwt_secret 135 | 136 | ngx.log(ngx.STDERR, inspect(claims)) 137 | 138 | ngx.req.set_header("X-Advokit-Account", claims['acc']) 139 | ngx.req.set_header("X-Advokit-Admin", tostring(claims['adm'])) 140 | ngx.req.set_header("X-Advokit-SuperAdmin", tostring(claims['adm'] and claims['acc'] == "superpixel")) 141 | ngx.req.set_header("X-Advokit-User", claims['sub']) 142 | ngx.req.set_header("X-Advokit-Test", tostring(claims['test'])) 143 | 144 | end 145 | 146 | return JwtHandler 147 | -------------------------------------------------------------------------------- /plugins/advokit-jwt/hooks.lua: -------------------------------------------------------------------------------- 1 | local events = require "kong.core.events" 2 | local cache = require "kong.tools.database_cache" 3 | 4 | local function invalidate(message_t) 5 | if message_t.collection == "jwt_secrets" then 6 | cache.delete(cache.jwtauth_credential_key(message_t.old_entity and message_t.old_entity.key or message_t.entity.key)) 7 | end 8 | end 9 | 10 | return { 11 | [events.TYPES.ENTITY_UPDATED] = function(message_t) 12 | invalidate(message_t) 13 | end, 14 | [events.TYPES.ENTITY_DELETED] = function(message_t) 15 | invalidate(message_t) 16 | end 17 | } -------------------------------------------------------------------------------- /plugins/advokit-jwt/jwt_parser.lua: -------------------------------------------------------------------------------- 1 | -- JWT verification module 2 | -- Adapted version of x25/luajwt for Kong. It provides various improvements and 3 | -- an OOP architecture allowing the JWT to be parsed and verified separatly, 4 | -- avoiding multiple parsings. 5 | -- 6 | -- @see https://github.com/x25/luajwt 7 | 8 | local json = require "cjson" 9 | local base64 = require "base64" 10 | local crypto = require "crypto" 11 | local utils = require "kong.tools.utils" 12 | 13 | local error = error 14 | local type = type 15 | local pcall = pcall 16 | local ngx_time = ngx.time 17 | local string_rep = string.rep 18 | local setmetatable = setmetatable 19 | 20 | --- Supported algorithms for signing tokens. 21 | local alg_sign = { 22 | ["HS256"] = function(data, key) return crypto.hmac.digest("sha256", data, key, true) end, 23 | ["HS384"] = function(data, key) return crypto.hmac.digest("sha384", data, key, true) end, 24 | ["HS512"] = function(data, key) return crypto.hmac.digest("sha512", data, key, true) end, 25 | ["RS256"] = function(data, key) return crypto.sign('sha256', data, crypto.pkey.from_pem(key, true)) end 26 | } 27 | 28 | --- Supported algorithms for verifying tokens. 29 | local alg_verify = { 30 | ["HS256"] = function(data, signature, key) return signature == alg_sign["HS256"](data, key) end, 31 | ["HS384"] = function(data, signature, key) return signature == alg_sign["HS384"](data, key) end, 32 | ["HS512"] = function(data, signature, key) return signature == alg_sign["HS512"](data, key) end, 33 | ["RS256"] = function(data, signature, key) 34 | return crypto.verify('sha256', data, signature, crypto.pkey.from_pem(key)) 35 | end 36 | } 37 | 38 | --- base 64 encoding 39 | -- @param input String to base64 encode 40 | -- @return Base64 encoded string 41 | local function b64_encode(input) 42 | local result = base64.encode(input) 43 | result = result:gsub("+", "-"):gsub("/", "_"):gsub("=", "") 44 | return result 45 | end 46 | 47 | --- base 64 decode 48 | -- @param input String to base64 decode 49 | -- @return Base64 decoded string 50 | local function b64_decode(input) 51 | local reminder = #input % 4 52 | 53 | if reminder > 0 then 54 | local padlen = 4 - reminder 55 | input = input..string_rep('=', padlen) 56 | end 57 | 58 | input = input:gsub("-", "+"):gsub("_", "/") 59 | return base64.decode(input) 60 | end 61 | 62 | --- Tokenize a string by delimiter 63 | -- Used to separate the header, claims and signature part of a JWT 64 | -- @param str String to tokenize 65 | -- @param div Delimiter 66 | -- @param len Number of parts to retrieve 67 | -- @return A table of strings 68 | local function tokenize(str, div, len) 69 | local result, pos = {}, 0 70 | 71 | for st, sp in function() return str:find(div, pos, true) end do 72 | result[#result + 1] = str:sub(pos, st-1) 73 | pos = sp + 1 74 | len = len - 1 75 | if len <= 1 then 76 | break 77 | end 78 | end 79 | 80 | result[#result + 1] = str:sub(pos) 81 | return result 82 | end 83 | 84 | --- Parse a JWT 85 | -- Parse a JWT and validate header values. 86 | -- @param token JWT to parse 87 | -- @return A table containing base64 and decoded headers, claims and signature 88 | local function decode_token(token) 89 | -- Get b64 parts 90 | local header_64, claims_64, signature_64 = unpack(tokenize(token, ".", 3)) 91 | 92 | -- Decode JSON 93 | local ok, header, claims, signature = pcall(function() 94 | return json.decode(b64_decode(header_64)), 95 | json.decode(b64_decode(claims_64)), 96 | b64_decode(signature_64) 97 | end) 98 | if not ok then 99 | return nil, "Invalid JSON" 100 | end 101 | 102 | if header.typ and header.typ:upper() ~= "JWT" then 103 | return nil, "Invalid typ" 104 | end 105 | 106 | if not header.alg or type(header.alg) ~= "string" or not alg_verify[header.alg] then 107 | return nil, "Invalid alg" 108 | end 109 | 110 | return { 111 | token = token, 112 | header_64 = header_64, 113 | claims_64 = claims_64, 114 | signature_64 = signature_64, 115 | header = header, 116 | claims = claims, 117 | signature = signature 118 | } 119 | end 120 | 121 | -- For test purposes 122 | local function encode_token(data, key, alg, header) 123 | if type(data) ~= "table" then error("Argument #1 must be table", 2) end 124 | if type(key) ~= "string" then error("Argument #2 must be string", 2) end 125 | if header and type(header) ~= "table" then error("Argument #4 must be a table", 2) end 126 | 127 | alg = alg or "HS256" 128 | 129 | if not alg_sign[alg] then 130 | error("Algorithm not supported", 2) 131 | end 132 | 133 | local header = header or {typ = "JWT", alg = alg} 134 | local segments = { 135 | b64_encode(json.encode(header)), 136 | b64_encode(json.encode(data)) 137 | } 138 | 139 | local signing_input = table.concat(segments, ".") 140 | local signature = alg_sign[alg](signing_input, key) 141 | segments[#segments+1] = b64_encode(signature) 142 | return table.concat(segments, ".") 143 | end 144 | 145 | --[[ 146 | 147 | JWT public interface 148 | 149 | ]]-- 150 | 151 | local _M = {} 152 | _M.__index = _M 153 | 154 | --- Instanciate a JWT parser 155 | -- Parse a JWT and instanciate a JWT parser for further operations 156 | -- Return errors instead of an instance if any encountered 157 | -- @param token JWT to parse 158 | -- @return JWT parser 159 | -- @return error if any 160 | function _M:new(token) 161 | if type(token) ~= "string" then error("JWT must be a string", 2) end 162 | 163 | local token, err = decode_token(token) 164 | if err then 165 | return nil, err 166 | end 167 | 168 | return setmetatable(token, _M) 169 | end 170 | 171 | --- Verify a JWT signature 172 | -- Verify the current JWT signature against a given key 173 | -- @param key Key against which to verify the signature 174 | -- @return A boolean indicating if the signature if verified or not 175 | function _M:verify_signature(key) 176 | return alg_verify[self.header.alg](self.header_64.."."..self.claims_64, self.signature, key) 177 | end 178 | 179 | function _M:b64_decode(input) 180 | return b64_decode(input) 181 | end 182 | 183 | --- Registered claims according to RFC 7519 Section 4.1 184 | local registered_claims = { 185 | ["nbf"] = { 186 | type = "number", 187 | check = function(nbf) 188 | if nbf > ngx_time() then 189 | return "token not valid yet" 190 | end 191 | end 192 | }, 193 | ["exp"] = { 194 | type = "number", 195 | check = function(exp) 196 | if exp <= ngx_time() then 197 | return "token expired" 198 | end 199 | end 200 | } 201 | } 202 | 203 | --- Verify registered claims (according to RFC 7519 Section 4.1) 204 | -- Claims are verified by type and a check. 205 | -- @param claims_to_verify A list of claims to verify. 206 | -- @return A boolean indicating true if no errors zere found 207 | -- @return A list of errors 208 | function _M:verify_registered_claims(claims_to_verify) 209 | if not claims_to_verify then claims_to_verify = {} end 210 | local errors = nil 211 | local claim, claim_rules 212 | 213 | for _, claim_name in pairs(claims_to_verify) do 214 | claim = self.claims[claim_name] 215 | claim_rules = registered_claims[claim_name] 216 | if type(claim) ~= claim_rules.type then 217 | errors = utils.add_error(errors, claim_name, "must be a "..claim_rules.type) 218 | else 219 | local check_err = claim_rules.check(claim) 220 | if check_err then 221 | errors = utils.add_error(errors, claim_name, check_err) 222 | end 223 | end 224 | end 225 | 226 | return errors == nil, errors 227 | end 228 | 229 | _M.encode = encode_token 230 | 231 | return _M 232 | -------------------------------------------------------------------------------- /plugins/advokit-jwt/migrations/cassandra.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | name = "2015-06-09-jwt-auth", 4 | up = [[ 5 | CREATE TABLE IF NOT EXISTS jwt_secrets( 6 | id uuid, 7 | consumer_id uuid, 8 | key text, 9 | secret text, 10 | created_at timestamp, 11 | PRIMARY KEY (id) 12 | ); 13 | 14 | CREATE INDEX IF NOT EXISTS ON jwt_secrets(key); 15 | CREATE INDEX IF NOT EXISTS ON jwt_secrets(secret); 16 | CREATE INDEX IF NOT EXISTS ON jwt_secrets(consumer_id); 17 | ]], 18 | down = [[ 19 | DROP TABLE jwt_secrets; 20 | ]] 21 | }, 22 | { 23 | name = "2016-03-07-jwt-alg", 24 | up = [[ 25 | ALTER TABLE jwt_secrets ADD algorithm text; 26 | ALTER TABLE jwt_secrets ADD rsa_public_key text; 27 | ]], 28 | down = [[ 29 | ALTER TABLE jwt_secrets DROP algorithm; 30 | ALTER TABLE jwt_secrets DROP rsa_public_key; 31 | ]] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /plugins/advokit-jwt/migrations/postgres.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | name = "2015-06-09-jwt-auth", 4 | up = [[ 5 | CREATE TABLE IF NOT EXISTS jwt_secrets( 6 | id uuid, 7 | consumer_id uuid REFERENCES consumers (id) ON DELETE CASCADE, 8 | key text UNIQUE, 9 | secret text UNIQUE, 10 | created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'), 11 | PRIMARY KEY (id) 12 | ); 13 | 14 | DO $$ 15 | BEGIN 16 | IF (SELECT to_regclass('jwt_secrets_key')) IS NULL THEN 17 | CREATE INDEX jwt_secrets_key ON jwt_secrets(key); 18 | END IF; 19 | IF (SELECT to_regclass('jwt_secrets_secret')) IS NULL THEN 20 | CREATE INDEX jwt_secrets_secret ON jwt_secrets(secret); 21 | END IF; 22 | IF (SELECT to_regclass('jwt_secrets_consumer_id')) IS NULL THEN 23 | CREATE INDEX jwt_secrets_consumer_id ON jwt_secrets(consumer_id); 24 | END IF; 25 | END$$; 26 | ]], 27 | down = [[ 28 | DROP TABLE jwt_secrets; 29 | ]] 30 | }, 31 | { 32 | name = "2016-03-07-jwt-alg", 33 | up = [[ 34 | ALTER TABLE jwt_secrets ADD COLUMN algorithm text; 35 | ALTER TABLE jwt_secrets ADD COLUMN rsa_public_key text; 36 | ]], 37 | down = [[ 38 | ALTER TABLE jwt_secrets DROP COLUMN algorithm; 39 | ALTER TABLE jwt_secrets DROP COLUMN rsa_public_key; 40 | ]] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugins/advokit-jwt/schema.lua: -------------------------------------------------------------------------------- 1 | return { 2 | no_consumer = true, 3 | fields = { 4 | uri_param_names = {type = "array", default = {"jwt"}}, 5 | key_claim_name = {type = "string", default = "iss"}, 6 | secret_is_base64 = {type = "boolean", default = false}, 7 | claims_to_verify = {type = "array", enum = {"exp", "nbf"}} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /plugins/helloworld/handler.lua: -------------------------------------------------------------------------------- 1 | -- Extending the Base Plugin handler is optional, as there is no real 2 | -- concept of interface in Lua, but the Base Plugin handler's methods 3 | -- can be called from your child implementation and will print logs 4 | -- in your `error.log` file (where all logs are printed). 5 | local BasePlugin = require "kong.plugins.base_plugin" 6 | local CustomHandler = BasePlugin:extend() 7 | 8 | -- Your plugin handler's constructor. If you are extending the 9 | -- Base Plugin handler, it's only role is to instanciate itself 10 | -- with a name. The name is your plugin name as it will be printed in the logs. 11 | function CustomHandler:new() 12 | CustomHandler.super.new(self, "helloworld") 13 | 14 | 15 | end 16 | 17 | -- Executed upon every Nginx worker process's startup. 18 | function CustomHandler:init_worker(config) 19 | -- Eventually, execute the parent implementation 20 | -- (will log that your plugin is entering this context) 21 | CustomHandler.super.init_worker(self) 22 | 23 | -- Implement any custom logic here 24 | 25 | end 26 | 27 | -- Executed during the SSL certificate serving phase of the SSL handshake. 28 | function CustomHandler:certificate(config) 29 | CustomHandler.super.certificate(self) 30 | 31 | -- Implement any custom logic here 32 | end 33 | 34 | -- Executed for every request upon it's reception from a client and before it is being proxied to the upstream service. 35 | function CustomHandler:access(config) 36 | CustomHandler.super.access(self) 37 | 38 | -- Implement any custom logic here 39 | ngx.header["X-My-Header"] = 'Hello World Again!'; 40 | 41 | end 42 | 43 | -- Executed when all response headers bytes have been received from the upstream service. 44 | function CustomHandler:header_filter(config) 45 | CustomHandler.super.header_filter(self) 46 | 47 | -- Implement any custom logic here 48 | end 49 | 50 | -- Executed for each chunk of the response body received from the upstream service. Since the response is streamed back to the client, it can exceed the buffer size and be streamed chunk by chunk. hence this method can be called multiple times if the response is large. See the lua-nginx-module documentation for more details. 51 | function CustomHandler:body_filter(config) 52 | CustomHandler.super.body_filter(self) 53 | 54 | -- Implement any custom logic here 55 | 56 | end 57 | 58 | -- Executed when the last response byte has been sent to the client. 59 | function CustomHandler:log(config) 60 | CustomHandler.super.log(self) 61 | 62 | -- Implement any custom logic here 63 | 64 | end 65 | 66 | -- This module needs to return the created table, so that Kong 67 | -- can execute those functions. 68 | return CustomHandler 69 | -------------------------------------------------------------------------------- /plugins/helloworld/schema.lua: -------------------------------------------------------------------------------- 1 | return { 2 | no_consumer = true, 3 | fields = { 4 | } 5 | } -------------------------------------------------------------------------------- /plugins/pluginbundle-1.0-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "pluginbundle" 2 | version = "1.0-0" 3 | source = { 4 | url = "SuperPixel.com" 5 | } 6 | description = { 7 | summary = "SuperPixel kong plugin development env.", 8 | license = "None" 9 | } 10 | dependencies = { 11 | "lua ~> 5.1", "inspect ~> 3.1.0" 12 | -- If you depend on other rocks, add them here 13 | 14 | 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | ["kong.plugins.helloworld.handler"] = "helloworld/handler.lua", 20 | ["kong.plugins.helloworld.schema"] = "helloworld/schema.lua", 21 | ["kong.plugins.primeauth.handler"] = "primeauth/handler.lua", 22 | ["kong.plugins.primeauth.schema"] = "primeauth/schema.lua", 23 | ["kong.plugins.advokit-jwt.handler"] = "advokit-jwt/handler.lua", 24 | ["kong.plugins.advokit-jwt.schema"] = "advokit-jwt/schema.lua", 25 | ["kong.plugins.advokit-jwt.jwt_parser"] = "advokit-jwt/jwt_parser.lua" 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /plugins/primeauth/handler.lua: -------------------------------------------------------------------------------- 1 | -- Extending the Base Plugin handler is optional, as there is no real 2 | -- concept of interface in Lua, but the Base Plugin handler's methods 3 | -- can be called from your child implementation and will print logs 4 | -- in your `error.log` file (where all logs are printed). 5 | local BasePlugin = require "kong.plugins.base_plugin" 6 | local PrimeAuthHandler = BasePlugin:extend() 7 | 8 | -- Your plugin handler's constructor. If you are extending the 9 | -- Base Plugin handler, it's only role is to instanciate itself 10 | -- with a name. The name is your plugin name as it will be printed in the logs. 11 | function PrimeAuthHandler:new() 12 | PrimeAuthHandler.super.new(self, "primeauth") 13 | 14 | 15 | end 16 | 17 | -- Executed upon every Nginx worker process's startup. 18 | function PrimeAuthHandler:init_worker(config) 19 | -- Eventually, execute the parent implementation 20 | -- (will log that your plugin is entering this context) 21 | PrimeAuthHandler.super.init_worker(self) 22 | 23 | -- Implement any custom logic here 24 | 25 | end 26 | 27 | -- Executed during the SSL certificate serving phase of the SSL handshake. 28 | function PrimeAuthHandler:certificate(config) 29 | PrimeAuthHandler.super.certificate(self) 30 | 31 | -- Implement any custom logic here 32 | end 33 | 34 | -- Executed for every request upon it's reception from a client and before it is being proxied to the upstream service. 35 | function PrimeAuthHandler:access(config) 36 | PrimeAuthHandler.super.access(self) 37 | 38 | -- Implement any custom logic here 39 | ngx.header["X-Prime-Auth"] = 'Prime auth here again!'; 40 | 41 | end 42 | 43 | -- Executed when all response headers bytes have been received from the upstream service. 44 | function PrimeAuthHandler:header_filter(config) 45 | PrimeAuthHandler.super.header_filter(self) 46 | 47 | -- Implement any custom logic here 48 | end 49 | 50 | -- Executed for each chunk of the response body received from the upstream service. Since the response is streamed back to the client, it can exceed the buffer size and be streamed chunk by chunk. hence this method can be called multiple times if the response is large. See the lua-nginx-module documentation for more details. 51 | function PrimeAuthHandler:body_filter(config) 52 | PrimeAuthHandler.super.body_filter(self) 53 | 54 | -- Implement any custom logic here 55 | 56 | end 57 | 58 | -- Executed when the last response byte has been sent to the client. 59 | function PrimeAuthHandler:log(config) 60 | PrimeAuthHandler.super.log(self) 61 | 62 | -- Implement any custom logic here 63 | 64 | end 65 | 66 | -- This module needs to return the created table, so that Kong 67 | -- can execute those functions. 68 | return PrimeAuthHandler 69 | -------------------------------------------------------------------------------- /plugins/primeauth/schema.lua: -------------------------------------------------------------------------------- 1 | return { 2 | no_consumer = true, 3 | fields = { 4 | } 5 | } --------------------------------------------------------------------------------