diff --git a/README.md b/README.md
index 6bd933e..2b97ef9 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ It also externalizes introspection and caching from APIs, to keep the API securi
If you are using luarocks, execute the following command to install the plugin:
```bash
-luarocks install kong-phantom-token 2.0.1
+luarocks install kong-phantom-token 3.0.0
```
Or deploy the .lua files into Kong's plugin directory, eg `/usr/local/share/lua/5.1/kong/plugins/phantom-token`.
@@ -30,7 +30,7 @@ Or deploy the .lua files into Kong's plugin directory, eg `/usr/local/share/lua/
If you are using luarocks, execute the following command to install the plugin:
```bash
-luarocks install lua-resty-phantom-token 2.0.1
+luarocks install lua-resty-phantom-token 3.0.0
```
Or deploy the `access.lua` file to `resty/phantom-token.lua`, where the resty folder is in the `lua_package_path`.\
@@ -157,7 +157,7 @@ Then apply the plugin to one or more locations with configuration similar to the
```nginx
location ~ ^/api {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'https://login.example.com/oauth/v2/oauth-introspect',
diff --git a/curity-test-config.xml b/curity-test-config.xml
index fd31032..19f0de1 100644
--- a/curity-test-config.xml
+++ b/curity-test-config.xml
@@ -151,6 +151,8 @@
default-datasource
jdbc:hsqldb:file:${se.curity:identity-server:db};ifexists=true;hsqldb.lock_file=false
+
+
org.hsqldb.jdbc.JDBCDriver
SA
diff --git a/docker/deploy.sh b/docker/deploy.sh
index 358ce7e..b771552 100755
--- a/docker/deploy.sh
+++ b/docker/deploy.sh
@@ -46,11 +46,11 @@ fi
#
if [ "$PROFILE" == 'kong' ]; then
- docker build -f docker/kong/Dockerfile --no-cache -t custom_kong:3.0.0-alpine .
+ docker build -f docker/kong/Dockerfile --no-cache -t custom_kong:3.9.0-ubuntu .
elif [ "$PROFILE" == 'openresty' ]; then
- docker build -f docker/openresty/Dockerfile --no-cache -t custom_openresty:1.21.4.1-bionic .
+ docker build -f docker/openresty/Dockerfile --no-cache -t custom_openresty:1.31.1.1-bookworm .
fi
if [ $? -ne 0 ]; then
echo "Problem encountered building the reverse proxy docker image"
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 4138b52..3cc9981 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -4,7 +4,7 @@ services:
# Use Kong Open Source as the reverse proxy when the kong profile is set on the command line
#
kong:
- image: custom_kong:3.0.0-alpine
+ image: custom_kong:3.9.0-ubuntu
hostname: kongserver
ports:
- 3000:3000
@@ -24,7 +24,7 @@ services:
# Use OpenResty as the reverse proxy when the openresty profile is set on the command line
#
openresty:
- image: custom_openresty:1.21.4.1-bionic
+ image: custom_openresty:1.31.1.1-bookworm
hostname: openrestyserver
ports:
- 3000:3000
@@ -49,7 +49,7 @@ services:
# The Curity Identity Server is deployed for all test configurations
#
curity:
- image: curity.azurecr.io/curity/idsvr
+ image: curity.azurecr.io/curity/idsvr:latest
hostname: curityserver
ports:
- 6749:6749
diff --git a/docker/kong/Dockerfile b/docker/kong/Dockerfile
index 91835b8..bcc7a4a 100644
--- a/docker/kong/Dockerfile
+++ b/docker/kong/Dockerfile
@@ -1,4 +1,4 @@
-FROM kong:3.0.0-alpine
+FROM kong:3.9-ubuntu
# Deploy the plugin and dependencies for local testing
USER root
diff --git a/docker/openresty/Dockerfile b/docker/openresty/Dockerfile
index 6044595..4ecb481 100644
--- a/docker/openresty/Dockerfile
+++ b/docker/openresty/Dockerfile
@@ -1,4 +1,4 @@
-FROM openresty/openresty:1.21.4.1-bionic
+FROM openresty/openresty:1.31.1.1-bookworm-fat
# Deploy the plugin and dependencies for local testing
COPY ./lua*.rockspec /tmp/phantom-token/
diff --git a/docker/openresty/nginx.conf b/docker/openresty/nginx.conf
index b2ebfdb..ee6cd78 100644
--- a/docker/openresty/nginx.conf
+++ b/docker/openresty/nginx.conf
@@ -32,7 +32,7 @@ http {
resolver 127.0.0.11;
# If required, introspect an opaque access token and forward a JWT to the API
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://curityserver:8443/oauth/v2/oauth-introspect',
@@ -42,7 +42,17 @@ http {
}
local phantomToken = require 'resty.phantom-token'
- phantomToken.run(config)
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
# Then proxy the updated request
diff --git a/kong-phantom-token-2.0.1-1.rockspec b/kong-phantom-token-3.0.0-1.rockspec
similarity index 97%
rename from kong-phantom-token-2.0.1-1.rockspec
rename to kong-phantom-token-3.0.0-1.rockspec
index a9f5094..c794cf2 100644
--- a/kong-phantom-token-2.0.1-1.rockspec
+++ b/kong-phantom-token-3.0.0-1.rockspec
@@ -1,8 +1,8 @@
package = "kong-phantom-token"
-version = "2.0.1-1"
+version = "3.0.0-1"
source = {
url = "git://github.com/curityio/nginx-lua-phantom-token-plugin",
- tag = "v2.0.1"
+ tag = "v3.0.0"
}
description = {
summary = "A Lua plugin used during API requests to exchange an opaque reference token for a JWT access token",
diff --git a/lua-resty-phantom-token-2.0.1-1.rockspec b/lua-resty-phantom-token-3.0.0-1.rockspec
similarity index 97%
rename from lua-resty-phantom-token-2.0.1-1.rockspec
rename to lua-resty-phantom-token-3.0.0-1.rockspec
index 2d9ed82..4b18fea 100644
--- a/lua-resty-phantom-token-2.0.1-1.rockspec
+++ b/lua-resty-phantom-token-3.0.0-1.rockspec
@@ -1,8 +1,8 @@
package = "lua-resty-phantom-token"
-version = "2.0.1-1"
+version = "3.0.0-1"
source = {
url = "git://github.com/curityio/nginx-lua-phantom-token-plugin",
- tag = "v2.0.1"
+ tag = "v3.0.0"
}
description = {
summary = "A Lua plugin used during API requests to exchange an opaque reference token for a JWT access token",
diff --git a/plugin/access.lua b/plugin/access.lua
index e1af18e..dda1b78 100644
--- a/plugin/access.lua
+++ b/plugin/access.lua
@@ -36,36 +36,10 @@ local function array_has_value(arr, val)
return false
end
---
--- Verify configuration and set defaults that are the same for all requests
---
-local function initialize_configuration(config)
-
- if config == nil or
- config.introspection_endpoint == nil or
- config.client_id == nil or
- config.client_secret == nil then
- ngx.log(ngx.WARN, 'The phantom token configuration is invalid and must be corrected')
- return false
- end
-
- if config.token_cache_seconds == nil or config.token_cache_seconds <= 0 then
- config.token_cache_seconds = 300
- end
- if config.scope == nil then
- config.scope = ''
- end
- if config.verify_ssl == nil then
- config.verify_ssl = true
- end
-
- return true
-end
-
--
-- Return errors due to invalid tokens or introspection technical problems
--
-local function error_response(status, code, message)
+local function error_response(scheme, status, code, message)
local method = ngx.req.get_method():upper()
if method ~= 'HEAD' then
@@ -73,7 +47,7 @@ local function error_response(status, code, message)
ngx.status = status
ngx.header['content-type'] = 'application/json'
if status == 401 then
- ngx.header['WWW-Authenticate'] = 'Bearer'
+ ngx.header['WWW-Authenticate'] = string.format('%s error="%s", error_description="%s"', scheme, code, message)
end
local jsonData = '{"code":"' .. code .. '","message":"' .. message .. '"}'
@@ -86,12 +60,12 @@ end
--
-- Return a generic message for all three of these error categories
--
-local function unauthorized_error_response()
- error_response(ngx.HTTP_UNAUTHORIZED, 'unauthorized', 'Missing, invalid or expired access token')
+local function unauthorized_error_response(scheme)
+ error_response(scheme, ngx.HTTP_UNAUTHORIZED, 'invalid_token', 'Missing, invalid or expired access token')
end
-local function server_error_response(config)
- error_response(ngx.HTTP_INTERNAL_SERVER_ERROR, 'server_error', 'Problem encountered processing the request')
+local function server_error_response()
+ error_response('', ngx.HTTP_INTERNAL_SERVER_ERROR, 'server_error', 'Problem encountered processing the request')
end
--
@@ -224,45 +198,101 @@ local function verify_access_token(access_token, config)
end
--
--- The public entry point to introspect the token then forward the JWT to the API
+-- Apply default configuration settings, e.g. when running in OpenResty
--
-function _M.run(config)
+local function apply_default_configuration(config)
- -- Start by validating configuration
- if initialize_configuration(config) == false then
- server_error_response(config)
- return
+ if config.token_cache_seconds == nil or config.token_cache_seconds <= 0 then
+ config.token_cache_seconds = 300
+ end
+
+ if config.verify_ssl == nil then
+ config.verify_ssl = true
+ end
+
+ if not config.scheme then
+ config.scheme = "Bearer"
+ end
+end
+
+--
+-- Validate incorrect configuration before running in OpenResty
+--
+function _M.validate(config)
+
+ if not config then
+ return nil, "The phantom token plugin requires configuration"
+ end
+
+ if not config.client_id then
+ return nil, "The phantom token plugin requires a client_id parameter"
+ end
+
+ if not config.client_secret then
+ return nil, "The phantom token plugin requires a client_secret parameter"
+ end
+
+ if not config.introspection_endpoint or not config.introspection_endpoint:match("^https?://") then
+ return nil, "The phantom token plugin requires an introspection endpoint that starts with http:// or https://"
end
+ if config.scheme and config.scheme ~= "Bearer" and config.scheme ~= "DPoP" then
+ return nil, "The phantom token plugin requires a scheme of Bearer or DPoP"
+ end
+
+ return true
+end
+
+--
+-- The public entry point to introspect the token then forward the JWT to the API
+--
+function _M.run(config)
+
if ngx.req.get_method() == 'OPTIONS' then
return
end
+ apply_default_configuration(config)
+
local auth_header = ngx.req.get_headers()['Authorization']
- if auth_header and string.len(auth_header) > 7 and string.lower(string.sub(auth_header, 1, 7)) == 'bearer ' then
+ if not auth_header then
+ ngx.log(ngx.WARN, 'No HTTP Authorization header was found')
+ unauthorized_error_response(config.scheme)
+ end
- local access_token_untrimmed = string.sub(auth_header, 8)
- local access_token = string.gsub(access_token_untrimmed, "%s+", "")
- local result = verify_access_token(access_token, config)
-
- if result.status == 500 then
- error_response(ngx.HTTP_INTERNAL_SERVER_ERROR, 'server_error', 'Problem encountered authorizing the HTTP request')
- end
+ -- Read either an Authorization: Bearer or Authorization: DPoP value
+ local scheme, access_token = auth_header:match("^%s*(%S+)%s+(.+)%s*$")
+ if not scheme or scheme:lower() ~= config.scheme:lower() then
+ ngx.log(ngx.WARN, 'No valid scheme was found in the HTTP Authorization header')
+ unauthorized_error_response(config.scheme)
+ end
- if result.status == 403 then
- error_response(ngx.HTTP_FORBIDDEN, 'forbidden', 'The token does not contain the required scope')
- end
+ if not access_token then
+ ngx.log(ngx.WARN, 'No valid access token was found in the HTTP Authorization header')
+ unauthorized_error_response(config.scheme)
+ end
- if result.status ~= 200 then
- ngx.log(ngx.WARN, 'Received a ' .. result.status .. ' introspection response due to the access token being invalid or expired')
- unauthorized_error_response()
- end
+ local result = verify_access_token(access_token, config)
- ngx.req.set_header('Authorization', 'Bearer ' .. result.jwt)
- else
+ if result.status == 500 then
+ error_response(config.scheme, ngx.HTTP_INTERNAL_SERVER_ERROR, 'server_error', 'Problem encountered authorizing the HTTP request')
+ end
- ngx.log(ngx.WARN, 'No valid access token was found in the HTTP Authorization header')
- unauthorized_error_response()
+ if result.status == 403 then
+ error_response(config.scheme, ngx.HTTP_FORBIDDEN, 'forbidden', 'The token does not contain the required scope')
+ end
+
+ if result.status ~= 200 then
+ ngx.log(ngx.WARN, 'Received a ' .. result.status .. ' introspection response due to the access token being invalid or expired')
+ unauthorized_error_response(config.scheme)
+ end
+
+ -- Pass the JWT to the next stage for processing
+ ngx.req.set_header('Authorization', config.scheme .. ' ' .. result.jwt)
+
+ -- For DPoP, make the original opaque access token available in a variable, to enable verification of the DPoP ath claim
+ if config.scheme == 'DPoP' then
+ ngx.var.original_access_token = access_token
end
end
diff --git a/plugin/handler.lua b/plugin/handler.lua
index 5ba2ec0..f9ff1a3 100644
--- a/plugin/handler.lua
+++ b/plugin/handler.lua
@@ -7,11 +7,11 @@ local access = require "kong.plugins.phantom-token.access"
-- See https://github.com/Kong/kong/discussions/7193 for more about the PRIORITY field
local PhantomToken = {
PRIORITY = 1000,
- VERSION = "2.0.1",
+ VERSION = "3.0.0",
}
-function PhantomToken:access(conf)
- access.run(conf)
+function PhantomToken:access(config)
+ access.run(config)
end
-return PhantomToken
+return PhantomToken
\ No newline at end of file
diff --git a/plugin/schema.lua b/plugin/schema.lua
index f1b9437..1f576b8 100644
--- a/plugin/schema.lua
+++ b/plugin/schema.lua
@@ -3,13 +3,15 @@ return {
fields = {{
config = {
type = "record",
+ required = true,
fields = {
- { introspection_endpoint = { type = "string", required = true } },
+ { introspection_endpoint = { type = "string", required = true, match = "^https?://" } },
{ client_id = { type = "string", required = true } },
{ client_secret = { type = "string", required = true } },
- { token_cache_seconds = { type = "number", required = true, default = 300 } },
+ { token_cache_seconds = { type = "number", required = false } },
{ scope = { type = "string", required = false } },
- { verify_ssl = { type = "boolean", required = true, default = true } }
+ { verify_ssl = { type = "boolean", required = false } },
+ { scheme = { type = "string", required = false, one_of = { "Bearer", "DPoP" } } }
}
}}
}
diff --git a/t/advanced_routing.t b/t/advanced_routing.t
index 537a493..44e9028 100644
--- a/t/advanced_routing.t
+++ b/t/advanced_routing.t
@@ -62,7 +62,7 @@ location @loc_bypass {
}
location @loc_phantom_token {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -112,7 +112,7 @@ location @loc_bypass {
}
location @loc_phantom_token {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
diff --git a/t/api_requests.t b/t/api_requests.t
index 6ef777c..e658d80 100644
--- a/t/api_requests.t
+++ b/t/api_requests.t
@@ -51,7 +51,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -93,7 +93,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -117,10 +117,12 @@ GET /t
--- response_headers
content-type: application/json
-WWW-Authenticate: Bearer
+
+--- response_headers_like
+WWW-Authenticate: ^Bearer
--- response_body_like chomp
-{"code":"unauthorized","message":"Missing, invalid or expired access token"}
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
=== TEST_API_REQUEST_3: Sending no authorization header results in an access denied error
#################################################
@@ -133,7 +135,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -154,10 +156,12 @@ GET /t
--- response_headers
content-type: application/json
-WWW-Authenticate: Bearer
+
+--- response_headers_like
+WWW-Authenticate: ^Bearer
--- response_body_like chomp
-{"code":"unauthorized","message":"Missing, invalid or expired access token"}
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
=== TEST_API_REQUEST_4: The wrong authorization scheme results in an access denied error
##############################################################
@@ -170,7 +174,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -194,10 +198,12 @@ GET /t
--- response_headers
content-type: application/json
-WWW-Authenticate: Bearer
+
+--- response_headers_like
+WWW-Authenticate: ^Bearer
--- response_body_like chomp
-{"code":"unauthorized","message":"Missing, invalid or expired access token"}
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
=== TEST_API_REQUEST_5: A valid token with trash after results in an access denied error
######################################################################################
@@ -210,7 +216,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -234,10 +240,12 @@ GET /t
--- response_headers
content-type: application/json
-WWW-Authenticate: Bearer
+
+--- response_headers_like
+WWW-Authenticate: ^Bearer
--- response_body_like chomp
-{"code":"unauthorized","message":"Missing, invalid or expired access token"}
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
=== TEST_API_REQUEST_6: The bearer HTTP method can be in upper case
#####################################################
@@ -250,7 +258,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -292,7 +300,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -334,7 +342,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
diff --git a/t/config.t b/t/config.t
index 14fd7c9..d91f0b1 100644
--- a/t/config.t
+++ b/t/config.t
@@ -51,7 +51,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
client_id = 'introspection-client',
@@ -60,7 +60,16 @@ location /t {
}
local phantomToken = require 'phantom-token'
- phantomToken.run(config)
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
}
@@ -72,8 +81,8 @@ GET /t
--- more_headers eval
"Authorization: bearer " . $main::token;
---- error_log
-The phantom token configuration is invalid and must be corrected
+--- error_log_like
+The phantom token plugin requires^
--- response_body_like chomp
{"code":"server_error","message":"Problem encountered processing the request"}
@@ -86,7 +95,7 @@ The phantom token configuration is invalid and must be corrected
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
client_id = 'introspection-client',
@@ -95,7 +104,16 @@ location /t {
}
local phantomToken = require 'phantom-token'
- phantomToken.run(config)
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
}
@@ -107,27 +125,36 @@ GET /t
--- more_headers eval
"Authorization: bearer " . $main::token;
---- error_log
-The phantom token configuration is invalid and must be corrected
+--- error_log_like
+The phantom token plugin requires^
--- response_body_like chomp
{"code":"server_error","message":"Problem encountered processing the request"}
-=== TEST CONFIG_2: A deployment with missing data does not crash NGINX
-#######################################################################################################
-# Verify that empty configuration is handled in a controlled manner rather than causing server problems
-#######################################################################################################
+=== TEST CONFIG_3: A deployment with missing data fails validation
+##################################################################
+# Verify that empty configuration is handled by failing validation
+##################################################################
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
}
local phantomToken = require 'phantom-token'
- phantomToken.run(config)
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
}
@@ -139,24 +166,34 @@ GET /t
--- more_headers eval
"Authorization: bearer " . $main::token;
---- error_log
-The phantom token configuration is invalid and must be corrected
+--- error_log_like
+The phantom token plugin requires^
--- response_body_like chomp
{"code":"server_error","message":"Problem encountered processing the request"}
-=== TEST CONFIG_3: A deployment with null data does not crash NGINX
-#######################################################################################################
-# Verify that null configuration is handled in a controlled manner rather than causing server problems
-#######################################################################################################
+=== TEST CONFIG_4: A deployment with null data fails validation
+#################################################################
+# Verify that null configuration is handled by failing validation
+#################################################################
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
+ local config = nil
local phantomToken = require 'phantom-token'
- phantomToken.run()
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
}
@@ -168,13 +205,13 @@ GET /t
--- more_headers eval
"Authorization: bearer " . $main::token;
---- error_log
-The phantom token configuration is invalid and must be corrected
+--- error_log_like
+The phantom token plugin requires^
--- response_body_like chomp
{"code":"server_error","message":"Problem encountered processing the request"}
-=== TEST CONFIG_4: A deployment with a misspelt field does not crash NGINX
+=== TEST CONFIG_5: A deployment with a misspelt field fails validation
#####################################################################################################
# Verify that bad configuration is handled in a controlled manner rather than causing server problems
#####################################################################################################
@@ -182,7 +219,7 @@ The phantom token configuration is invalid and must be corrected
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspectionn_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -192,7 +229,16 @@ location /t {
}
local phantomToken = require 'phantom-token'
- phantomToken.run(config)
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
}
@@ -204,13 +250,13 @@ GET /t
--- more_headers eval
"Authorization: bearer " . $main::token;
---- error_log
-The phantom token configuration is invalid and must be corrected
+--- error_log_like
+The phantom token plugin requires^
--- response_body_like chomp
{"code":"server_error","message":"Problem encountered processing the request"}
-=== TEST_CONFIG_5: A deployment with all optional fields are omitted successfully introspects tokens
+=== TEST_CONFIG_6: A deployment with all optional fields are omitted successfully introspects tokens
#######################################################################
# The happy case works as expected when all optional fields are omitted
#######################################################################
@@ -221,7 +267,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -230,7 +276,16 @@ location /t {
}
local phantomToken = require 'phantom-token'
- phantomToken.run(config)
+ local ok, err = phantomToken.validate(config)
+ if ok then
+ phantomToken.run(config)
+ else
+ ngx.log(ngx.ERR, err)
+ ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
+ ngx.header.content_type = 'application/json'
+ ngx.say('{"code":"server_error","message":"Problem encountered processing the request"}')
+ return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
+ end
}
proxy_pass http://127.0.0.1:1984/target;
diff --git a/t/failure_scenarios.t b/t/failure_scenarios.t
index 4f78610..2435469 100644
--- a/t/failure_scenarios.t
+++ b/t/failure_scenarios.t
@@ -52,7 +52,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8447/oauth/v2/oauth-introspect',
@@ -88,7 +88,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -111,4 +111,4 @@ GET /t
"Authorization: bearer " . $main::token
--- response_body_like chomp
-{"code":"unauthorized","message":"Missing, invalid or expired access token"}
\ No newline at end of file
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
\ No newline at end of file
diff --git a/t/schemes.t b/t/schemes.t
new file mode 100644
index 0000000..91a4170
--- /dev/null
+++ b/t/schemes.t
@@ -0,0 +1,214 @@
+#!/usr/bin/perl
+
+################################################################
+# Runs tests focused on authorization schemes of Bearer and DPoP
+################################################################
+
+use strict;
+use warnings;
+use FindBin;
+use lib "$FindBin::Bin/lib";
+use Test::Nginx::Socket 'no_plan';
+
+SKIP: {
+ our $token = &get_token_from_idsvr();
+ if ($token) {
+ run_tests();
+ }
+ else {
+ fail("Could not get token from idsvr");
+ }
+}
+
+sub get_token_from_idsvr {
+ use LWP::UserAgent;
+
+ my $ua = LWP::UserAgent->new();
+
+ my $response = $ua->post("http://localhost:8443/oauth/v2/oauth-token", {
+ "client_id" => "test-client",
+ "client_secret" => "secret1",
+ "grant_type" => "client_credentials",
+ "scope" => "read"
+ });
+ my $content = $response->decoded_content();
+
+ my ($result) = $content =~ /access_token":"([^"]+)/;
+
+ return $result;
+}
+
+__DATA__
+
+=== TEST_SCHEME_1: An opaque bearer token can be introspected for a phantom token
+########################################################
+# The happy case works as expected when you set a scheme
+########################################################
+
+--- http_config
+lua_shared_dict phantom-token 10m;
+
+--- config
+location /t {
+
+ access_by_lua_block {
+
+ local config = {
+ introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
+ client_id = 'introspection-client',
+ client_secret = 'secret2',
+ token_cache_seconds = 900,
+ scheme = 'Bearer'
+ }
+
+ local phantomToken = require 'phantom-token'
+ phantomToken.run(config)
+ }
+
+ proxy_pass http://127.0.0.1:1984/target;
+}
+location /target {
+ add_header 'authorization' $http_authorization;
+ return 200;
+}
+
+--- error_code: 200
+
+--- request
+GET /t
+
+--- more_headers eval
+"Authorization: bearer " . $main::token;
+
+--- response_headers_like
+authorization: Bearer ey.*
+
+=== TEST_SCHEME_2: Sending an invalid bearer token that fails introspection results in an access denied error
+#########################################################
+# An unrecognised token is rejected when you set a scheme
+#########################################################
+
+--- http_config
+lua_shared_dict phantom-token 10m;
+
+--- config
+location /t {
+
+ access_by_lua_block {
+
+ local config = {
+ introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
+ client_id = 'introspection-client',
+ client_secret = 'secret2',
+ token_cache_seconds = 900,
+ scheme = 'Bearer'
+ }
+
+ local phantomToken = require 'phantom-token'
+ phantomToken.run(config)
+ }
+}
+
+--- more_headers
+Authorization: bearer zort
+
+--- request
+GET /t
+
+--- error_code: 401
+
+--- response_headers
+content-type: application/json
+
+--- response_headers_like
+WWW-Authenticate: ^Bearer
+
+--- response_body_like chomp
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
+
+=== TEST_SCHEME_3: An opaque DPoP token can be introspected for a JWT
+#############################################################
+# The happy case works as expected when you set a DPoP scheme
+#############################################################
+
+--- http_config
+lua_shared_dict phantom-token 10m;
+
+--- config
+location /t {
+
+ set $original_access_token "";
+ access_by_lua_block {
+
+ local config = {
+ introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
+ client_id = 'introspection-client',
+ client_secret = 'secret2',
+ token_cache_seconds = 900,
+ scheme = 'DPoP'
+ }
+
+ local phantomToken = require 'phantom-token'
+ phantomToken.run(config)
+ }
+
+ proxy_pass http://127.0.0.1:1984/target;
+}
+location /target {
+ add_header 'authorization' $http_authorization;
+ return 200;
+}
+
+--- error_code: 200
+
+--- request
+GET /t
+
+--- more_headers eval
+"Authorization: dpop " . $main::token;
+
+--- response_headers_like
+authorization: DPoP ey.*
+
+=== TEST_SCHEME_4: Sending an invalid DPoP token that fails introspection results in an access denied error
+##############################################################
+# An unrecognised token is rejected when you set a DPoP scheme
+##############################################################
+
+--- http_config
+lua_shared_dict phantom-token 10m;
+
+--- config
+location /t {
+
+ access_by_lua_block {
+
+ local config = {
+ introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
+ client_id = 'introspection-client',
+ client_secret = 'secret2',
+ token_cache_seconds = 900,
+ scheme = 'DPoP'
+ }
+
+ local phantomToken = require 'phantom-token'
+ phantomToken.run(config)
+ }
+}
+
+--- more_headers
+Authorization: dpop zort
+
+--- request
+GET /t
+
+--- error_code: 401
+
+--- response_headers
+content-type: application/json
+
+--- response_headers_like
+WWW-Authenticate: ^DPoP
+
+--- response_body_like chomp
+{"code":"invalid_token","message":"Missing, invalid or expired access token"}
diff --git a/t/scope_checks.t b/t/scope_checks.t
index b4dc1b4..745f43e 100644
--- a/t/scope_checks.t
+++ b/t/scope_checks.t
@@ -52,7 +52,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -95,7 +95,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
@@ -135,7 +135,7 @@ lua_shared_dict phantom-token 10m;
--- config
location /t {
- rewrite_by_lua_block {
+ access_by_lua_block {
local config = {
introspection_endpoint = 'http://127.0.0.1:8443/oauth/v2/oauth-introspect',
diff --git a/test.sh b/test.sh
index 298a763..a081b2f 100755
--- a/test.sh
+++ b/test.sh
@@ -14,7 +14,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
#
# Point to the OpenResty install
#
-OPENRESTY_ROOT=/usr/local/Cellar/openresty/1.25.3.1_1
+OPENRESTY_ROOT=/opt/homebrew/opt/openresty/
#
# Ensure that the OpenResty nginx, with LUA support, will be found by the prove tool