diff --git a/kubernetes-deploy.gemspec b/kubernetes-deploy.gemspec index c54a0d3d6..9bf9f14b8 100644 --- a/kubernetes-deploy.gemspec +++ b/kubernetes-deploy.gemspec @@ -22,9 +22,11 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.add_dependency "activesupport", ">= 4.2" spec.add_dependency "kubeclient", "~> 2.3" + spec.add_dependency "googleauth" spec.add_development_dependency "bundler", "~> 1.13" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "minitest", "~> 5.0" spec.add_development_dependency "minitest-stub-const", "~> 0.6" + spec.add_development_dependency "webmock", "~> 3.0" end diff --git a/lib/kubernetes-deploy/kubeclient_builder.rb b/lib/kubernetes-deploy/kubeclient_builder.rb index 9763bf64e..33645e442 100644 --- a/lib/kubernetes-deploy/kubeclient_builder.rb +++ b/lib/kubernetes-deploy/kubeclient_builder.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require 'kubeclient' +require 'kubernetes-deploy/kubeclient_builder/google_friendly_config' module KubernetesDeploy module KubeclientBuilder @@ -28,7 +29,7 @@ def build_v1beta1_kubeclient(context) end def _build_kubeclient(api_version:, context:, endpoint_path: nil) - config = Kubeclient::Config.read(ENV.fetch("KUBECONFIG")) + config = GoogleFriendlyConfig.read(ENV.fetch("KUBECONFIG")) unless config.contexts.include?(context) raise ContextMissingError, context end diff --git a/lib/kubernetes-deploy/kubeclient_builder/google_friendly_config.rb b/lib/kubernetes-deploy/kubeclient_builder/google_friendly_config.rb new file mode 100644 index 000000000..f4ae690c9 --- /dev/null +++ b/lib/kubernetes-deploy/kubeclient_builder/google_friendly_config.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'googleauth' +module KubernetesDeploy + module KubeclientBuilder + class GoogleFriendlyConfig < Kubeclient::Config + def fetch_user_auth_options(user) + if user['auth-provider'] && (user['auth-provider']['name'] == 'gcp') + { bearer_token: new_token } + else + super + end + end + + def self.read(filename) + new(YAML.load_file(filename), File.dirname(filename)) + end + + def new_token + scopes = ['https://www.googleapis.com/auth/cloud-platform'] + authorization = Google::Auth.get_application_default(scopes) + + authorization.apply({}) + + authorization.access_token + + rescue Signet::AuthorizationError => e + err_message = json_error_message(e.response.body) || e.message + raise KubeException.new(e.response.status, err_message, e.response.body) + end + + private + + def json_error_message(body) + json_error_msg = begin + JSON.parse(body || '') || {} + rescue JSON::ParserError + {} + end + json_error_msg['message'] + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index a9152a0a1..689d42bf2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,6 +6,7 @@ require 'timecop' require 'minitest/autorun' require 'minitest/stub/const' +require 'webmock/minitest' require 'helpers/kubeclient_helper' require 'helpers/fixture_deploy_helper' @@ -14,6 +15,8 @@ ENV["KUBECONFIG"] ||= "#{Dir.home}/.kube/config" +WebMock.allow_net_connect! + module KubernetesDeploy class TestCase < ::Minitest::Test def setup diff --git a/test/unit/kubernetes-deploy/google_friendly_config_test.rb b/test/unit/kubernetes-deploy/google_friendly_config_test.rb new file mode 100644 index 000000000..f86d52e1a --- /dev/null +++ b/test/unit/kubernetes-deploy/google_friendly_config_test.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true +require 'test_helper' + +class GoogleFriendlyConfigTest < KubernetesDeploy::TestCase + def setup + WebMock.disable_net_connect! + set_google_env_vars + end + + def teardown + WebMock.allow_net_connect! + end + + def test_auth_use_default_gcp_success + config = KubernetesDeploy::KubeclientBuilder::GoogleFriendlyConfig.new(kubeconfig, "") + + stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: { + "access_token": "bearer_token", + "token_type": "Bearer", + "expires_in": 3600, + "id_token": "identity_token" + }.to_json, + status: 200 + ) + + context = config.context("google") + assert_equal 'bearer_token', context.auth_options[:bearer_token] + end + + def test_auth_use_default_gcp_failure + config = KubernetesDeploy::KubeclientBuilder::GoogleFriendlyConfig.new(kubeconfig, "") + + stub_request(:post, 'https://www.googleapis.com/oauth2/v3/token') + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: '', + status: 401 + ) + + assert_raises(KubeException) do + config.context("google") + end + end + + def test_non_google_auth_works + config = KubernetesDeploy::KubeclientBuilder::GoogleFriendlyConfig.new(kubeconfig, "") + + context = config.context("minikube") + + assert_equal 'test', context.auth_options[:password] + assert_equal 'admin', context.auth_options[:username] + end + + def kubeconfig + { + 'apiVersion' => 'v1', + 'clusters' => [ + { 'cluster' => { 'server' => 'https://192.168.64.3:8443' }, 'name' => 'test' } + ], + 'contexts' => [ + { + 'context' => { + 'cluster' => 'test', + 'user' => 'google' + }, + 'name' => 'google' + }, + { + 'context' => { + 'cluster' => 'test', 'user' => 'minikube' + }, + 'name' => 'minikube' + }, + ], + 'users' => [ + { + 'name' => 'google', + 'user' => { + 'auth-provider' => { + 'name' => 'gcp', + 'config' => { 'access_token' => 'test' } + } + } + }, + { + 'name' => 'minikube', + 'user' => { + 'password' => 'test', + 'username' => 'admin' + } + } + ] + }.stringify_keys + end + + def set_google_env_vars + ENV["GOOGLE_PRIVATE_KEY"] ||= "FAKE" + ENV["GOOGLE_CLIENT_EMAIL"] ||= "fake@email.com" + ENV["GOOGLE_ACCOUNT_TYPE"] ||= 'authorized_user' + ENV["GOOGLE_CLIENT_ID"] ||= 'fake' + ENV["GOOGLE_CLIENT_SECRET"] ||= 'fake' + ENV["REFRESH_TOKEN_VAR"] ||= 'fake' + end +end