Skip to content

Commit

Permalink
Merge pull request #1533 from tkan145/THREESCALE-11474-resty-jwt-0.2.3
Browse files Browse the repository at this point in the history
[THREESCALE-11474] JWT signature verification, support for ES256/ES512
  • Loading branch information
tkan145 authored Feb 3, 2025
2 parents ff932e1 + 2c5a82a commit cd477ef
Show file tree
Hide file tree
Showing 18 changed files with 276 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add `APICAST_LUA_SOCKET_KEEPALIVE_REQUESTS` to limit the number of requests a single keepalive socket can handle [PR #1496](https://github.com/3scale/APIcast/pull/1496) [THREESCALE-11321](https://issues.redhat.com/browse/THREESCALE-11321)
- 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)

## [3.15.0] 2024-04-04

Expand Down
11 changes: 9 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM registry.access.redhat.com/ubi8:8.5

ARG OPENRESTY_RPM_VERSION="1.21.4-1.el8"
ARG LUAROCKS_VERSION="2.3.0"
ARG LUAROCKS_VERSION="3.11.1"
ARG JAEGERTRACING_CPP_CLIENT_RPM_VERSION="0.3.1-13.el8"

LABEL summary="The 3scale API gateway (APIcast) is an OpenResty application, which consists of two parts: NGINX configuration and Lua files." \
Expand Down Expand Up @@ -47,7 +47,6 @@ ENV PATH="./lua_modules/bin:/usr/local/openresty/luajit/bin/:${PATH}" \
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/pintsized/lua-resty-http-0.17.1-0.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/kikito/router-2.1-0.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/kikito/inspect-3.1.1-0.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/cdbattags/lua-resty-jwt-0.2.0-0.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/3scale/lua-resty-url-0.3.5-1.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/3scale/lua-resty-env-0.4.0-1.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/3scale/liquid-0.2.0-2.src.rock
Expand All @@ -63,6 +62,14 @@ RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/man
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/membphis/lua-resty-ipmatcher-0.6.1-0.src.rock
RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/fffonion/lua-resty-openssl-1.5.1-1.src.rock

# Install lua-resty-jwt from source due to Authentication Bypass bug
# See https://github.com/cdbattags/lua-resty-jwt/issues/61
RUN cd /tmp \
&& git clone --recurse-submodules https://github.com/cdbattags/lua-resty-jwt \
&& cd lua-resty-jwt \
&& git reset --hard d1558e2 \
&& luarocks make --tree /usr/local lua-resty-jwt-dev-0.rockspec

