From 6d6860ac4965e528a7f858c8b970964cbbaba0aa Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Fri, 6 Mar 2020 16:06:06 +0100 Subject: [PATCH] Add support for JWKS based OAuth Token validation. Keycloak only enables the introspection API when using a 'confidential' client. This means that for user initiated OAuth2 tokens the user would need to know the secrets for a client (ie the confidential piece). With Keycloak to use a 'public' client, where the user doesn't need to know a client secret, you need to use a JWKS to validate the JWT tokens instead of the introspection endpoint. OIDCOAuthVerifyJwksUri tells mod_auth_openidc where to find that certificate. You also need to *remove* OIDCOAuthIntrospectionEndpoint as this conflicts with OIDCOAuthVerifyJwksUri Change-Id: I95220db87f61ca19e50c64b4f799d3973b256858 --- manifests/federation/openidc.pp | 36 +++++++++++++++++-- .../keystone_federation_openidc_spec.rb | 20 +++++++++-- templates/openidc.conf.erb | 4 +++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/manifests/federation/openidc.pp b/manifests/federation/openidc.pp index 80558280c..b4b4dfbd8 100644 --- a/manifests/federation/openidc.pp +++ b/manifests/federation/openidc.pp @@ -61,9 +61,23 @@ # # [*openidc_enable_oauth*] # (Optional) Set to true to enable oauthsupport. +# Defaults to false. # # [*openidc_introspection_endpoint*] -# (Required if oauth is enabled) Oauth introspection endpoint url. +# (Required if oauth is enabled and configured for introspection) +# OAuth introspection endpoint url. +# Defaults to undef. +# +# [*openidc_verify_jwks_uri*] +# (Required if oauth is enabled and configured for JWKS based validation) +# The JWKS URL on which the Identity Provider +# publishes the keys used to sign its JWT access tokens. +# Defaults to undef. +# +# [*openidc_verify_method*] +# (Optional) The method used to verify OAuth tokens. +# Must be one of introspection or jwks +# Defaults to introspection # # [*memcached_servers*] # (Optional) A list of memcache servers. Defaults to undef. @@ -80,6 +94,7 @@ # [*remote_id_attribute*] # (Optional) Value to be used to obtain the entity ID of the Identity # Provider from the environment. +# Defaults to undef. # # [*template_order*] # This number indicates the order for the concat::fragment that will apply @@ -112,6 +127,8 @@ class keystone::federation::openidc ( $openidc_cache_clean_interval = undef, $openidc_enable_oauth = false, $openidc_introspection_endpoint = undef, + $openidc_verify_jwks_uri = undef, + $openidc_verify_method = 'introspection', $memcached_servers = undef, $redis_server = undef, $redis_password = undef, @@ -124,8 +141,21 @@ class keystone::federation::openidc ( include keystone::deps include keystone::params - if $openidc_enable_oauth and !$openidc_introspection_endpoint { - fail('You must set openidc_introspection_endpoint when enabling oauth support') + if !($openidc_verify_method in ['introspection', 'jwks']) { + fail('Unsupported token verification method.' + + ' Must be one of "introspection" or "jwks"') + } + + if ($openidc_verify_method == 'introspection') { + if $openidc_enable_oauth and !$openidc_introspection_endpoint { + fail('You must set openidc_introspection_endpoint when enabling oauth support' + + ' and introspection.') + } + } elsif ($openidc_verify_method == 'jwks') { + if $openidc_enable_oauth and !$openidc_verify_jwks_uri { + fail('You must set openidc_verify_jwks_uri when enabling oauth support' + + ' and local signature verification using a JWKS URL') + } } $memcached_servers_real = join(any2array($memcached_servers), ' ') diff --git a/spec/classes/keystone_federation_openidc_spec.rb b/spec/classes/keystone_federation_openidc_spec.rb index c9d5e043f..f8392c2d1 100644 --- a/spec/classes/keystone_federation_openidc_spec.rb +++ b/spec/classes/keystone_federation_openidc_spec.rb @@ -94,7 +94,7 @@ describe 'keystone::federation::openidc' do end end - context 'with oauth enabled' do + context 'with oauth and introspection enabled' do before do params.merge!({ :openidc_enable_oauth => true, @@ -102,7 +102,7 @@ describe 'keystone::federation::openidc' do }) end - it 'should contain oauth config' do + it 'should contain oauth and introspection config' do content = get_param('concat::fragment', 'configure_openidc_keystone', 'content') expect(content).to match('OIDCOAuthClientID "openid_client_id"') expect(content).to match('OIDCOAuthClientSecret "openid_client_secret"') @@ -111,6 +111,22 @@ describe 'keystone::federation::openidc' do end end + context 'with oauth and jwks enabled' do + before do + params.merge!({ + :openidc_enable_oauth => true, + :openidc_verify_method => 'jwks', + :openidc_verify_jwks_uri => 'http://example.com', + }) + end + + it 'should contain oauth and jwks config' do + content = get_param('concat::fragment', 'configure_openidc_keystone', 'content') + expect(content).to match('OIDCOAuthVerifyJwksUri "http://example.com"') + expect(content).to match('/v3/OS-FEDERATION/identity_providers/myidp/protocols/openid/auth') + end + end + context 'with remote id attribute' do before do params.merge!({ diff --git a/templates/openidc.conf.erb b/templates/openidc.conf.erb index a0d344323..51ef2dd52 100644 --- a/templates/openidc.conf.erb +++ b/templates/openidc.conf.erb @@ -48,9 +48,13 @@ <%- if scope['::keystone::federation::openidc::openidc_enable_oauth'] -%> + <%- if scope['keystone::federation::openidc::openidc_verify_method'] == 'introspection' -%> OIDCOAuthClientID "<%= scope['keystone::federation::openidc::openidc_client_id']-%>" OIDCOAuthClientSecret "<%= scope['keystone::federation::openidc::openidc_client_secret']-%>" OIDCOAuthIntrospectionEndpoint "<%= scope['keystone::federation::openidc::openidc_introspection_endpoint']-%>" + <%- elsif scope['keystone::federation::openidc::openidc_verify_method'] == 'jwks' -%> + OIDCOAuthVerifyJwksUri "<%= scope['keystone::federation::openidc::openidc_verify_jwks_uri']-%>" + <%- end -%> /protocols/openid/auth"> AuthType oauth20