Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Releasing 4.9.0 #454

Merged
merged 1 commit into from
Aug 3, 2020
Merged
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: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
Kubeclient release versioning follows [SemVer](https://semver.org/).

## 4.9.0 - 2020-08-03
### Added
- Support for `user: exec` credential plugins using TLS client auth (#453)

## 4.8.0 — 2020-07-03

### Added
Expand Down
14 changes: 11 additions & 3 deletions lib/kubeclient/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ def contexts
def context(context_name = nil)
cluster, user, namespace = fetch_context(context_name || @kcfg['current-context'])

if user.key?('exec')
exec_opts = expand_command_option(user['exec'], 'command')
user['exec_result'] = ExecCredentials.run(exec_opts)
end

ca_cert_data = fetch_cluster_ca_data(cluster)
client_cert_data = fetch_user_cert_data(user)
client_key_data = fetch_user_key_data(user)
Expand Down Expand Up @@ -134,6 +139,8 @@ def fetch_user_cert_data(user)
File.read(ext_file_path(user['client-certificate']))
elsif user.key?('client-certificate-data')
Base64.decode64(user['client-certificate-data'])
elsif user.key?('exec_result') && user['exec_result'].key?('clientCertificateData')
user['exec_result']['clientCertificateData']
end
end

Expand All @@ -142,16 +149,17 @@ def fetch_user_key_data(user)
File.read(ext_file_path(user['client-key']))
elsif user.key?('client-key-data')
Base64.decode64(user['client-key-data'])
elsif user.key?('exec_result') && user['exec_result'].key?('clientKeyData')
user['exec_result']['clientKeyData']
end
end

def fetch_user_auth_options(user)
options = {}
if user.key?('token')
options[:bearer_token] = user['token']
elsif user.key?('exec')
exec_opts = expand_command_option(user['exec'], 'command')
options[:bearer_token] = Kubeclient::ExecCredentials.token(exec_opts)
elsif user.key?('exec_result') && user['exec_result'].key?('token')
options[:bearer_token] = user['exec_result']['token']
elsif user.key?('auth-provider')
options[:bearer_token] = fetch_token_from_provider(user['auth-provider'])
else
Expand Down
37 changes: 33 additions & 4 deletions lib/kubeclient/exec_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module Kubeclient
# Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go
class ExecCredentials
class << self
def token(opts)
def run(opts)
require 'open3'
require 'json'

Expand All @@ -25,7 +25,7 @@ def token(opts)

creds = JSON.parse(out)
validate_credentials(opts, creds)
creds['status']['token']
creds['status']
end

private
Expand All @@ -34,6 +34,36 @@ def validate_opts(opts)
raise KeyError, 'exec command is required' unless opts['command']
end

def validate_client_credentials_status(status)
has_client_cert_data = status.key?('clientCertificateData')
has_client_key_data = status.key?('clientKeyData')

if has_client_cert_data && !has_client_key_data
raise 'exec plugin didn\'t return client key data'
end

if !has_client_cert_data && has_client_key_data
raise 'exec plugin didn\'t return client certificate data'
end

has_client_cert_data && has_client_key_data
end

def validate_credentials_status(status)
raise 'exec plugin didn\'t return a status field' if status.nil?

has_client_credentials = validate_client_credentials_status(status)
has_token = status.key?('token')

if has_client_credentials && has_token
raise 'exec plugin returned both token and client data'
end

return if has_client_credentials || has_token

raise 'exec plugin didn\'t return a token or client data' unless has_token
end

def validate_credentials(opts, creds)
# out should have ExecCredential structure
raise 'invalid credentials' if creds.nil?
Expand All @@ -45,8 +75,7 @@ def validate_credentials(opts, creds)
"plugin returned version #{creds['apiVersion']}"
end

raise 'exec plugin didn\'t return a status field' if creds['status'].nil?
raise 'exec plugin didn\'t return a token' if creds['status']['token'].nil?
validate_credentials_status(creds['status'])
end

# Transform name/value pairs to hash
Expand Down
2 changes: 1 addition & 1 deletion lib/kubeclient/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Kubernetes REST-API Client
module Kubeclient
VERSION = '4.8.0'.freeze
VERSION = '4.9.0'.freeze
end
138 changes: 119 additions & 19 deletions test/test_exec_credentials.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
require_relative 'test_helper'
require 'open3'

# Unit tests for the ExecCredentials token provider
# Unit tests for the ExecCredentials provider
class ExecCredentialsTest < MiniTest::Test
def test_exec_opts_missing
expected_msg =
'exec options are required'
exception = assert_raises(ArgumentError) do
Kubeclient::ExecCredentials.token(nil)
Kubeclient::ExecCredentials.run(nil)
end
assert_equal(expected_msg, exception.message)
end
Expand All @@ -16,7 +16,7 @@ def test_exec_command_missing
expected_msg =
'exec command is required'
exception = assert_raises(KeyError) do
Kubeclient::ExecCredentials.token({})
Kubeclient::ExecCredentials.run({})
end
assert_equal(expected_msg, exception.message)
end
Expand All @@ -33,34 +33,134 @@ def test_exec_command_failure

Open3.stub(:capture3, [nil, err, st]) do
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.token(opts)
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
end

def test_token
def test_run_with_token_credentials
opts = { 'command' => 'dummy' }

credentials = {
'token' => '0123456789ABCDEF0123456789ABCDEF'
}

creds = JSON.dump(
'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
'status' => credentials
)

st = Minitest::Mock.new
st.expect(:success?, true)

Open3.stub(:capture3, [creds, nil, st]) do
assert_equal(credentials, Kubeclient::ExecCredentials.run(opts))
end
end

def test_run_with_client_credentials
opts = { 'command' => 'dummy' }

credentials = {
'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF',
'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF'
}

creds = JSON.dump(
'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
'status' => credentials
)

st = Minitest::Mock.new
st.expect(:success?, true)

Open3.stub(:capture3, [creds, nil, st]) do
assert_equal(credentials, Kubeclient::ExecCredentials.run(opts))
end
end

def test_run_with_missing_client_certificate_data
opts = { 'command' => 'dummy' }

credentials = {
'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF'
}

creds = JSON.dump(
'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
'status' => credentials
)

st = Minitest::Mock.new
st.expect(:success?, true)

expected_msg = 'exec plugin didn\'t return client certificate data'

Open3.stub(:capture3, [creds, nil, st]) do
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
end

def test_run_with_missing_client_key_data
opts = { 'command' => 'dummy' }

credentials = {
'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF'
}

creds = JSON.dump(
'apiVersion': 'client.authentication.k8s.io/v1alpha1',
'status': {
'token': '0123456789ABCDEF0123456789ABCDEF'
}
'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
'status' => credentials
)

st = Minitest::Mock.new
st.expect(:success?, true)

expected_msg = 'exec plugin didn\'t return client key data'

Open3.stub(:capture3, [creds, nil, st]) do
assert_equal('0123456789ABCDEF0123456789ABCDEF', Kubeclient::ExecCredentials.token(opts))
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
end

def test_run_with_client_data_and_token
opts = { 'command' => 'dummy' }

credentials = {
'clientCertificateData' => '0123456789ABCDEF0123456789ABCDEF',
'clientKeyData' => '0123456789ABCDEF0123456789ABCDEF',
'token' => '0123456789ABCDEF0123456789ABCDEF'
}

creds = JSON.dump(
'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
'status' => credentials
)

st = Minitest::Mock.new
st.expect(:success?, true)

expected_msg = 'exec plugin returned both token and client data'

Open3.stub(:capture3, [creds, nil, st]) do
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
end

def test_status_missing
opts = { 'command' => 'dummy' }

creds = JSON.dump('apiVersion': 'client.authentication.k8s.io/v1alpha1')
creds = JSON.dump('apiVersion' => 'client.authentication.k8s.io/v1alpha1')

st = Minitest::Mock.new
st.expect(:success?, true)
Expand All @@ -69,28 +169,28 @@ def test_status_missing

Open3.stub(:capture3, [creds, nil, st]) do
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.token(opts)
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
end

def test_token_missing
def test_credentials_missing
opts = { 'command' => 'dummy' }

creds = JSON.dump(
'apiVersion': 'client.authentication.k8s.io/v1alpha1',
'status': {}
'apiVersion' => 'client.authentication.k8s.io/v1alpha1',
'status' => {}
)

st = Minitest::Mock.new
st.expect(:success?, true)

expected_msg = 'exec plugin didn\'t return a token'
expected_msg = 'exec plugin didn\'t return a token or client data'

Open3.stub(:capture3, [creds, nil, st]) do
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.token(opts)
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
Expand All @@ -106,7 +206,7 @@ def test_api_version_mismatch
}

creds = JSON.dump(
'apiVersion': api_version
'apiVersion' => api_version
)

st = Minitest::Mock.new
Expand All @@ -117,7 +217,7 @@ def test_api_version_mismatch

Open3.stub(:capture3, [creds, nil, st]) do
exception = assert_raises(RuntimeError) do
Kubeclient::ExecCredentials.token(opts)
Kubeclient::ExecCredentials.run(opts)
end
assert_equal(expected_msg, exception.message)
end
Expand Down