-
Notifications
You must be signed in to change notification settings - Fork 166
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #363 from motymichaely/feature/exec-credential
Adds support for exec credential plugin
- Loading branch information
Showing
6 changed files
with
250 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# frozen_string_literal: true | ||
|
||
module Kubeclient | ||
# An exec-based client auth provide | ||
# https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuration | ||
# Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go | ||
class ExecCredentials | ||
class << self | ||
def token(opts) | ||
require 'open3' | ||
require 'json' | ||
|
||
raise ArgumentError, 'exec options are required' if opts.nil? | ||
|
||
cmd = opts['command'] | ||
args = opts['args'] | ||
env = map_env(opts['env']) | ||
|
||
# Validate exec options | ||
validate_opts(opts) | ||
|
||
out, err, st = Open3.capture3(env, cmd, *args) | ||
|
||
raise "exec command failed: #{err}" unless st.success? | ||
|
||
creds = JSON.parse(out) | ||
validate_credentials(opts, creds) | ||
creds['status']['token'] | ||
end | ||
|
||
private | ||
|
||
def validate_opts(opts) | ||
raise KeyError, 'exec command is required' unless opts['command'] | ||
end | ||
|
||
def validate_credentials(opts, creds) | ||
# out should have ExecCredential structure | ||
raise 'invalid credentials' if creds.nil? | ||
|
||
# Verify apiVersion? | ||
api_version = opts['apiVersion'] | ||
if api_version && api_version != creds['apiVersion'] | ||
raise "exec plugin is configured to use API version #{api_version}, " \ | ||
"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? | ||
end | ||
|
||
# Transform name/value pairs to hash | ||
def map_env(env) | ||
return {} unless env | ||
|
||
Hash[env.map { |e| [e['name'], e['value']] }] | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
apiVersion: v1 | ||
clusters: | ||
- cluster: | ||
server: https://localhost:8443 | ||
insecure-skip-tls-verify: true | ||
name: localhost:8443 | ||
contexts: | ||
- context: | ||
cluster: localhost:8443 | ||
namespace: default | ||
user: system:admin:exec | ||
name: localhost/system:admin:exec | ||
current-context: localhost/system:admin:exec | ||
kind: Config | ||
preferences: {} | ||
users: | ||
- name: system:admin:exec | ||
user: | ||
exec: | ||
# Command to execute. Required. | ||
command: "example-exec-plugin" | ||
|
||
# API version to use when decoding the ExecCredentials resource. Required. | ||
# | ||
# The API version returned by the plugin MUST match the version listed here. | ||
# | ||
# To integrate with tools that support multiple versions (such as client.authentication.k8s.io/v1alpha1), | ||
# set an environment variable or pass an argument to the tool that indicates which version the exec plugin expects. | ||
apiVersion: "client.authentication.k8s.io/v1beta1" | ||
|
||
# Environment variables to set when executing the plugin. Optional. | ||
env: | ||
- name: "FOO" | ||
value: "bar" | ||
|
||
# Arguments to pass when executing the plugin. Optional. | ||
args: | ||
- "arg1" | ||
- "arg2" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
require_relative 'test_helper' | ||
require 'open3' | ||
|
||
# Unit tests for the ExecCredentials token 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) | ||
end | ||
assert_equal(expected_msg, exception.message) | ||
end | ||
|
||
def test_exec_command_missing | ||
expected_msg = | ||
'exec command is required' | ||
exception = assert_raises(KeyError) do | ||
Kubeclient::ExecCredentials.token({}) | ||
end | ||
assert_equal(expected_msg, exception.message) | ||
end | ||
|
||
def test_exec_command_failure | ||
err = 'Error' | ||
expected_msg = | ||
"exec command failed: #{err}" | ||
|
||
st = Minitest::Mock.new | ||
st.expect(:success?, false) | ||
|
||
opts = { 'command' => 'dummy' } | ||
|
||
Open3.stub(:capture3, [nil, err, st]) do | ||
exception = assert_raises(RuntimeError) do | ||
Kubeclient::ExecCredentials.token(opts) | ||
end | ||
assert_equal(expected_msg, exception.message) | ||
end | ||
end | ||
|
||
def test_token | ||
opts = { 'command' => 'dummy' } | ||
|
||
creds = JSON.dump( | ||
'apiVersion': 'client.authentication.k8s.io/v1alpha1', | ||
'status': { | ||
'token': '0123456789ABCDEF0123456789ABCDEF' | ||
} | ||
) | ||
|
||
st = Minitest::Mock.new | ||
st.expect(:success?, true) | ||
|
||
Open3.stub(:capture3, [creds, nil, st]) do | ||
assert_equal('0123456789ABCDEF0123456789ABCDEF', Kubeclient::ExecCredentials.token(opts)) | ||
end | ||
end | ||
|
||
def test_status_missing | ||
opts = { 'command' => 'dummy' } | ||
|
||
creds = JSON.dump('apiVersion': 'client.authentication.k8s.io/v1alpha1') | ||
|
||
st = Minitest::Mock.new | ||
st.expect(:success?, true) | ||
|
||
expected_msg = 'exec plugin didn\'t return a status field' | ||
|
||
Open3.stub(:capture3, [creds, nil, st]) do | ||
exception = assert_raises(RuntimeError) do | ||
Kubeclient::ExecCredentials.token(opts) | ||
end | ||
assert_equal(expected_msg, exception.message) | ||
end | ||
end | ||
|
||
def test_token_missing | ||
opts = { 'command' => 'dummy' } | ||
|
||
creds = JSON.dump( | ||
'apiVersion': 'client.authentication.k8s.io/v1alpha1', | ||
'status': {} | ||
) | ||
|
||
st = Minitest::Mock.new | ||
st.expect(:success?, true) | ||
|
||
expected_msg = 'exec plugin didn\'t return a token' | ||
|
||
Open3.stub(:capture3, [creds, nil, st]) do | ||
exception = assert_raises(RuntimeError) do | ||
Kubeclient::ExecCredentials.token(opts) | ||
end | ||
assert_equal(expected_msg, exception.message) | ||
end | ||
end | ||
|
||
def test_api_version_mismatch | ||
api_version = 'client.authentication.k8s.io/v1alpha1' | ||
expected_version = 'client.authentication.k8s.io/v1beta1' | ||
|
||
opts = { | ||
'command' => 'dummy', | ||
'apiVersion' => expected_version | ||
} | ||
|
||
creds = JSON.dump( | ||
'apiVersion': api_version | ||
) | ||
|
||
st = Minitest::Mock.new | ||
st.expect(:success?, true) | ||
|
||
expected_msg = "exec plugin is configured to use API version #{expected_version}," \ | ||
" plugin returned version #{api_version}" | ||
|
||
Open3.stub(:capture3, [creds, nil, st]) do | ||
exception = assert_raises(RuntimeError) do | ||
Kubeclient::ExecCredentials.token(opts) | ||
end | ||
assert_equal(expected_msg, exception.message) | ||
end | ||
end | ||
end |