-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The plugin takes the given key from a configured header name of the incoming traffic, then accesses the configured authentication service for a (JWT) token. The token is cached as the secified TTL and inserted to the request header to access the target service. TODOs: - Add more test coverage for both unit and integration. - Understand where to put the doc for this plugin. - Need more detailed granular control of accessing authN/authZ server. For example the timeout, return code handling. - Need more discussion on the high level design of this plugin. Does the actual use case exist? And how to make it more reliable and flexible.
- Loading branch information
Showing
6 changed files
with
272 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
local http = require "kong.plugins.key-token.auth_service" | ||
local error = error | ||
|
||
|
||
local _M = {} | ||
|
||
|
||
function _M:execute(plugin_conf) | ||
-- Here is the main logic | ||
-- 1. check if the client has the auth_key in the header. | ||
-- 1.1 if user has no auth_key, return 412 | ||
-- 1.2 if user has auth_key use the key to auth authentication server | ||
-- 2.1 if the JWT token is valid then go to 4 | ||
-- 2.2 request the authentication server with the auth_key | ||
-- 2.2.1 authentication server reply 200 with the JWT token is the auth_key is valid, | ||
-- 2.2.2 authentication server reply 403 reject the request if the auth_key is invalid | ||
-- 3 cache the JWT token | ||
-- 4.1 if none 200, return immediately, no access to the upstream | ||
-- 4.2 if 200, access the upstream with the Authentication header | ||
local auth_key = kong.request.get_header(plugin_conf.request_key_name) | ||
local auth_server = plugin_conf.auth_server | ||
local ttl = plugin_conf.ttl | ||
local cached_token, err = self:get_cached_token(auth_key, auth_server, ttl) | ||
if err then | ||
kong.log.err("Failed to acquire token associates with the key. Error: " .. err) | ||
return | ||
end | ||
|
||
if cached_token then | ||
self:inject_token_to_service_header(cached_token) | ||
return | ||
end | ||
|
||
end | ||
|
||
|
||
function _M:get_cached_token(auth_key, auth_server, ttl) | ||
-- return the cached token if it is not out of life. or else return nil | ||
local cache = kong.cache | ||
local credential_cache_key = kong.db.keyauth_credentials:cache_key(auth_key) | ||
local credential, err = cache:get(credential_cache_key, { resurrent_ttl = ttl }, load_auth_token, auth_key, auth_server) | ||
if err then | ||
return nil, err | ||
else | ||
return credential, nil | ||
end | ||
end | ||
|
||
|
||
function _M:inject_token_to_service_header(token) | ||
kong.service.request.set_header("Authorizaion", "Bearer " .. token) | ||
end | ||
|
||
|
||
function _M:save_token_to_cache(auth_key, token) | ||
end | ||
|
||
function load_auth_token(auth_key, auth_server) | ||
-- Maybe to set TIMEOUT variable to control the timeout? | ||
local auth_headers = { auth_key = auth_key } | ||
local body, status_code, headers, status_text = http.request { | ||
url = auth_server, | ||
headers = auth_headers, | ||
} | ||
|
||
kong.log.debug("return code from auth service " .. status_code) | ||
if status_code == 200 then | ||
return body, nil | ||
else | ||
return nil, error("Auth server return non 200 code " .. status_code) | ||
end | ||
end | ||
|
||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
local http = require "socket.http" | ||
|
||
|
||
local default_payload = "123456" | ||
local _M = {} | ||
|
||
function _M.request(req) | ||
kong.log.inspect(req) | ||
if string.find(req.url, "localhost") then | ||
-- mock the http request | ||
return default_payload, 200, req.headers, "200 OK" | ||
else | ||
return http.request(req) | ||
end | ||
end | ||
|
||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
local access = require "kong.plugins.key-token.access" | ||
|
||
|
||
local plugin = { | ||
PRIORITY = 1255, -- Execute before key-auth | ||
VERSION = "0.1.0", -- The initial version | ||
} | ||
|
||
|
||
-- runs in the 'access_by_lua_block' | ||
function plugin:access(plugin_conf) | ||
access:execute(plugin_conf) | ||
end --]] | ||
|
||
|
||
-- return our plugin object | ||
return plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
local typedefs = require "kong.db.schema.typedefs" | ||
|
||
|
||
local PLUGIN_NAME = "key-token" | ||
|
||
|
||
local schema = { | ||
name = PLUGIN_NAME, | ||
fields = { | ||
-- the 'fields' array is the top-level entry with fields defined by Kong | ||
{ consumer = typedefs.no_consumer }, -- this plugin cannot be configured on a consumer (typical for auth plugins) | ||
{ protocols = typedefs.protocols_http }, | ||
{ config = { | ||
-- The 'config' record is the custom part of the plugin schema | ||
type = "record", | ||
fields = { | ||
-- a standard defined field (typedef), with some customizations | ||
{ request_key_name = typedefs.header_name { | ||
description = "The header name that is used to send to backend authentication service.", | ||
type = "string", | ||
required = true, | ||
default = "auth_key" }, }, | ||
{ auth_server = typedefs.url { | ||
description = "The authenticaiton/authorization service URL. please note that 'localhost' is reserved for integration test.", | ||
required = true, | ||
default = "http://auth_server.com" }, }, | ||
{ ttl = { | ||
description = "TTL for cached token from auth server.", | ||
type = "integer", | ||
default = 600, | ||
required = true, | ||
gt = 0, }, }, | ||
}, | ||
entity_checks = { }, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
return schema |
86 changes: 86 additions & 0 deletions
86
spec/02-integration/23-key-token_plugins/01-integration_spec.lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
local helpers = require "spec.helpers" | ||
|
||
|
||
local PLUGIN_NAME = "key-token" | ||
|
||
|
||
for _, strategy in helpers.all_strategies() do if strategy ~= "cassandra" then | ||
describe(PLUGIN_NAME .. ": (access) [#" .. strategy .. "]", function() | ||
local client | ||
|
||
lazy_setup(function() | ||
|
||
local bp = helpers.get_db_utils(strategy == "off" and "postgres" or strategy, nil, { PLUGIN_NAME }) | ||
|
||
-- Inject a test route. No need to create a service, there is a default | ||
-- service which will echo the request. | ||
local route_auth_server = bp.routes:insert({ | ||
hosts = { "auth_server.com" }, | ||
}) | ||
|
||
local route_upstream = bp.routes:insert({ | ||
hosts = { "resource1.com" }, | ||
}) | ||
-- add the plugin to test to the route we created | ||
bp.plugins:insert { | ||
name = PLUGIN_NAME, | ||
route = { id = route_auth_server.id }, | ||
config = {}, | ||
} | ||
|
||
bp.plugins:insert { | ||
name = PLUGIN_NAME, | ||
route = { id = route_upstream.id }, | ||
config = {auth_server = "http://localhost:9001"}, | ||
} | ||
-- start kong | ||
assert(helpers.start_kong({ | ||
-- set the strategy | ||
database = strategy, | ||
-- use the custom test template to create a local mock server | ||
nginx_conf = "spec/fixtures/custom_nginx.template", | ||
-- make sure our plugin gets loaded | ||
plugins = "bundled," .. PLUGIN_NAME, | ||
-- write & load declarative config, only if 'strategy=off' | ||
declarative_config = strategy == "off" and helpers.make_yaml_file() or nil, | ||
})) | ||
end) | ||
|
||
lazy_teardown(function() | ||
helpers.stop_kong(nil, true) | ||
end) | ||
|
||
before_each(function() | ||
client = helpers.proxy_client() | ||
end) | ||
|
||
after_each(function() | ||
if client then client:close() end | ||
end) | ||
|
||
|
||
|
||
describe("request", function() | ||
it("gets a auth key header", function() | ||
local r = client:get("/request", { | ||
headers = { | ||
host = "resource1.com", | ||
auth_key = "123456" | ||
} | ||
}) | ||
-- validate that the request succeeded, response status 200 | ||
assert.response(r).has.status(200) | ||
-- now check the request (as echoed by the mock backend) to have the header | ||
local header_value = assert.request(r).has.header("auth_key") | ||
-- validate the value of that header | ||
assert.equal("123456", header_value) | ||
-- The mock server returns 123456. The ideal auth server would issue JWT token | ||
local auth_token = assert.request(r).has.header("Authorizaion") | ||
assert.equal("Bearer 123456", auth_token) | ||
end) | ||
end) | ||
|
||
|
||
end) | ||
|
||
end end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
local PLUGIN_NAME = "key-token" | ||
|
||
|
||
-- helper function to validate data against a schema | ||
local validate do | ||
local validate_entity = require("spec.helpers").validate_plugin_config_schema | ||
local plugin_schema = require("kong.plugins."..PLUGIN_NAME..".schema") | ||
|
||
function validate(data) | ||
return validate_entity(data, plugin_schema) | ||
end | ||
end | ||
|
||
|
||
describe(PLUGIN_NAME .. ": (schema)", function() | ||
|
||
|
||
it("accepts request key, auth server and ttl", function() | ||
local ok, err = validate({ | ||
request_key_name = "My-Request-Header", | ||
auth_server = "http://my-auth-service/", | ||
ttl = 300 | ||
}) | ||
assert.is_nil(err) | ||
assert.is_truthy(ok) | ||
end) | ||
|
||
|
||
it("accepts default configs", function() | ||
local ok, err = validate({ }) | ||
assert.is_nil(err) | ||
assert.is_truthy(ok) | ||
end) | ||
|
||
|
||
end) |