kubeclient 0.3.0 → 4.9.2
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 +5 -5
- data/.github/workflows/actions.yml +35 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +29 -0
- data/CHANGELOG.md +208 -0
- data/Gemfile +3 -0
- data/README.md +706 -57
- data/RELEASING.md +69 -0
- data/Rakefile +3 -5
- data/kubeclient.gemspec +19 -11
- data/lib/kubeclient/aws_eks_credentials.rb +46 -0
- data/lib/kubeclient/common.rb +597 -161
- data/lib/kubeclient/config.rb +195 -0
- data/lib/kubeclient/entity_list.rb +7 -2
- data/lib/kubeclient/exec_credentials.rb +89 -0
- data/lib/kubeclient/gcp_auth_provider.rb +19 -0
- data/lib/kubeclient/gcp_command_credentials.rb +31 -0
- data/lib/kubeclient/google_application_default_credentials.rb +31 -0
- data/lib/kubeclient/http_error.rb +25 -0
- data/lib/kubeclient/missing_kind_compatibility.rb +68 -0
- data/lib/kubeclient/oidc_auth_provider.rb +52 -0
- data/lib/kubeclient/resource.rb +11 -0
- data/lib/kubeclient/resource_not_found_error.rb +4 -0
- data/lib/kubeclient/version.rb +1 -1
- data/lib/kubeclient/watch_stream.rb +71 -28
- data/lib/kubeclient.rb +25 -82
- metadata +140 -114
- data/.travis.yml +0 -6
- data/lib/kubeclient/kube_exception.rb +0 -13
- data/lib/kubeclient/watch_notice.rb +0 -7
- data/test/json/created_namespace_b3.json +0 -20
- data/test/json/created_secret.json +0 -16
- data/test/json/created_service_b3.json +0 -31
- data/test/json/empty_pod_list_b3.json +0 -9
- data/test/json/endpoint_list_b3.json +0 -48
- data/test/json/entity_list_b3.json +0 -56
- data/test/json/event_list_b3.json +0 -35
- data/test/json/namespace_b3.json +0 -13
- data/test/json/namespace_exception_b3.json +0 -8
- data/test/json/namespace_list_b3.json +0 -32
- data/test/json/node_b3.json +0 -29
- data/test/json/node_list_b3.json +0 -37
- data/test/json/pod_b3.json +0 -92
- data/test/json/pod_list_b3.json +0 -75
- data/test/json/replication_controller_b3.json +0 -57
- data/test/json/replication_controller_list_b3.json +0 -64
- data/test/json/secret_list_b3.json +0 -44
- data/test/json/service_b3.json +0 -33
- data/test/json/service_illegal_json_404.json +0 -1
- data/test/json/service_list_b3.json +0 -97
- data/test/json/service_update_b3.json +0 -22
- data/test/json/versions_list.json +0 -6
- data/test/json/watch_stream_b3.json +0 -3
- data/test/test_helper.rb +0 -4
- data/test/test_kubeclient.rb +0 -407
- data/test/test_namespace.rb +0 -53
- data/test/test_node.rb +0 -25
- data/test/test_pod.rb +0 -21
- data/test/test_replication_controller.rb +0 -24
- data/test/test_secret.rb +0 -58
- data/test/test_service.rb +0 -136
- data/test/test_watch.rb +0 -37
- data/test/valid_token_file +0 -1
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'base64'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Kubeclient
|
6
|
+
# Kubernetes client configuration class
|
7
|
+
class Config
|
8
|
+
# Kubernetes client configuration context class
|
9
|
+
class Context
|
10
|
+
attr_reader :api_endpoint, :api_version, :ssl_options, :auth_options, :namespace
|
11
|
+
|
12
|
+
def initialize(api_endpoint, api_version, ssl_options, auth_options, namespace)
|
13
|
+
@api_endpoint = api_endpoint
|
14
|
+
@api_version = api_version
|
15
|
+
@ssl_options = ssl_options
|
16
|
+
@auth_options = auth_options
|
17
|
+
@namespace = namespace
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# data (Hash) - Parsed kubeconfig data.
|
22
|
+
# kcfg_path (string) - Base directory for resolving relative references to external files.
|
23
|
+
# If set to nil, all external lookups & commands are disabled (even for absolute paths).
|
24
|
+
# See also the more convenient Config.read
|
25
|
+
def initialize(data, kcfg_path)
|
26
|
+
@kcfg = data
|
27
|
+
@kcfg_path = kcfg_path
|
28
|
+
raise 'Unknown kubeconfig version' if @kcfg['apiVersion'] != 'v1'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Builds Config instance by parsing given file, with lookups relative to file's directory.
|
32
|
+
def self.read(filename)
|
33
|
+
parsed =
|
34
|
+
if RUBY_VERSION >= '2.6'
|
35
|
+
YAML.safe_load(File.read(filename), permitted_classes: [Date, Time])
|
36
|
+
else
|
37
|
+
YAML.safe_load(File.read(filename), [Date, Time])
|
38
|
+
end
|
39
|
+
Config.new(parsed, File.dirname(filename))
|
40
|
+
end
|
41
|
+
|
42
|
+
def contexts
|
43
|
+
@kcfg['contexts'].map { |x| x['name'] }
|
44
|
+
end
|
45
|
+
|
46
|
+
def context(context_name = nil)
|
47
|
+
cluster, user, namespace = fetch_context(context_name || @kcfg['current-context'])
|
48
|
+
|
49
|
+
if user.key?('exec')
|
50
|
+
exec_opts = expand_command_option(user['exec'], 'command')
|
51
|
+
user['exec_result'] = ExecCredentials.run(exec_opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
ca_cert_data = fetch_cluster_ca_data(cluster)
|
55
|
+
client_cert_data = fetch_user_cert_data(user)
|
56
|
+
client_key_data = fetch_user_key_data(user)
|
57
|
+
auth_options = fetch_user_auth_options(user)
|
58
|
+
|
59
|
+
ssl_options = {}
|
60
|
+
|
61
|
+
if !ca_cert_data.nil?
|
62
|
+
cert_store = OpenSSL::X509::Store.new
|
63
|
+
cert_store.add_cert(OpenSSL::X509::Certificate.new(ca_cert_data))
|
64
|
+
ssl_options[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
|
65
|
+
ssl_options[:cert_store] = cert_store
|
66
|
+
else
|
67
|
+
ssl_options[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
|
68
|
+
end
|
69
|
+
|
70
|
+
unless client_cert_data.nil?
|
71
|
+
ssl_options[:client_cert] = OpenSSL::X509::Certificate.new(client_cert_data)
|
72
|
+
end
|
73
|
+
|
74
|
+
unless client_key_data.nil?
|
75
|
+
ssl_options[:client_key] = OpenSSL::PKey.read(client_key_data)
|
76
|
+
end
|
77
|
+
|
78
|
+
Context.new(cluster['server'], @kcfg['apiVersion'], ssl_options, auth_options, namespace)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def allow_external_lookups?
|
84
|
+
@kcfg_path != nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def ext_file_path(path)
|
88
|
+
unless allow_external_lookups?
|
89
|
+
raise "Kubeclient::Config: external lookups disabled, can't load '#{path}'"
|
90
|
+
end
|
91
|
+
Pathname(path).absolute? ? path : File.join(@kcfg_path, path)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ext_command_path(path)
|
95
|
+
unless allow_external_lookups?
|
96
|
+
raise "Kubeclient::Config: external lookups disabled, can't execute '#{path}'"
|
97
|
+
end
|
98
|
+
# Like go client https://github.com/kubernetes/kubernetes/pull/59495#discussion_r171138995,
|
99
|
+
# distinguish 3 cases:
|
100
|
+
# - absolute (e.g. /path/to/foo)
|
101
|
+
# - $PATH-based (e.g. curl)
|
102
|
+
# - relative to config file's dir (e.g. ./foo)
|
103
|
+
if Pathname(path).absolute?
|
104
|
+
path
|
105
|
+
elsif File.basename(path) == path
|
106
|
+
path
|
107
|
+
else
|
108
|
+
File.join(@kcfg_path, path)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def fetch_context(context_name)
|
113
|
+
context = @kcfg['contexts'].detect do |x|
|
114
|
+
break x['context'] if x['name'] == context_name
|
115
|
+
end
|
116
|
+
|
117
|
+
raise KeyError, "Unknown context #{context_name}" unless context
|
118
|
+
|
119
|
+
cluster = @kcfg['clusters'].detect do |x|
|
120
|
+
break x['cluster'] if x['name'] == context['cluster']
|
121
|
+
end
|
122
|
+
|
123
|
+
raise KeyError, "Unknown cluster #{context['cluster']}" unless cluster
|
124
|
+
|
125
|
+
user = @kcfg['users'].detect do |x|
|
126
|
+
break x['user'] if x['name'] == context['user']
|
127
|
+
end || {}
|
128
|
+
|
129
|
+
namespace = context['namespace']
|
130
|
+
|
131
|
+
[cluster, user, namespace]
|
132
|
+
end
|
133
|
+
|
134
|
+
def fetch_cluster_ca_data(cluster)
|
135
|
+
if cluster.key?('certificate-authority')
|
136
|
+
File.read(ext_file_path(cluster['certificate-authority']))
|
137
|
+
elsif cluster.key?('certificate-authority-data')
|
138
|
+
Base64.decode64(cluster['certificate-authority-data'])
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def fetch_user_cert_data(user)
|
143
|
+
if user.key?('client-certificate')
|
144
|
+
File.read(ext_file_path(user['client-certificate']))
|
145
|
+
elsif user.key?('client-certificate-data')
|
146
|
+
Base64.decode64(user['client-certificate-data'])
|
147
|
+
elsif user.key?('exec_result') && user['exec_result'].key?('clientCertificateData')
|
148
|
+
user['exec_result']['clientCertificateData']
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def fetch_user_key_data(user)
|
153
|
+
if user.key?('client-key')
|
154
|
+
File.read(ext_file_path(user['client-key']))
|
155
|
+
elsif user.key?('client-key-data')
|
156
|
+
Base64.decode64(user['client-key-data'])
|
157
|
+
elsif user.key?('exec_result') && user['exec_result'].key?('clientKeyData')
|
158
|
+
user['exec_result']['clientKeyData']
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def fetch_user_auth_options(user)
|
163
|
+
options = {}
|
164
|
+
if user.key?('token')
|
165
|
+
options[:bearer_token] = user['token']
|
166
|
+
elsif user.key?('exec_result') && user['exec_result'].key?('token')
|
167
|
+
options[:bearer_token] = user['exec_result']['token']
|
168
|
+
elsif user.key?('auth-provider')
|
169
|
+
options[:bearer_token] = fetch_token_from_provider(user['auth-provider'])
|
170
|
+
else
|
171
|
+
%w[username password].each do |attr|
|
172
|
+
options[attr.to_sym] = user[attr] if user.key?(attr)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
options
|
176
|
+
end
|
177
|
+
|
178
|
+
def fetch_token_from_provider(auth_provider)
|
179
|
+
case auth_provider['name']
|
180
|
+
when 'gcp'
|
181
|
+
config = expand_command_option(auth_provider['config'], 'cmd-path')
|
182
|
+
Kubeclient::GCPAuthProvider.token(config)
|
183
|
+
when 'oidc'
|
184
|
+
Kubeclient::OIDCAuthProvider.token(auth_provider['config'])
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def expand_command_option(config, key)
|
189
|
+
config = config.dup
|
190
|
+
config[key] = ext_command_path(config[key]) if config[key]
|
191
|
+
|
192
|
+
config
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -3,14 +3,19 @@ module Kubeclient
|
|
3
3
|
module Common
|
4
4
|
# Kubernetes Entity List
|
5
5
|
class EntityList < DelegateClass(Array)
|
6
|
-
attr_reader :kind, :resourceVersion
|
6
|
+
attr_reader :continue, :kind, :resourceVersion
|
7
7
|
|
8
|
-
def initialize(kind, resource_version, list)
|
8
|
+
def initialize(kind, resource_version, list, continue = nil)
|
9
9
|
@kind = kind
|
10
10
|
# rubocop:disable Style/VariableName
|
11
11
|
@resourceVersion = resource_version
|
12
|
+
@continue = continue
|
12
13
|
super(list)
|
13
14
|
end
|
15
|
+
|
16
|
+
def last?
|
17
|
+
continue.nil?
|
18
|
+
end
|
14
19
|
end
|
15
20
|
end
|
16
21
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kubeclient
|
4
|
+
# An exec-based client auth provide
|
5
|
+
# https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuration
|
6
|
+
# Inspired by https://github.com/kubernetes/client-go/blob/master/plugin/pkg/client/auth/exec/exec.go
|
7
|
+
class ExecCredentials
|
8
|
+
class << self
|
9
|
+
def run(opts)
|
10
|
+
require 'open3'
|
11
|
+
require 'json'
|
12
|
+
|
13
|
+
raise ArgumentError, 'exec options are required' if opts.nil?
|
14
|
+
|
15
|
+
cmd = opts['command']
|
16
|
+
args = opts['args']
|
17
|
+
env = map_env(opts['env'])
|
18
|
+
|
19
|
+
# Validate exec options
|
20
|
+
validate_opts(opts)
|
21
|
+
|
22
|
+
out, err, st = Open3.capture3(env, cmd, *args)
|
23
|
+
|
24
|
+
raise "exec command failed: #{err}" unless st.success?
|
25
|
+
|
26
|
+
creds = JSON.parse(out)
|
27
|
+
validate_credentials(opts, creds)
|
28
|
+
creds['status']
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_opts(opts)
|
34
|
+
raise KeyError, 'exec command is required' unless opts['command']
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_client_credentials_status(status)
|
38
|
+
has_client_cert_data = status.key?('clientCertificateData')
|
39
|
+
has_client_key_data = status.key?('clientKeyData')
|
40
|
+
|
41
|
+
if has_client_cert_data && !has_client_key_data
|
42
|
+
raise 'exec plugin didn\'t return client key data'
|
43
|
+
end
|
44
|
+
|
45
|
+
if !has_client_cert_data && has_client_key_data
|
46
|
+
raise 'exec plugin didn\'t return client certificate data'
|
47
|
+
end
|
48
|
+
|
49
|
+
has_client_cert_data && has_client_key_data
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_credentials_status(status)
|
53
|
+
raise 'exec plugin didn\'t return a status field' if status.nil?
|
54
|
+
|
55
|
+
has_client_credentials = validate_client_credentials_status(status)
|
56
|
+
has_token = status.key?('token')
|
57
|
+
|
58
|
+
if has_client_credentials && has_token
|
59
|
+
raise 'exec plugin returned both token and client data'
|
60
|
+
end
|
61
|
+
|
62
|
+
return if has_client_credentials || has_token
|
63
|
+
|
64
|
+
raise 'exec plugin didn\'t return a token or client data' unless has_token
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_credentials(opts, creds)
|
68
|
+
# out should have ExecCredential structure
|
69
|
+
raise 'invalid credentials' if creds.nil?
|
70
|
+
|
71
|
+
# Verify apiVersion?
|
72
|
+
api_version = opts['apiVersion']
|
73
|
+
if api_version && api_version != creds['apiVersion']
|
74
|
+
raise "exec plugin is configured to use API version #{api_version}, " \
|
75
|
+
"plugin returned version #{creds['apiVersion']}"
|
76
|
+
end
|
77
|
+
|
78
|
+
validate_credentials_status(creds['status'])
|
79
|
+
end
|
80
|
+
|
81
|
+
# Transform name/value pairs to hash
|
82
|
+
def map_env(env)
|
83
|
+
return {} unless env
|
84
|
+
|
85
|
+
Hash[env.map { |e| [e['name'], e['value']] }]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
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
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kubeclient
|
4
|
+
# Get a bearer token from the Google's application default credentials.
|
5
|
+
class GoogleApplicationDefaultCredentials
|
6
|
+
class GoogleDependencyError < LoadError # rubocop:disable Lint/InheritException
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def token
|
11
|
+
begin
|
12
|
+
require 'googleauth'
|
13
|
+
rescue LoadError => e
|
14
|
+
raise GoogleDependencyError,
|
15
|
+
'Error requiring googleauth gem. Kubeclient itself does not include the ' \
|
16
|
+
'googleauth gem. To support auth-provider gcp, you must include it in your ' \
|
17
|
+
"calling application. Failed with: #{e.message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
scopes = [
|
21
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
22
|
+
'https://www.googleapis.com/auth/userinfo.email'
|
23
|
+
]
|
24
|
+
|
25
|
+
authorization = Google::Auth.get_application_default(scopes)
|
26
|
+
authorization.apply({})
|
27
|
+
authorization.access_token
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# TODO: remove this on next major version bump
|
2
|
+
# Deprected http exception
|
3
|
+
class KubeException < StandardError
|
4
|
+
attr_reader :error_code, :message, :response
|
5
|
+
|
6
|
+
def initialize(error_code, message, response)
|
7
|
+
@error_code = error_code
|
8
|
+
@message = message
|
9
|
+
@response = response
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
string = "HTTP status code #{@error_code}, #{@message}"
|
14
|
+
if @response.is_a?(RestClient::Response) && @response.request
|
15
|
+
string << " for #{@response.request.method.upcase} #{@response.request.url}"
|
16
|
+
end
|
17
|
+
string
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Kubeclient
|
22
|
+
# Exception that is raised when a http request fails
|
23
|
+
class HttpError < KubeException
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Kubeclient
|
2
|
+
module Common
|
3
|
+
# Backward compatibility for old versions where kind is missing (e.g. OpenShift Enterprise 3.1)
|
4
|
+
class MissingKindCompatibility
|
5
|
+
MAPPING = {
|
6
|
+
'bindings' => 'Binding',
|
7
|
+
'componentstatuses' => 'ComponentStatus',
|
8
|
+
'endpoints' => 'Endpoints',
|
9
|
+
'events' => 'Event',
|
10
|
+
'limitranges' => 'LimitRange',
|
11
|
+
'namespaces' => 'Namespace',
|
12
|
+
'nodes' => 'Node',
|
13
|
+
'persistentvolumeclaims' => 'PersistentVolumeClaim',
|
14
|
+
'persistentvolumes' => 'PersistentVolume',
|
15
|
+
'pods' => 'Pod',
|
16
|
+
'podtemplates' => 'PodTemplate',
|
17
|
+
'replicationcontrollers' => 'ReplicationController',
|
18
|
+
'resourcequotas' => 'ResourceQuota',
|
19
|
+
'secrets' => 'Secret',
|
20
|
+
'securitycontextconstraints' => 'SecurityContextConstraints',
|
21
|
+
'serviceaccounts' => 'ServiceAccount',
|
22
|
+
'services' => 'Service',
|
23
|
+
'buildconfigs' => 'BuildConfig',
|
24
|
+
'builds' => 'Build',
|
25
|
+
'clusternetworks' => 'ClusterNetwork',
|
26
|
+
'clusterpolicies' => 'ClusterPolicy',
|
27
|
+
'clusterpolicybindings' => 'ClusterPolicyBinding',
|
28
|
+
'clusterrolebindings' => 'ClusterRoleBinding',
|
29
|
+
'clusterroles' => 'ClusterRole',
|
30
|
+
'deploymentconfigrollbacks' => 'DeploymentConfigRollback',
|
31
|
+
'deploymentconfigs' => 'DeploymentConfig',
|
32
|
+
'generatedeploymentconfigs' => 'DeploymentConfig',
|
33
|
+
'groups' => 'Group',
|
34
|
+
'hostsubnets' => 'HostSubnet',
|
35
|
+
'identities' => 'Identity',
|
36
|
+
'images' => 'Image',
|
37
|
+
'imagestreamimages' => 'ImageStreamImage',
|
38
|
+
'imagestreammappings' => 'ImageStreamMapping',
|
39
|
+
'imagestreams' => 'ImageStream',
|
40
|
+
'imagestreamtags' => 'ImageStreamTag',
|
41
|
+
'localresourceaccessreviews' => 'LocalResourceAccessReview',
|
42
|
+
'localsubjectaccessreviews' => 'LocalSubjectAccessReview',
|
43
|
+
'netnamespaces' => 'NetNamespace',
|
44
|
+
'oauthaccesstokens' => 'OAuthAccessToken',
|
45
|
+
'oauthauthorizetokens' => 'OAuthAuthorizeToken',
|
46
|
+
'oauthclientauthorizations' => 'OAuthClientAuthorization',
|
47
|
+
'oauthclients' => 'OAuthClient',
|
48
|
+
'policies' => 'Policy',
|
49
|
+
'policybindings' => 'PolicyBinding',
|
50
|
+
'processedtemplates' => 'Template',
|
51
|
+
'projectrequests' => 'ProjectRequest',
|
52
|
+
'projects' => 'Project',
|
53
|
+
'resourceaccessreviews' => 'ResourceAccessReview',
|
54
|
+
'rolebindings' => 'RoleBinding',
|
55
|
+
'roles' => 'Role',
|
56
|
+
'routes' => 'Route',
|
57
|
+
'subjectaccessreviews' => 'SubjectAccessReview',
|
58
|
+
'templates' => 'Template',
|
59
|
+
'useridentitymappings' => 'UserIdentityMapping',
|
60
|
+
'users' => 'User'
|
61
|
+
}.freeze
|
62
|
+
|
63
|
+
def self.resource_kind(name)
|
64
|
+
MAPPING[name]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kubeclient
|
4
|
+
# Uses OIDC id-tokens and refreshes them if they are stale.
|
5
|
+
class OIDCAuthProvider
|
6
|
+
class OpenIDConnectDependencyError < LoadError # rubocop:disable Lint/InheritException
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def token(provider_config)
|
11
|
+
begin
|
12
|
+
require 'openid_connect'
|
13
|
+
rescue LoadError => e
|
14
|
+
raise OpenIDConnectDependencyError,
|
15
|
+
'Error requiring openid_connect gem. Kubeclient itself does not include the ' \
|
16
|
+
'openid_connect gem. To support auth-provider oidc, you must include it in your ' \
|
17
|
+
"calling application. Failed with: #{e.message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
issuer_url = provider_config['idp-issuer-url']
|
21
|
+
discovery = OpenIDConnect::Discovery::Provider::Config.discover! issuer_url
|
22
|
+
|
23
|
+
if provider_config.key? 'id-token'
|
24
|
+
return provider_config['id-token'] unless expired?(provider_config['id-token'], discovery)
|
25
|
+
end
|
26
|
+
|
27
|
+
client = OpenIDConnect::Client.new(
|
28
|
+
identifier: provider_config['client-id'],
|
29
|
+
secret: provider_config['client-secret'],
|
30
|
+
authorization_endpoint: discovery.authorization_endpoint,
|
31
|
+
token_endpoint: discovery.token_endpoint,
|
32
|
+
userinfo_endpoint: discovery.userinfo_endpoint
|
33
|
+
)
|
34
|
+
client.refresh_token = provider_config['refresh-token']
|
35
|
+
client.access_token!.id_token
|
36
|
+
end
|
37
|
+
|
38
|
+
def expired?(id_token, discovery)
|
39
|
+
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode(
|
40
|
+
id_token,
|
41
|
+
discovery.jwks
|
42
|
+
)
|
43
|
+
# If token expired or expiring within 60 seconds
|
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
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'recursive_open_struct'
|
2
|
+
|
3
|
+
module Kubeclient
|
4
|
+
# Represents all the objects returned by Kubeclient
|
5
|
+
class Resource < RecursiveOpenStruct
|
6
|
+
def initialize(hash = nil, args = {})
|
7
|
+
args[:recurse_over_arrays] = true
|
8
|
+
super(hash, args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/kubeclient/version.rb
CHANGED