RUN yum -y remove libyaml-devel m4 openssl-devel git gcc luarocks && \
rm -rf /var/cache/yum && yum clean all -y && \
rm -rf ./*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Source0: licenses.xml
Source1: lua-resty-http-0.17.1-0.src.rock
Source2: router-2.1-0.src.rock
Source3: inspect-3.1.1-0.src.rock
Source4: lua-resty-jwt-0.2.0-0.src.rock
Source4: lua-resty-jwt-0.2.3-1.src.rock
Source5: lua-resty-url-0.3.5-1.src.rock
Source6: lua-resty-env-0.4.0-1.src.rock
Source7: liquid-0.2.0-2.src.rock
Expand Down Expand Up @@ -80,6 +80,8 @@ rm -rf %{buildroot}
%license licenses.xml

%changelog
* Mon Jan 03 2025 An Tran <atra@redhat.com> - 2.10.0-2
- Upgrade lua-resty-jwt to v0.2.3-1

* Fri Dec 02 2022 Yorgos Saslis <gsaslisl@redhat.com> - 2.10.0-1
- Minor version bump to clarify lua-liquid dependency bump that already happened in 2.9.6-12
Expand Down
2 changes: 1 addition & 1 deletion dependencies/rpm-specs/gateway-rockspecs/rockspecs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
https://luarocks.org/manifests/pintsized/lua-resty-http-0.17.1-0.rockspec
https://luarocks.org/manifests/kikito/router-2.1-0.rockspec
https://luarocks.org/manifests/kikito/inspect-3.1.1-0.rockspec
https://luarocks.org/manifests/cdbattags/lua-resty-jwt-0.2.0-0.rockspec
https://luarocks.org/manifests/cdbattags/lua-resty-jwt-0.2.3-0.rockspec
https://luarocks.org/manifests/3scale/lua-resty-url-0.3.5-1.rockspec
https://luarocks.org/manifests/3scale/lua-resty-env-0.4.0-1.rockspec
https://luarocks.org/manifests/3scale/liquid-0.2.0-2.rockspec
Expand Down
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion gateway/Roverfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ lua-resty-http 0.17.1-0|4ab4269cf442ba52507aa2c718f606054452fcad|production
lua-resty-ipmatcher 0.6.1-0|62d4c44d67227e8f3fe02331c2f8b90fe0d7ccd1|production
lua-resty-iputils 0.3.0-2|6110b41eaa52efd25e56f89e34412ab95f700d57|production
lua-resty-jit-uuid 0.0.7-2|64ae38de75c9d58f330d89e140ac872771c19223|production
lua-resty-jwt 0.2.0-0|2a62ff95eae91df6bd8655080a4b9b04c61bec6b|production
lua-resty-jwt 0.2.3-0|b3d5c085643fa95099e72a609c57095802106ff9|production
lua-resty-openssl 1.5.1-1|a900c5f5897448c181dd58073e51cdeeb3fd0029|production
lua-resty-repl 0.0.6-0|3878f41b7e8f97b1c96919db19dbee9496569dda|development
lua-resty-url 0.3.5-1||production
Expand Down
2 changes: 1 addition & 1 deletion gateway/apicast-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencies = {
'inspect',
'lyaml',
'router',
'lua-resty-jwt == 0.2.0',
'lua-resty-jwt',
'lua-resty-url',
'lua-resty-env',
'lua-resty-execvp',
Expand Down
49 changes: 49 additions & 0 deletions spec/fixtures/certs.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
return {
rsa_public_key = [[
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALClz96cDQ965ENYMfZzG+Acu25lpx2K
NpAALBQ+catCA59us7+uLY5rjQR6SOgZpCz5PJiKNAdRPDJMXSmXqM0CAwEAAQ==
-----END PUBLIC KEY-----
]],
rsa_private_key = [[
-----BEGIN RSA PRIVATE KEY-----
MIIBPAIBAAJBALClz96cDQ965ENYMfZzG+Acu25lpx2KNpAALBQ+catCA59us7+u
LY5rjQR6SOgZpCz5PJiKNAdRPDJMXSmXqM0CAwEAAQJBAJnwZa4BIACVf8aQXToA
JhKv90bFn1TG1bW38LHTmQs8EM9XCmghLWCje7d/NbUrUceotIOnjtv/xHTywGt2
NwECIQDhvMZDQ+ZRRbbwONcvO9G7h6hFgy0okiv6JciZccvtxQIhAMhUTAWgV1hQ
O2yWTRYRQZosEIsFB3kZfsLMeTKjk8dpAiEAslsZ92m9n3dKrJDsjFhiRR5ROOMF
Gior7xBNZ9e+vdUCIDsjf4nNqttcXB6TRFB2aapsxbl0k58xYpV5LXJAjfi5AiEA
vRaSauBfRCP3JgXHNgcDSW017/BtbwGiz8aITv6B0Fw=
-----END RSA PRIVATE KEY-----
]],
es256_public_key = [[
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERm/e/qZmgIyVSksHrHh1lzf9F6WT
oTKnyWLfLdz8SZiaLVaI1GW3GekLwVlbKZUkmqUnKfrNs2U9DuJ3jSyX8A==
-----END PUBLIC KEY-----
]],
es256_private_key = [[
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIA8Bv35dnS289gK/lBbp0bj3LzFuvFvZowNClcUW4E84oAoGCCqGSM49
AwEHoUQDQgAERm/e/qZmgIyVSksHrHh1lzf9F6WToTKnyWLfLdz8SZiaLVaI1GW3
GekLwVlbKZUkmqUnKfrNs2U9DuJ3jSyX8A==
-----END EC PRIVATE KEY-----
]],
es512_public_key = [[
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB85b81Iol6jVQwVuusRR4SYHUN6oP
KhLWWyobYydIJ/VAmmEHg5Wi/VcYpP3/qlatMhuQPKjC/j4lfLal716byRoAcEtS
+V4w6yT1pIszwAovp8u4PJpEoe3f9JosV3Wvmzauk+o0uaW/cFiarb81hQDTD/Go
xFWEYfqgS1kv+NpEJAA=
-----END PUBLIC KEY-----
]],
es512_private_key = [[
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIBEsogZYirhy12UPSwJx3ps/6j9fRHdIrqzUfz8uDz1xZ4KGm1uzyQ
oB355ifR/5IriiWzai1LZM+dyR1uS8wV2qKgBwYFK4EEACOhgYkDgYYABAHzlvzU
iiXqNVDBW66xFHhJgdQ3qg8qEtZbKhtjJ0gn9UCaYQeDlaL9Vxik/f+qVq0yG5A8
qML+PiV8tqXvXpvJGgBwS1L5XjDrJPWkizPACi+ny7g8mkSh7d/0mixXda+bNq6T
6jS5pb9wWJqtvzWFANMP8ajEVYRh+qBLWS/42kQkAA==
-----END EC PRIVATE KEY-----
]],
}
19 changes: 0 additions & 19 deletions spec/fixtures/rsa.lua

This file was deleted.

92 changes: 72 additions & 20 deletions spec/oauth/oidc_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ local _M = require('apicast.oauth.oidc')
local jwt_validators = require('resty.jwt-validators')
local jwt = require('resty.jwt')

local rsa = require('fixtures.rsa')
local certs = require('fixtures.certs')
local ngx_variable = require('apicast.policy.ngx_variable')

describe('OIDC', function()
Expand Down Expand Up @@ -43,19 +43,29 @@ describe('OIDC', function()
local oidc_config = {
issuer = 'https://example.com/auth/realms/apicast',
config = { id_token_signing_alg_values_supported = { 'RS256', 'HS256' } },
keys = { somekid = { pem = rsa.pub, alg = 'RS256' } },
keys = { somekid = { pem = certs.rsa_public_key, alg = 'RS256' } },
}
local es256_oidc_config = {
issuer = 'https://example.com/auth/realms/apicast',
config = { id_token_signing_alg_values_supported = { 'ES256'} },
keys = { somekid = { pem = certs.es256_public_key, alg = 'ES256' } },
}
local es512_oidc_config = {
issuer = 'https://example.com/auth/realms/apicast',
config = { id_token_signing_alg_values_supported = { 'ES512' } },
keys = { somekid = { pem = certs.es512_public_key, alg = 'ES512' } },
}
local oidc_config_no_alg = {
issuer = 'https://example.com/auth/realms/apicast',
config = { id_token_signing_alg_values_supported = { 'RS256', 'HS256' } },
keys = { somekid = { pem = rsa.pub } },
keys = { somekid = { pem = certs.rsa_public_key } },
}

before_each(function() jwt_validators.set_system_clock(function() return 0 end) end)

it('successfully verifies token', function()
it('successfully verifies token using RS256', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -74,9 +84,51 @@ describe('OIDC', function()
assert.equal(10, ttl)
end)

it('successfully verifies token using ES256', function()
local oidc = _M.new(es256_oidc_config)
local access_token = jwt:sign(certs.es256_private_key, {
header = { typ = 'JWT', alg = 'ES256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
aud = 'notused',
azp = 'ce3b2e5e',
sub = 'someone',
exp = ngx.now() + 10,
},
})

local credentials, ttl, _, err = oidc:transform_credentials({ access_token = access_token })

assert(credentials, err)

assert.same({ app_id = "ce3b2e5e" }, credentials)
assert.equal(10, ttl)
end)

it('successfully verifies token using ES512', function()
local oidc = _M.new(es512_oidc_config)
local access_token = jwt:sign(certs.es512_private_key, {
header = { typ = 'JWT', alg = 'ES512', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
aud = 'notused',
azp = 'ce3b2e5e',
sub = 'someone',
exp = ngx.now() + 10,
},
})

local credentials, ttl, _, err = oidc:transform_credentials({ access_token = access_token })

assert(credentials, err)

assert.same({ app_id = "ce3b2e5e" }, credentials)
assert.equal(10, ttl)
end)

it('caches verification', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -103,7 +155,7 @@ describe('OIDC', function()

it('verifies iss', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -124,7 +176,7 @@ describe('OIDC', function()
stub(ngx, 'now', now)

local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -142,7 +194,7 @@ describe('OIDC', function()

it('verifies iat', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -160,7 +212,7 @@ describe('OIDC', function()

it('verifies exp', function()
local oidc = _M.new(oidc_config, {})
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -184,7 +236,7 @@ describe('OIDC', function()

it('verifies alg', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'HS512' },
payload = { },
})
Expand All @@ -197,7 +249,7 @@ describe('OIDC', function()

it('validation fails when typ is invalid', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -217,7 +269,7 @@ describe('OIDC', function()

it('validation is successful when typ is included and is Bearer', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -236,7 +288,7 @@ describe('OIDC', function()

it('validation fails when jwk.alg does not match jwt.header.alg', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'HS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -256,7 +308,7 @@ describe('OIDC', function()

it('validation passes when jwk.alg matches jwt.header.alg', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -275,7 +327,7 @@ describe('OIDC', function()

it('validation passes when jwk.alg does not exist', function()
local oidc = _M.new(oidc_config_no_alg)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -294,7 +346,7 @@ describe('OIDC', function()

it('token was signed by a different key', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'otherkid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -312,7 +364,7 @@ describe('OIDC', function()

it('token was signed by a different issuer', function()
local oidc = _M.new(oidc_config)
local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = 'other_issuer',
Expand All @@ -334,7 +386,7 @@ describe('OIDC', function()
stub(ngx_variable, 'available_context', function(context) return context end)
end)

local access_token = jwt:sign(rsa.private, {
local access_token = jwt:sign(certs.rsa_private_key, {
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
payload = {
iss = oidc_config.issuer,
Expand All @@ -350,7 +402,7 @@ describe('OIDC', function()
local config = {
issuer = 'https://example.com/auth/realms/apicast',
config = { id_token_signing_alg_values_supported = { 'RS256' } },
keys = { somekid = { pem = rsa.pub, alg = 'RS256' } }}
keys = { somekid = { pem = certs.rsa_public_key, alg = 'RS256' } }}

for key,value in pairs(params) do
config[key] = value
Expand Down
4 changes: 2 additions & 2 deletions spec/policy/3scale_batcher/keys_helper_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local keys_helper = require 'apicast.policy.3scale_batcher.keys_helper'
local Usage = require 'apicast.usage'
local Transaction = require 'apicast.policy.3scale_batcher.transaction'
local JWT = require('resty.jwt')
local rsa = require('fixtures.rsa')
local certs = require('fixtures.certs')

local access_token = setmetatable({
header = { typ = 'JWT', alg = 'RS256', kid = 'somekid' },
Expand All @@ -12,7 +12,7 @@ local access_token = setmetatable({
aud = 'one',
exp = ngx.now() + 3600,
},
}, { __tostring = function(jwt) return JWT:sign(rsa.private, jwt) end })
}, { __tostring = function(jwt) return JWT:sign(certs.rsa_private_key, jwt) end })

describe('Keys Helper', function()
describe('.key_for_cached_auth', function()
Expand Down
Loading

0 comments on commit cd477ef

Please sign in to comment.