Skip to content
Open
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
4 changes: 2 additions & 2 deletions lib/kubeclient/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ def fetch_token_from_provider(auth_provider)
case auth_provider['name']
when 'gcp'
config = expand_command_option(auth_provider['config'], 'cmd-path')
Kubeclient::GCPAuthProvider.token(config)
-> { Kubeclient::GCPAuthProvider.token(config) }
when 'oidc'
Kubeclient::OIDCAuthProvider.token(auth_provider['config'])
-> { Kubeclient::OIDCAuthProvider.token(auth_provider['config']) }
end
end

Expand Down
27 changes: 15 additions & 12 deletions lib/kubeclient/watch_stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,25 @@ def build_client
)
end

bearer_token = nil
if @http_options[:bearer_token_file]
bearer_token_file = @http_options[:bearer_token_file]
if (bearer_token = extract_bearer_token)
client = client.auth("Bearer #{bearer_token}")
end

client
end

def extract_bearer_token
if (bearer_token_file = @http_options[:bearer_token_file])
if File.file?(bearer_token_file) && File.readable?(bearer_token_file)
token = File.read(bearer_token_file).chomp
bearer_token = "Bearer #{token}"
File.read(bearer_token_file).chomp
end
elsif @http_options[:bearer_token]
bearer_token = "Bearer #{@http_options[:bearer_token]}"
end

if bearer_token
client = client.auth(bearer_token)
if @http_options[:bearer_token].respond_to?(:call)
@http_options[:bearer_token].call
else
@http_options[:bearer_token]
end
end

client
end

def using_proxy
Expand Down
58 changes: 42 additions & 16 deletions test/test_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -177,26 +177,17 @@ def test_user_exec_nopath
end

def test_gcp_default_auth
Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token1').once
parsed = load_yaml(config_file('gcpauth.kubeconfig'))
config = Kubeclient::Config.new(parsed, nil)
config.context(config.contexts.first)
end

# Each call to .context() obtains a new token, calling .auth_options doesn't change anything.
# NOTE: this is not a guarantee, may change, just testing current behavior.
def test_gcp_default_auth_renew
Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token1').once
parsed = load_yaml(config_file('gcpauth.kubeconfig'))
config = Kubeclient::Config.new(parsed, nil)
context = config.context(config.contexts.first)
assert_equal({ bearer_token: 'token1' }, context.auth_options)
assert_equal({ bearer_token: 'token1' }, context.auth_options)

assert_respond_to(context.auth_options[:bearer_token], :call)
assert_equal('token1', context.auth_options[:bearer_token].call)

Kubeclient::GoogleApplicationDefaultCredentials.expects(:token).returns('token2').once
context2 = config.context(config.contexts.first)
assert_equal({ bearer_token: 'token2' }, context2.auth_options)
assert_equal({ bearer_token: 'token1' }, context.auth_options)

assert_equal('token2', context.auth_options[:bearer_token].call)
end

def test_gcp_command_auth
Expand All @@ -213,7 +204,25 @@ def test_gcp_command_auth
.returns('token1')
.once
config = Kubeclient::Config.read(config_file('gcpcmdauth.kubeconfig'))
config.context(config.contexts.first)
context = config.context(config.contexts.first)

assert_respond_to(context.auth_options[:bearer_token], :call)
assert_equal('token1', context.auth_options[:bearer_token].call)

Kubeclient::GCPCommandCredentials
.expects(:token)
.with(
'access-token' => '<fake_token>',
'cmd-args' => 'config config-helper --format=json',
'cmd-path' => '/path/to/gcloud',
'expiry' => '2019-04-09 19:26:18 UTC',
'expiry-key' => '{.credential.token_expiry}',
'token-key' => '{.credential.access_token}'
)
.returns('token2')
.once

assert_equal('token2', context.auth_options[:bearer_token].call)
end

def test_oidc_auth_provider
Expand All @@ -230,7 +239,24 @@ def test_oidc_auth_provider
.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_respond_to(context.auth_options[:bearer_token], :call)
assert_equal('token1', context.auth_options[:bearer_token].call)

Kubeclient::OIDCAuthProvider
.expects(:token)
.with(
'client-id' => 'fake-client-id',
'client-secret' => 'fake-client-secret',
'id-token' => 'fake-id-token',
'idp-issuer-url' => 'https://accounts.google.com',
'refresh-token' => 'fake-refresh-token'
)
.returns('token2')
.once

assert_equal('token2', context.auth_options[:bearer_token].call)
end

def test_impersonate
Expand Down
32 changes: 32 additions & 0 deletions test/test_kubeclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,38 @@ def test_api_bearer_token_file_success
assert_equal(1, pods.size)
end

def test_api_bearer_token_file_refreshes
stub_core_api_list

file = File.join(File.dirname(__FILE__), 'valid_token_file')
client = Kubeclient::Client.new(
'http://localhost:8080/api/', 'v1',
auth_options: { bearer_token_file: file }
)

first_request = stub_request(:get, 'http://localhost:8080/api/v1/pods')
.with(headers: { Authorization: 'Bearer valid_token' })
.to_return(body: '{}', status: 200)

client.get_pods

assert_requested(first_request)

WebMock.reset!

File.open(file, 'w') { |f| f.puts('another_valid_token') }

second_request = stub_request(:get, 'http://localhost:8080/api/v1/pods')
.with(headers: { Authorization: 'Bearer another_valid_token' })
.to_return(body: '{}', status: 200)

client.get_pods

assert_requested(second_request)
ensure
File.open(file, 'w') { |f| f.puts('valid_token') }
end

def test_impersonate
pods_stub = stub_request(:get, 'http://localhost:8080/api/v1/pods')
.with(
Expand Down
36 changes: 32 additions & 4 deletions test/test_watch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ def test_watch_pod_api_bearer_token_file_success
def test_watch_pod_api_bearer_token_success
stub_core_api_list

file = Tempfile.new('token')
client = Kubeclient::Client.new(
'http://localhost:8080/api/', 'v1',
auth_options: { bearer_token: 'valid_token' }
Expand All @@ -155,9 +154,38 @@ def test_watch_pod_api_bearer_token_success
got = nil
client.watch_pods(as: :raw).each { |notice| got = notice }
assert_match(/\A{"type":"DELETED"/, got)
ensure
file.close
file.unlink # deletes the temp file
end

def test_watch_pod_api_callable_bearer_token
stub_core_api_list

token = 'valid_token'

client = Kubeclient::Client.new(
'http://localhost:8080/api/', 'v1',
auth_options: { bearer_token: -> { token } }
)

watcher = client.watch_pods(as: :raw)

stub_token = stub_request(:get, %r{/watch/pods})
.with(headers: { Authorization: 'Bearer valid_token' })
.to_return(body: open_test_file('watch_stream.json'), status: 200)

got = nil
watcher.each { |notice| got = notice }
assert_match(/\A{"type":"DELETED"/, got)
remove_request_stub(stub_token)

stub_request(:get, %r{/watch/pods})
.with(headers: { Authorization: 'Bearer rotated_token' })
.to_return(body: open_test_file('watch_stream.json'), status: 200)

token = 'rotated_token'

got = nil
watcher.each { |notice| got = notice }
assert_match(/\A{"type":"DELETED"/, got)
end

# Ensure that WatchStream respects a format that's not JSON
Expand Down