/
jwt_claim_check.lua
101 lines (84 loc) · 3.08 KB
/
jwt_claim_check.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
local policy = require('apicast.policy')
local _M = policy.new('JWT check policy', 'builtin')
local Condition = require('apicast.conditions.condition')
local MappingRule = require('apicast.mapping_rule')
local Operation = require('apicast.conditions.operation')
local TemplateString = require('apicast.template_string')
local escape = require("resty.http.uri_escape")
local ipairs = ipairs
local new = _M.new
local default_error_message = "Request blocked due to JWT claim policy"
local default_template_type = 'plain'
local default_allowed_methods = { MappingRule.any_method }
local function deny_request(error_msg)
ngx.status = ngx.HTTP_FORBIDDEN
ngx.say(error_msg)
ngx.exit(ngx.status)
end
function _M.new(config)
local self = new(config)
self.error_message = config.error_message or default_error_message
self.rules = {}
for _, rule in ipairs(config.rules) do
local conditions = {}
for _, condition in ipairs(rule.operations) do
if condition.jwt_claim_type == "plain" then
-- Due to this need to be fetched from the JWT claim, render the match
-- as liquid to be able to fetch the info from the JWT_claim
condition.jwt_claim = "{{"..condition.jwt_claim.."}}"
end
table.insert( conditions,
Operation.new(
condition.jwt_claim, "liquid",
condition.op,
condition.value, condition.value_type or default_template_type))
end
table.insert( self.rules, {
condition = Condition.new(conditions, rule.combine_op),
methods = rule.methods or default_allowed_methods,
resource = TemplateString.new(
rule.resource,
rule.resource_type or default_template_type),
})
end
return self
end
-- is_rule_denied_request returns true if the request need to be blocked based
-- on a provided rule with the request context.
-- This function will only work if the request match on resource and in one of
-- the methods, the methods that are not defined in the rule will be allowed.
local function is_rule_denied_request(rule, context)
local uri = context:get_uri()
-- URI need to be escaped to be able to match values with special characters
-- (like spaces)
-- Example: if URI is `/foo /bar` it will be translated to `/foo%20/bar`
local escaped_uri = escape.escape_uri(uri)
local request_method = ngx.req.get_method()
local resource = rule.resource:render(context)
local mapping_rule_match = false
for _, method in ipairs(rule.methods) do
local mapping_rule = MappingRule.from_proxy_rule({
http_method = method,
pattern = resource,
querystring_parameters = {},
-- the name of the metric is irrelevant
metric_system_name = 'hits'
})
if mapping_rule:matches(request_method, escaped_uri) then
mapping_rule_match = true
break
end
end
if not mapping_rule_match then
return false
end
return not rule.condition:evaluate(context.jwt)
end
function _M:access(context)
for _, rule in ipairs(self.rules) do
if is_rule_denied_request(rule, context) then
return deny_request(self.error_message)
end
end
end
return _M