Skip to content
Closed
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,24 @@ You can read it as follows:
puts config.context.namespace
```

### Impersonation

Impersonation is supported when loading a kubectl config and via the Ruby API, for example:

```ruby
client = Kubeclient::Client.new(
context.api_endpoint, 'v1',
auth_options: {
as: "admin",
as_groups: ["system:masters"],
as_uid: "123", # optional
as_user_extra: {
"reason" => ["admin access"]
}
}
)
```

### Supported kubernetes versions

We try to support the last 3 minor versions, matching the [official support policy for Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/release/versioning.md#supported-releases-and-component-skew).
Expand Down
18 changes: 17 additions & 1 deletion lib/kubeclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ def initialize_client(
validate_bearer_token_file
bearer_token(File.read(@auth_options[:bearer_token_file]))
end

configure_impersonation_headers
end

def configure_faraday(&block)
Expand Down Expand Up @@ -675,7 +677,6 @@ def fetch_entities
end

def bearer_token(bearer_token)
@headers ||= {}
@headers[:Authorization] = "Bearer #{bearer_token}"
end

Expand All @@ -702,6 +703,21 @@ def validate_bearer_token_file
raise ArgumentError, msg unless File.readable?(@auth_options[:bearer_token_file])
end

# following https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation
def configure_impersonation_headers
return unless (auth_as = @auth_options[:as])
@headers[:'Impersonate-User'] = auth_as
if (auth_as_groups = @auth_options[:as_groups])
@headers[:'Impersonate-Group'] = Array(auth_as_groups).join
end
if (auth_as_uid = @auth_options[:as_uid])
@headers[:'Impersonate-Uid'] = auth_as_uid
end
@auth_options[:as_user_extra]&.each do |k, v|
@headers[:"Impersonate-Extra-#{k}"] = Array(v).join
end
end

def return_or_yield_to_watcher(watcher, &block)
return watcher unless block_given?

Expand Down
10 changes: 10 additions & 0 deletions lib/kubeclient/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,16 @@ def fetch_user_auth_options(user)
options[attr.to_sym] = user[attr] if user.key?(attr)
end
end

# TODO: allow setting Impersonate-Uid from here or comment on why it is not possible
[
['as', :as],
['as-groups', :as_groups],
['as-user-extra', :as_user_extra]
].each do |k, v|
options[v] = user[k] if user.key?(k)
end

options
end

Expand Down
22 changes: 22 additions & 0 deletions test/config/impersonate.kubeconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: v1
clusters:
- cluster:
server: https://localhost:8443
insecure-skip-tls-verify: true
name: localhost:8443
contexts:
- context:
cluster: localhost:8443
namespace: default
user: impersonate
name: localhost/impersonate
current-context: localhost/impersonate
kind: Config
preferences: {}
users:
- name: impersonate
user:
as: foo
as-groups: [bar, baz]
as-user-extra:
reason: [foo]
13 changes: 13 additions & 0 deletions test/test_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,19 @@ def test_oidc_auth_provider
config.context(config.contexts.first)
end

def test_impersonate
parsed = YAML.safe_load(File.read(config_file('impersonate.kubeconfig')))
config = Kubeclient::Config.new(parsed, nil)
assert_equal(
{
as: 'foo',
as_groups: ['bar', 'baz'],
as_user_extra: { 'reason' => ['foo'] }
},
config.context(config.contexts.first).auth_options
)
end

private

def check_context(context, ssl: true)
Expand Down
31 changes: 31 additions & 0 deletions test/test_kubeclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,37 @@ def test_api_bearer_token_file_success
assert_equal(1, pods.size)
end

def test_impersonate
stub_request(:get, 'http://localhost:8080/api/v1/pods')
.with(
headers: {
Authorization: 'Bearer valid_token',
'Impersonate-Extra-Reason': 'baz',
'Impersonate-Group': 'bar',
'Impersonate-User': 'foo',
'Impersonate-Uid': '123'
}
)
.to_return(body: { items: [] }.to_json)
stub_request(:get, %r{/api/v1$})
.with(headers: { Authorization: 'Bearer valid_token' })
.to_return(body: open_test_file('core_api_resource_list.json'))

client = Kubeclient::Client.new(
'http://localhost:8080/api/',
'v1',
auth_options: {
bearer_token: 'valid_token',
as: 'foo',
as_groups: ['bar'],
as_user_extra: { 'reason' => ['baz'] },
as_uid: '123'
}
)

client.get_pods
end

def test_proxy_url
stub_core_api_list

Expand Down