Skip to content

Commit

Permalink
Merge pull request #1535 from tkan145/THREESCALE-9510-jwt-clain-check…
Browse files Browse the repository at this point in the history
…-extened-context

[THREESCALE-9510] Allow JWT Claim Check policy access to full request context when evaluate condition
  • Loading branch information
tkan145 authored Feb 16, 2025
2 parents cd477ef + 5296f6f commit b132033
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Replace internal OPENSSL module with lua-resty-openssl [PR #1502](https://github.com/3scale/APIcast/pull/1502) [THREESCALE-11412](https://issues.redhat.com/browse/THREESCALE-11412)
- Remove opentracing support [PR #1520](https://github.com/3scale/APIcast/pull/1520) [THREESCALE-11603](https://issues.redhat.com/browse/THREESCALE-11603)
- JWT signature verification, support for ES256/ES512 #1533 [PR #1533](https://github.com/3scale/APIcast/pull/1533) [THREESCALE-11474](https://issues.redhat.com/browse/THREESCALE-11474)
- Add `enable_extended_context` to allow JWT Claim Check access full request context [PR #1535](https://github.com/3scale/APIcast/pull/1535) [THREESCALE-9510](https://issues.redhat.com/browse/THREESCALE-9510)

## [3.15.0] 2024-04-04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
"type": "object",
"properties": {
"jwt_claim": {
"description": "String to get JWT claim",
"description": "String to get JWT claim",
"type": "string"
},
"jwt_claim_type": {
Expand Down Expand Up @@ -110,6 +110,11 @@
}
}
}
},
"enable_extended_context": {
"description": "Whether to enable extened context when evaluate the condition",
"type": "boolean",
"default": false
}
}
}
Expand Down
16 changes: 13 additions & 3 deletions gateway/src/apicast/policy/jwt_claim_check/jwt_claim_check.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ function _M.new(config)
local self = new(config)
self.error_message = config.error_message or default_error_message
self.rules = {}
self.enable_extended_context = config.enable_extended_context
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
if self.enable_extended_context then
condition.jwt_claim = "jwt."..condition.jwt_claim
end
condition.jwt_claim = "{{"..condition.jwt_claim.."}}"
end

Expand All @@ -49,14 +54,15 @@ function _M.new(config)
})
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 function is_rule_denied_request(rule, context, enable_extended_context)

local uri = context:get_uri()
-- URI need to be escaped to be able to match values with special characters
Expand Down Expand Up @@ -86,13 +92,17 @@ local function is_rule_denied_request(rule, context)
return false
end

return not rule.condition:evaluate(context.jwt)
if enable_extended_context then
return not rule.condition:evaluate(context)
else
return not rule.condition:evaluate(context.jwt)
end
end


function _M:access(context)
for _, rule in ipairs(self.rules) do
if is_rule_denied_request(rule, context) then
if is_rule_denied_request(rule, context, self.enable_extended_context) then
return deny_request(self.error_message)
end
end
Expand Down
68 changes: 68 additions & 0 deletions gateway/src/apicast/policy/jwt_claim_check/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,71 @@ the request will be denied.
}
}
```

- Set `enable_extended_context` to `true` to access the full request context, this
allow you to do interesting thing such as checking the claim agains the value of query `check`

```json
{
"name": "apicast.policy.jwt_claim_check",
"configuration": {
"error_message": "Invalid JWT check",
"rules": [
{
"operations": [
{"op": "==", "jwt_claim": "role", "jwt_claim_type": "plain", "value": "{{original_request.query | split: \"check=\" | last}}", "value_type": "liquid"}
],
"combine_op": "and",
"methods": ["ANY"],
"resource": "/resource",
"resource_type": "plain"
}
],
"enable_extended_context": true
}
}
```

NOTE: when `enable_extended_context` is set and `jwt_claim_type`/`value_type` is set to liquid ,the JWT claim value is accessible using the `jwt` prefix.

```json
{
"name": "apicast.policy.jwt_claim_check",
"configuration": {
"error_message": "Invalid JWT check",
"rules": [
{
"operations": [
{"op": "==", "jwt_claim": "{{jwt.role}}", "jwt_claim_type": "liquid", "value": "client1"}
],
"combine_op": "and",
"methods": ["ANY"],
"resource": "/resource",
"resource_type": "plain"
}
],
"enable_extended_context": true
}
}
```

```json
{
"name": "apicast.policy.jwt_claim_check",
"configuration": {
"error_message": "Invalid JWT check",
"rules": [
{
"operations": [
{"op": "==", "jwt_claim": "{{jwt.role}}", "jwt_claim_type": "liquid", "value": "{{jwt.role}}", "value_type": "liquid"}
],
"combine_op": "and",
"methods": ["ANY"],
"resource": "/resource",
"resource_type": "plain"
}
],
"enable_extended_context": true
}
}
```
112 changes: 112 additions & 0 deletions spec/policy/jwt_claim_check/jwt_claim_check_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,34 @@ describe('JWT claim check policy', function()
jwt_check:access(context)
assert.same(ngx.status, 403)
end)

it("with extended context", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="foo", jwt_claim_type="plain", value="fooValue"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.not_same(ngx.status, 403)
end)

it("with extended context and invalid claim", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="invalid", jwt_claim_type="plain", value="fooValue"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.same(ngx.status, 403)
end)
end)

describe("liquid JWT_claim_type", function()
Expand Down Expand Up @@ -86,6 +114,48 @@ describe('JWT claim check policy', function()
jwt_check:access(context)
assert.same(ngx.status, 403)
end)

it("with extended context", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="{{jwt.foo}}", jwt_claim_type="liquid", value="fooValue"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.not_same(ngx.status, 403)
end)

it("with extended context and invalid claim", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="{{invalid}}", jwt_claim_type="liquid", value="fooValue"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.same(ngx.status, 403)
end)

it("with extended context and invalid claim access via jwt prefix", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="{{jwt.invalid}}", jwt_claim_type="liquid", value="fooValue"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.same(ngx.status, 403)
end)
end)

describe("Liquid value type", function()
Expand Down Expand Up @@ -115,6 +185,48 @@ describe('JWT claim check policy', function()
jwt_check:access(context)
assert.same(ngx.status, 403)
end)

it("with extended context, claim is access via with jwt prefix", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="{{jwt.foo}}", jwt_claim_type="liquid", value="{{jwt.foo}}", value_type="liquid"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.not_same(ngx.status, 403)
end)

it("with extended context, invalid claim", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="{{jwt.invalid}}", jwt_claim_type="liquid", value="{{jwt.foo}}", value_type="liquid"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.same(ngx.status, 403)
end)

it("with extended context, invalid value", function()
local jwt_check = JWTClaimCheckPolicy.new({rules={
{
operations={{op="==", jwt_claim="{{jwt.foo}}", jwt_claim_type="liquid", value="{{foo}}", value_type="liquid"}},
combine_op="and",
methods = {"GET"},
resource = ngx.var.uri,
}},
enable_extended_context = true
})
jwt_check:access(context)
assert.same(ngx.status, 403)
end)
end)

describe("Conditions combinations", function()
Expand Down
Loading

0 comments on commit b132033

Please sign in to comment.