kubeclient 4.3.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of kubeclient might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f53d064748148a041be5853562e7789f8cc792735e4916c1859c63bf93f1f8bc
4
- data.tar.gz: 15a4ba1b298c4bfb3469765a92ad58e8dd806f470870bbadaaf81a0c7ac3dbf4
3
+ metadata.gz: 688623a12e9f3e68f6573b39d0d8e58ddee8848154d72670848d05096453a827
4
+ data.tar.gz: fa3432fed8c342a2686a9f0f1ba599f7e87fa85ac42aa410f39b61674754e6ca
5
5
  SHA512:
6
- metadata.gz: 39b85eead8370920f1dbf6f30720506de756894ae4d34b55a5e589b9ff1c8bce0a6e93c5517d4afbc7bb039b729e760250c7b4ead17b4fced76f52941b6bcf44
7
- data.tar.gz: 6c95743f7680fac757b90804050b81488d084fe083749890f069ca8ed1a97158c789d3a912fcccc72f64d089695a9fe0c2085b45dffa492773c82d2ae5b3626d
6
+ metadata.gz: c6543f0f8494b64d3d56342dab4dc7ff70eeeb97f6a18cfa2915885c56c5e9051e0cd8df6eba330129330b8ba3e3e4088838f8b06cc10093f40a280e52e870b9
7
+ data.tar.gz: 7df2f38d1b116a137446935c4c166236c7ce04027cd17bc038047bdfee817c4b4a734d6a5775dafc1e1d02ed1ad3ec15843d5fcb59d0ca62c459dfe0de1848cb
@@ -4,13 +4,24 @@ Notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
5
5
  Kubeclient release versioning follows [SemVer](https://semver.org/).
6
6
 
7
+ ## Unreleased
8
+
9
+ ## 4.4.0 — 2019-05-03
10
+
11
+ ### Added
12
+ - GCP configs with `user[auth-provider][name] == 'gcp'` will execute credential plugin (normally the `gcloud config config-helper` subcommand) when the config specifies it in `cmd-path`, `cmd-args` fields (similar to `exec` support). This code path works without `googleauth` gem. Otherwise, `GoogleApplicationDefaultCredentials` path will be tried as before. (#410)
13
+ - `AmazonEksCredentials` helper for obtaining a token to authenticate against Amazon EKS. This is not currently integrated in `Config`, you will need to invoke it yourself. You'll need some aws gems that Kubeclient _does not_ include. (#404, #406)
14
+
15
+ ### Changed
16
+ - OpenID Connect tokens which cannot be validaded because we cannot identify the key they were signed with will be considered expired and refreshed as usual. (#407)
17
+
7
18
  ## 4.3.0 — 2019-03-03
8
19
 
9
20
  ### Changed
10
- - `GoogleApplicationDefaultCredentials` will automatically be used in `fetch_user_auth_options` if the `user[auth-provider][name] == 'gcp'` in the provided context. Note that `user[exec]` is checked first in anticipation of this functionality being added to GCP sometime in the future. Kubeclient _does not_ import the required `googleauth` gem, so you will need to import it in your calling application. (#394)
21
+ - `GoogleApplicationDefaultCredentials` will now automatically be used by `Config` if the `user[auth-provider][name] == 'gcp'` in the provided context. Note that `user[exec]` is checked first in anticipation of this functionality being added to GCP sometime in the future. Kubeclient _does not_ include the required `googleauth` gem, so you will need to include it in your calling application. (#394)
11
22
 
12
23
  ### Added
13
- - OpenID Connect credentials will automatically be used if the `user[auth-provider][name] == 'oidc'` in the provided context. Note that `user[exec]` is checked first. Kubeclient _does not_ import the required `openid_connect` gem, so you will need to import it in your calling application. (#396)
24
+ - OpenID Connect credentials will automatically be used if the `user[auth-provider][name] == 'oidc'` in the provided context. Note that `user[exec]` is checked first. Kubeclient _does not_ include the required `openid_connect` gem, so you will need to include it in your calling application. (#396)
14
25
 
15
26
  - Support for `json_patch_#{entity}` and `merge_patch_#{entity}`. `patch_#{entity}` will continue to use strategic merge patch. (#390)
16
27
 
data/README.md CHANGED
@@ -287,6 +287,45 @@ Kubeclient::Client.new(
287
287
  ```
288
288
 
289
289
 
290
+ #### Amazon EKS Credentials
291
+
292
+ On Amazon EKS by default the authentication method is IAM. When running kubectl a temporary token is generated by shelling out to
293
+ the aws-iam-authenticator binary which is sent to authenticate the user.
294
+ See [aws-iam-authenticator](https://github.com/kubernetes-sigs/aws-iam-authenticator).
295
+ To replicate that functionality, the `Kubeclient::AmazonEksCredentials` class can accept a set of IAM credentials and
296
+ contains a helper method to generate the authentication token for you.
297
+
298
+ This requires a set of gems which are _not_ included in
299
+ `kubeclient` dependencies (`aws-sigv4`) so you should add them to your bundle.
300
+ You will also require either the `aws-sdk` v2 or `aws-sdk-core` v3 gems to generate the required `Aws:Credentials` object to pass to this method.
301
+
302
+ To obtain a token:
303
+
304
+ ```ruby
305
+ require 'aws-sdk-core'
306
+ # Use keys
307
+ credentials = Aws::Credentials.new(access_key, secret_key)
308
+ # Or a profile
309
+ credentials = Aws::SharedCredentials.new(profile_name: 'default').credentials
310
+
311
+ auth_options = {
312
+ bearer_token: Kubeclient::AmazonEksCredentials.token(credentials, eks_cluster_name)
313
+ }
314
+ client = Kubeclient::Client.new(
315
+ eks_cluster_https_endpoint, 'v1', auth_options: auth_options
316
+ )
317
+ ```
318
+
319
+ Note that this returns a token good for one minute. If your code requires authorization for longer than that, you should plan to
320
+ acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
321
+
322
+ #### Google GCP credential plugin
323
+
324
+ If kubeconfig file has `user: {auth-provider: {name: gcp, cmd-path: ..., cmd-args: ..., token-key: ...}}`, the command will be executed to obtain a token.
325
+ (Normally this would be a `gcloud config config-helper` command.)
326
+
327
+ Note that this returns an expiring token. If your code requires authorization for a long time, you should plan to acquire a new one, see [How to manually renew](#how-to-manually-renew-expired-credentials) section.
328
+
290
329
  #### Google's Application Default Credentials
291
330
 
292
331
  On Google Compute Engine, Google App Engine, or Google Cloud Functions, as well as `gcloud`-configured systems
@@ -296,7 +335,7 @@ kubeclient can use `googleauth` gem to authorize.
296
335
  This requires the [`googleauth` gem](https://github.com/google/google-auth-library-ruby) that is _not_ included in
297
336
  `kubeclient` dependencies so you should add it to your bundle.
298
337
 
299
- If you use `Config.context(...).auth_options` and the kubeconfig file has `user: {auth-provider: {name: gcp}}`, kubeclient will automatically try this (raising LoadError if you don't have `googleauth` in your bundle).
338
+ If you use `Config.context(...).auth_options` and the kubeconfig file has `user: {auth-provider: {name: gcp}}`, but does not contain `cmd-path` key, kubeclient will automatically try this (raising LoadError if you don't have `googleauth` in your bundle).
300
339
 
301
340
  Or you can obtain a token manually:
302
341
 
@@ -21,6 +21,7 @@ Edit `CHANGELOG.md` as necessary. Even if all included changes remembered to up
21
21
  Bump `lib/kubeclient/version.rb` manually, or by using:
22
22
  ```bash
23
23
  git checkout -b release-$RELEASE_VERSION
24
+ # Won't work with uncommitted changes, you have to commit the changelog first.
24
25
  gem bump --version $RELEASE_VERSION
25
26
  git show # View version bump change.
26
27
  ```
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'googleauth', '~> 0.5.1'
31
31
  spec.add_development_dependency('mocha', '~> 1.5')
32
32
  spec.add_development_dependency 'openid_connect', '~> 1.1'
33
+ spec.add_development_dependency 'jsonpath', '~> 1.0'
33
34
 
34
35
  spec.add_dependency 'rest-client', '~> 2.0'
35
36
  spec.add_dependency 'recursive-open-struct', '~> 1.0', '>= 1.0.4'
@@ -1,11 +1,12 @@
1
1
  require 'json'
2
2
  require 'rest-client'
3
3
 
4
+ require 'kubeclient/aws_eks_credentials'
4
5
  require 'kubeclient/common'
5
6
  require 'kubeclient/config'
6
7
  require 'kubeclient/entity_list'
7
- require 'kubeclient/google_application_default_credentials'
8
8
  require 'kubeclient/exec_credentials'
9
+ require 'kubeclient/gcp_auth_provider'
9
10
  require 'kubeclient/http_error'
10
11
  require 'kubeclient/missing_kind_compatibility'
11
12
  require 'kubeclient/oidc_auth_provider'
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kubeclient
4
+ # Get a bearer token to authenticate against aws eks.
5
+ class AmazonEksCredentials
6
+ class AmazonEksDependencyError < LoadError # rubocop:disable Lint/InheritException
7
+ end
8
+
9
+ class << self
10
+ def token(credentials, eks_cluster)
11
+ begin
12
+ require 'aws-sigv4'
13
+ require 'base64'
14
+ require 'cgi'
15
+ rescue LoadError => e
16
+ raise AmazonEksDependencyError,
17
+ 'Error requiring aws gems. Kubeclient itself does not include the following ' \
18
+ 'gems: [aws-sigv4]. To support auth-provider eks, you must ' \
19
+ "include it in your calling application. Failed with: #{e.message}"
20
+ end
21
+ # https://github.com/aws/aws-sdk-ruby/pull/1848
22
+ # Get a signer
23
+ # Note - sts only has ONE endpoint (not regional) so 'us-east-1' hardcoding should be OK
24
+ signer = Aws::Sigv4::Signer.new(
25
+ service: 'sts',
26
+ region: 'us-east-1',
27
+ credentials: credentials
28
+ )
29
+
30
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Sigv4/Signer.html#presign_url-instance_method
31
+ presigned_url_string = signer.presign_url(
32
+ http_method: 'GET',
33
+ url: 'https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15',
34
+ body: '',
35
+ credentials: credentials,
36
+ expires_in: 60,
37
+ headers: {
38
+ 'X-K8s-Aws-Id' => eks_cluster
39
+ }
40
+ )
41
+ kube_token = 'k8s-aws-v1.' + Base64.urlsafe_encode64(presigned_url_string.to_s).chomp('==')
42
+ kube_token
43
+ end
44
+ end
45
+ end
46
+ end
@@ -150,17 +150,10 @@ module Kubeclient
150
150
  if user.key?('token')
151
151
  options[:bearer_token] = user['token']
152
152
  elsif user.key?('exec')
153
- exec_opts = user['exec'].dup
154
- exec_opts['command'] = ext_command_path(exec_opts['command']) if exec_opts['command']
153
+ exec_opts = expand_command_option(user['exec'], 'command')
155
154
  options[:bearer_token] = Kubeclient::ExecCredentials.token(exec_opts)
156
155
  elsif user.key?('auth-provider')
157
- auth_provider = user['auth-provider']
158
- options[:bearer_token] = case auth_provider['name']
159
- when 'gcp'
160
- then Kubeclient::GoogleApplicationDefaultCredentials.token
161
- when 'oidc'
162
- then Kubeclient::OIDCAuthProvider.token(auth_provider['config'])
163
- end
156
+ options[:bearer_token] = fetch_token_from_provider(user['auth-provider'])
164
157
  else
165
158
  %w[username password].each do |attr|
166
159
  options[attr.to_sym] = user[attr] if user.key?(attr)
@@ -168,5 +161,22 @@ module Kubeclient
168
161
  end
169
162
  options
170
163
  end
164
+
165
+ def fetch_token_from_provider(auth_provider)
166
+ case auth_provider['name']
167
+ when 'gcp'
168
+ config = expand_command_option(auth_provider['config'], 'cmd-path')
169
+ Kubeclient::GCPAuthProvider.token(config)
170
+ when 'oidc'
171
+ Kubeclient::OIDCAuthProvider.token(auth_provider['config'])
172
+ end
173
+ end
174
+
175
+ def expand_command_option(config, key)
176
+ config = config.dup
177
+ config[key] = ext_command_path(config[key]) if config[key]
178
+
179
+ config
180
+ end
171
181
  end
172
182
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kubeclient/google_application_default_credentials'
4
+ require 'kubeclient/gcp_command_credentials'
5
+
6
+ module Kubeclient
7
+ # Handle different ways to get a bearer token for Google Cloud Platform.
8
+ class GCPAuthProvider
9
+ class << self
10
+ def token(config)
11
+ if config.key?('cmd-path')
12
+ Kubeclient::GCPCommandCredentials.token(config)
13
+ else
14
+ Kubeclient::GoogleApplicationDefaultCredentials.token
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kubeclient
4
+ # Generates a bearer token for Google Cloud Platform.
5
+ class GCPCommandCredentials
6
+ class << self
7
+ def token(config)
8
+ require 'open3'
9
+ require 'shellwords'
10
+ require 'json'
11
+ require 'jsonpath'
12
+
13
+ cmd = config['cmd-path']
14
+ args = config['cmd-args']
15
+ token_key = config['token-key']
16
+
17
+ out, err, st = Open3.capture3(cmd, *args.split(' '))
18
+
19
+ raise "exec command failed: #{err}" unless st.success?
20
+
21
+ extract_token(out, token_key)
22
+ end
23
+
24
+ private
25
+
26
+ def extract_token(output, token_key)
27
+ JsonPath.on(output, token_key.gsub(/^{|}$/, '')).first
28
+ end
29
+ end
30
+ end
31
+ end
@@ -21,9 +21,7 @@ module Kubeclient
21
21
  discovery = OpenIDConnect::Discovery::Provider::Config.discover! issuer_url
22
22
 
23
23
  if provider_config.key? 'id-token'
24
- id_token = OpenIDConnect::ResponseObject::IdToken.decode provider_config['id-token'],
25
- discovery.jwks
26
- return provider_config['id-token'] unless expired?(id_token)
24
+ return provider_config['id-token'] unless expired?(provider_config['id-token'], discovery)
27
25
  end
28
26
 
29
27
  client = OpenIDConnect::Client.new(
@@ -37,9 +35,17 @@ module Kubeclient
37
35
  client.access_token!.id_token
38
36
  end
39
37
 
40
- def expired?(id_token)
38
+ def expired?(id_token, discovery)
39
+ decoded_token = OpenIDConnect::ResponseObject::IdToken.decode(
40
+ id_token,
41
+ discovery.jwks
42
+ )
41
43
  # If token expired or expiring within 60 seconds
42
- Time.now.to_i + 60 > id_token.exp.to_i
44
+ Time.now.to_i + 60 > decoded_token.exp.to_i
45
+ rescue JSON::JWK::Set::KidNotFound
46
+ # Token cannot be verified: the kid it was signed with is not available for discovery
47
+ # Consider it expired and fetch a new one.
48
+ true
43
49
  end
44
50
  end
45
51
  end
@@ -1,4 +1,4 @@
1
1
  # Kubernetes REST-API Client
2
2
  module Kubeclient
3
- VERSION = '4.3.0'.freeze
3
+ VERSION = '4.4.0'.freeze
4
4
  end
@@ -0,0 +1,26 @@
1
+ apiVersion: v1
2
+ clusters:
3
+ - cluster:
4
+ server: https://localhost:8443
5
+ insecure-skip-tls-verify: true
6
+ name: localhost:8443
7
+ contexts:
8
+ - context:
9
+ cluster: localhost:8443
10
+ namespace: default
11
+ user: application-default-credentials
12
+ name: localhost/application-default-credentials
13
+ kind: Config
14
+ preferences: {}
15
+ users:
16
+ - name: application-default-credentials
17
+ user:
18
+ auth-provider:
19
+ config:
20
+ access-token: <fake_token>
21
+ cmd-args: config config-helper --format=json
22
+ cmd-path: /path/to/gcloud
23
+ expiry: 2019-04-09T19:26:18Z
24
+ expiry-key: '{.credential.token_expiry}'
25
+ token-key: '{.credential.access_token}'
26
+ name: gcp
@@ -146,6 +146,20 @@ class KubeclientConfigTest < MiniTest::Test
146
146
  assert_equal({ bearer_token: 'token1' }, context.auth_options)
147
147
  end
148
148
 
149
+ def test_gcp_command_auth
150
+ Kubeclient::GCPCommandCredentials.expects(:token)
151
+ .with('access-token' => '<fake_token>',
152
+ 'cmd-args' => 'config config-helper --format=json',
153
+ 'cmd-path' => '/path/to/gcloud',
154
+ 'expiry' => '2019-04-09 19:26:18 UTC',
155
+ 'expiry-key' => '{.credential.token_expiry}',
156
+ 'token-key' => '{.credential.access_token}')
157
+ .returns('token1')
158
+ .once
159
+ config = Kubeclient::Config.read(config_file('gcpcmdauth.kubeconfig'))
160
+ config.context(config.contexts.first)
161
+ end
162
+
149
163
  def test_oidc_auth_provider
150
164
  Kubeclient::OIDCAuthProvider.expects(:token)
151
165
  .with('client-id' => 'fake-client-id',
@@ -0,0 +1,27 @@
1
+ require_relative 'test_helper'
2
+ require 'open3'
3
+
4
+ # Unit tests for the GCPCommandCredentials token provider
5
+ class GCPCommandCredentialsTest < MiniTest::Test
6
+ def test_token
7
+ opts = { 'cmd-args' => 'config config-helper --format=json',
8
+ 'cmd-path' => '/path/to/gcloud',
9
+ 'expiry-key' => '{.credential.token_expiry}',
10
+ 'token-key' => '{.credential.access_token}' }
11
+
12
+ creds = JSON.dump(
13
+ 'credential' => {
14
+ 'access_token' => '9A3A941836F2458175BE18AA1971EBBF47949B07',
15
+ 'token_expiry' => '2019-04-12T15:02:51Z'
16
+ }
17
+ )
18
+
19
+ st = Minitest::Mock.new
20
+ st.expect(:success?, true)
21
+
22
+ Open3.stub(:capture3, [creds, nil, st]) do
23
+ assert_equal('9A3A941836F2458175BE18AA1971EBBF47949B07',
24
+ Kubeclient::GCPCommandCredentials.token(opts))
25
+ end
26
+ end
27
+ end
@@ -57,6 +57,25 @@ class OIDCAuthProviderTest < MiniTest::Test
57
57
  end
58
58
  end
59
59
 
60
+ def test_token_with_unknown_kid
61
+ OpenIDConnect::Discovery::Provider::Config.stub(:discover!, discovery_mock) do
62
+ OpenIDConnect::ResponseObject::IdToken.stub(
63
+ :decode, ->(_token, _jwks) { raise JSON::JWK::Set::KidNotFound }
64
+ ) do
65
+ OpenIDConnect::Client.stub(:new, openid_client_mock) do
66
+ retrieved_id_token = Kubeclient::OIDCAuthProvider.token(
67
+ 'client-id' => @client_id,
68
+ 'client-secret' => @client_secret,
69
+ 'id-token' => @id_token,
70
+ 'idp-issuer-url' => @idp_issuer_url,
71
+ 'refresh-token' => @refresh_token
72
+ )
73
+ assert_equal(@new_id_token, retrieved_id_token)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
60
79
  private
61
80
 
62
81
  def openid_client_mock
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kubeclient
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alissa Bonas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-03 00:00:00.000000000 Z
11
+ date: 2019-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
152
  version: '1.1'
153
+ - !ruby/object:Gem::Dependency
154
+ name: jsonpath
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.0'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: rest-client
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -216,10 +230,13 @@ files:
216
230
  - Rakefile
217
231
  - kubeclient.gemspec
218
232
  - lib/kubeclient.rb
233
+ - lib/kubeclient/aws_eks_credentials.rb
219
234
  - lib/kubeclient/common.rb
220
235
  - lib/kubeclient/config.rb
221
236
  - lib/kubeclient/entity_list.rb
222
237
  - lib/kubeclient/exec_credentials.rb
238
+ - lib/kubeclient/gcp_auth_provider.rb
239
+ - lib/kubeclient/gcp_command_credentials.rb
223
240
  - lib/kubeclient/google_application_default_credentials.rb
224
241
  - lib/kubeclient/http_error.rb
225
242
  - lib/kubeclient/missing_kind_compatibility.rb
@@ -236,6 +253,7 @@ files:
236
253
  - test/config/external-key.rsa
237
254
  - test/config/external.kubeconfig
238
255
  - test/config/gcpauth.kubeconfig
256
+ - test/config/gcpcmdauth.kubeconfig
239
257
  - test/config/nouser.kubeconfig
240
258
  - test/config/oidcauth.kubeconfig
241
259
  - test/config/timestamps.kubeconfig
@@ -304,6 +322,7 @@ files:
304
322
  - test/test_config.rb
305
323
  - test/test_endpoint.rb
306
324
  - test/test_exec_credentials.rb
325
+ - test/test_gcp_command_credentials.rb
307
326
  - test/test_google_application_default_credentials.rb
308
327
  - test/test_guestbook_go.rb
309
328
  - test/test_helper.rb
@@ -361,6 +380,7 @@ test_files:
361
380
  - test/config/external-key.rsa
362
381
  - test/config/external.kubeconfig
363
382
  - test/config/gcpauth.kubeconfig
383
+ - test/config/gcpcmdauth.kubeconfig
364
384
  - test/config/nouser.kubeconfig
365
385
  - test/config/oidcauth.kubeconfig
366
386
  - test/config/timestamps.kubeconfig
@@ -429,6 +449,7 @@ test_files:
429
449
  - test/test_config.rb
430
450
  - test/test_endpoint.rb
431
451
  - test/test_exec_credentials.rb
452
+ - test/test_gcp_command_credentials.rb
432
453
  - test/test_google_application_default_credentials.rb
433
454
  - test/test_guestbook_go.rb
434
455
  - test/test_helper.rb