diff --git a/lib/kubeclient/config.rb b/lib/kubeclient/config.rb index 339f4ab0..d4c28642 100644 --- a/lib/kubeclient/config.rb +++ b/lib/kubeclient/config.rb @@ -155,12 +155,14 @@ def fetch_user_auth_options(user) options[:bearer_token] = Kubeclient::ExecCredentials.token(exec_opts) elsif user.key?('auth-provider') auth_provider = user['auth-provider'] - options[:bearer_token] = case auth_provider['name'] - when 'gcp' - then Kubeclient::GoogleApplicationDefaultCredentials.token - when 'oidc' - then Kubeclient::OIDCAuthProvider.token(auth_provider['config']) - end + case auth_provider['name'] + when 'gcp' + options[:bearer_token] = Kubeclient::GoogleApplicationDefaultCredentials.token + when 'oidc' + access_token = Kubeclient::OIDCAuthProvider.token(auth_provider['config']) + options[:bearer_token] = access_token.id_token + options[:refresh_token] = access_token.refresh_token + end else %w[username password].each do |attr| options[attr.to_sym] = user[attr] if user.key?(attr) diff --git a/lib/kubeclient/oidc_auth_provider.rb b/lib/kubeclient/oidc_auth_provider.rb index 08605525..a571c76b 100644 --- a/lib/kubeclient/oidc_auth_provider.rb +++ b/lib/kubeclient/oidc_auth_provider.rb @@ -6,6 +6,8 @@ class OIDCAuthProvider class OpenIDConnectDependencyError < LoadError # rubocop:disable Lint/InheritException end + AlreadyValidAccessToken = Struct.new(:id_token, :refresh_token) + class << self def token(provider_config) begin @@ -21,9 +23,11 @@ def token(provider_config) discovery = OpenIDConnect::Discovery::Provider::Config.discover! issuer_url if provider_config.key? 'id-token' - id_token = OpenIDConnect::ResponseObject::IdToken.decode provider_config['id-token'], - discovery.jwks - return provider_config['id-token'] unless expired?(id_token) + access_token = AlreadyValidAccessToken.new( + provider_config['id-token'], + provider_config['refresh-token'] + ) + return access_token unless expired?(access_token.id_token, discovery) end client = OpenIDConnect::Client.new( @@ -34,12 +38,20 @@ def token(provider_config) userinfo_endpoint: discovery.userinfo_endpoint ) client.refresh_token = provider_config['refresh-token'] - client.access_token!.id_token + client.access_token! end - def expired?(id_token) + def expired?(id_token, discovery) + decoded_token = OpenIDConnect::ResponseObject::IdToken.decode( + id_token, + discovery.jwks + ) # If token expired or expiring within 60 seconds - Time.now.to_i + 60 > id_token.exp.to_i + Time.now.to_i + 60 > decoded_token.exp.to_i + rescue JSON::JWK::Set::KidNotFound + # Token cannot be verified: the kid it was signed with is not available for discovery + # Consider it expired and fetch a new one. + true end end end diff --git a/test/test_config.rb b/test/test_config.rb index b769e3f3..feb9ce11 100644 --- a/test/test_config.rb +++ b/test/test_config.rb @@ -153,11 +153,20 @@ def test_oidc_auth_provider 'id-token' => 'fake-id-token', 'idp-issuer-url' => 'https://accounts.google.com', 'refresh-token' => 'fake-refresh-token') - .returns('token1') + .returns( + Kubeclient::OIDCAuthProvider::AlreadyValidAccessToken.new( + 'token1', + 'new-refresh-token' + ) + ) .once parsed = YAML.safe_load(File.read(config_file('oidcauth.kubeconfig'))) config = Kubeclient::Config.new(parsed, nil) - config.context(config.contexts.first) + context = config.context(config.contexts.first) + assert_equal( + { bearer_token: 'token1', refresh_token: 'new-refresh-token' }, + context.auth_options + ) end private diff --git a/test/test_oidc_auth_provider.rb b/test/test_oidc_auth_provider.rb index 9f16ff67..ad42348e 100644 --- a/test/test_oidc_auth_provider.rb +++ b/test/test_oidc_auth_provider.rb @@ -21,7 +21,7 @@ def test_expired_token 'id-token' => @id_token, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token - ) + ).id_token assert_equal(@new_id_token, retrieved_id_token) end end @@ -37,7 +37,7 @@ def test_valid_token 'id-token' => @id_token, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token - ) + ).id_token assert_equal(@id_token, retrieved_id_token) end end @@ -51,12 +51,31 @@ def test_missing_id_token 'client-secret' => @client_secret, 'idp-issuer-url' => @idp_issuer_url, 'refresh-token' => @refresh_token - ) + ).id_token assert_equal(@new_id_token, retrieved_id_token) end end end + def test_token_with_unknown_kid + OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do + OpenIDConnect::ResponseObject::IdToken.stub( + :decode, ->(_token, _jwks) { raise JSON::JWK::Set::KidNotFound } + ) do + OpenIDConnect::Client.stub(:new, openid_client_mock) do + retrieved_id_token = Kubeclient::OIDCAuthProvider.token( + 'client-id' => @client_id, + 'client-secret' => @client_secret, + 'id-token' => @id_token, + 'idp-issuer-url' => @idp_issuer_url, + 'refresh-token' => @refresh_token + ).id_token + assert_equal(@new_id_token, retrieved_id_token) + end + end + end + end + private def openid_client_mock