Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions lib/kubeclient/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 18 additions & 6 deletions lib/kubeclient/oidc_auth_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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
Expand Down
13 changes: 11 additions & 2 deletions test/test_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 22 additions & 3 deletions test/test_oidc_auth_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down