diff options
author | Mayra Cabrera <mcabrera@gitlab.com> | 2018-09-21 17:23:33 -0500 |
---|---|---|
committer | Mayra Cabrera <mcabrera@gitlab.com> | 2018-09-26 21:47:29 -0300 |
commit | e5a512628b7889fad30242751f982251dffdc463 (patch) | |
tree | 7561cd92417c54e38628d67ca4ef7f0b2eefa0d5 | |
parent | e255b88e51b956d92afb5e9b90a2749a60e63459 (diff) | |
download | gitlab-ce-51716-automatically-create-service-account-to-project-namespace.tar.gz |
Limit GCP Kubernetes service to project namespace51716-automatically-create-service-account-to-project-namespace
This is needed to support RBAC on AutoDevOps, basically we:
- Creates a service account under project's namespace and assign it a
different token
- If RBAC is enabled we create a RoleBinding for this new service
account with edit access
- Service account name is exposed through environment variables on
Platform::Kubernetes
- KUBE_TOKEN and KUBECONFIG are replaced with new credentials
Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/51716
14 files changed, 455 insertions, 175 deletions
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 3a335909101..1e097953dd5 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -71,6 +71,7 @@ module Clusters .append(key: 'KUBE_TOKEN', value: token, public: false) .append(key: 'KUBE_NAMESPACE', value: actual_namespace) .append(key: 'KUBECONFIG', value: config, public: false, file: true) + .append(key: 'KUBE_SERVICE_ACCOUNT', value: service_account_name) if ca_pem.present? variables @@ -105,6 +106,12 @@ module Clusters @kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io']) end + def service_account_name + default_name = Clusters::Gcp::Kubernetes::SERVICE_ACCOUNT_NAME + + rbac? ? "#{default_name}-#{actual_namespace}" : default_name + end + private def kubeconfig diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 3ae0a4a19d0..5893f27a5cf 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -9,8 +9,9 @@ module Clusters @provider = provider configure_provider - create_gitlab_service_account! configure_kubernetes + create_gitlab_services_account! + configure_kubernetes_token cluster.save! rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e @@ -23,8 +24,8 @@ module Clusters private - def create_gitlab_service_account! - Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute + def create_gitlab_services_account! + Clusters::Gcp::ServicesAccountService.new(kube_client, cluster).execute end def configure_provider @@ -39,19 +40,25 @@ module Clusters ca_cert: Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate), username: gke_cluster.master_auth.username, password: gke_cluster.master_auth.password, - authorization_type: authorization_type, - token: request_kubernetes_token) + authorization_type: authorization_type + ) + end + + def configure_kubernetes_token + cluster.platform_kubernetes.token = request_kubernetes_token end def request_kubernetes_token - Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute + namespace = rbac_cluster? ? cluster.platform_kubernetes.actual_namespace : Clusters::Gcp::Kubernetes::SERVICE_ACCOUNT_NAMESPACE + + Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client, namespace).execute end def authorization_type - create_rbac_cluster? ? 'rbac' : 'abac' + rbac_cluster? ? 'rbac' : 'abac' end - def create_rbac_cluster? + def rbac_cluster? !provider.legacy_abac? end diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb index d014d73b3e8..483f5a63c48 100644 --- a/app/services/clusters/gcp/kubernetes.rb +++ b/app/services/clusters/gcp/kubernetes.rb @@ -8,6 +8,7 @@ module Clusters SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token' CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin' CLUSTER_ROLE_NAME = 'cluster-admin' + EDIT_ROLE_NAME = 'edit' end end end diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb index d17744591e6..be2740d0c4e 100644 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -4,46 +4,46 @@ module Clusters module Gcp module Kubernetes class CreateServiceAccountService - attr_reader :kubeclient, :rbac + attr_reader :kubeclient, :name, :namespace, :rbac - def initialize(kubeclient, rbac:) + def initialize(kubeclient, name:, namespace:, rbac:) @kubeclient = kubeclient + @name = name + @namespace = namespace @rbac = rbac end def execute kubeclient.create_service_account(service_account_resource) kubeclient.create_secret(service_account_token_resource) - kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac + kubeclient.create_role_binding(role_binding_resource) if rbac end private def service_account_resource - Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate + Gitlab::Kubernetes::ServiceAccount.new(name, namespace).generate end def service_account_token_resource Gitlab::Kubernetes::ServiceAccountToken.new( - SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate + service_account_token_name, name, namespace).generate end - def cluster_role_binding_resource - subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }] - - Gitlab::Kubernetes::ClusterRoleBinding.new( - CLUSTER_ROLE_BINDING_NAME, - CLUSTER_ROLE_NAME, - subjects - ).generate + def service_account_token_name + SERVICE_ACCOUNT_TOKEN_NAME end - def service_account_name - SERVICE_ACCOUNT_NAME + def edit_role_name + EDIT_ROLE_NAME end - def service_account_namespace - SERVICE_ACCOUNT_NAMESPACE + def role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + role_name: edit_role_name, + namespace: namespace, + service_account_name: name + ).generate end end end diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb index 9e09345c8dc..89209ed8bfa 100644 --- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb +++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb @@ -4,10 +4,11 @@ module Clusters module Gcp module Kubernetes class FetchKubernetesTokenService - attr_reader :kubeclient + attr_reader :kubeclient, :namespace - def initialize(kubeclient) + def initialize(kubeclient, namespace) @kubeclient = kubeclient + @namespace = namespace end def execute @@ -18,12 +19,16 @@ module Clusters private def get_secret - kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json + kubeclient.get_secret(service_account_token_name, namespace).as_json rescue Kubeclient::HttpError => err raise err unless err.error_code == 404 nil end + + def service_account_token_name + SERVICE_ACCOUNT_TOKEN_NAME + end end end end diff --git a/app/services/clusters/gcp/services_account_service.rb b/app/services/clusters/gcp/services_account_service.rb new file mode 100644 index 00000000000..064a00d4c2e --- /dev/null +++ b/app/services/clusters/gcp/services_account_service.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Clusters + module Gcp + class ServicesAccountService + attr_reader :kube_client, :cluster + + def initialize(kube_client, cluster) + @kube_client = kube_client + @cluster = cluster + end + + def execute + create_service_account + create_namespaced_service_account + end + + private + + def create_namespaced_service_account + return unless cluster.platform_kubernetes_rbac? + + namespace_name = cluster.platform_kubernetes.actual_namespace + + ensure_namespace_exists(namespace_name) + create_service_account(namespace: namespace_name, rbac: true) + end + + def ensure_namespace_exists(namespace_name) + Gitlab::Kubernetes::Namespace.new(namespace_name, kube_client).ensure_exists! + end + + def create_service_account(namespace: 'default', rbac: false) + Clusters::Gcp::Kubernetes::CreateServiceAccountService.new( + kube_client, + name: cluster.platform_kubernetes.service_account_name, + namespace: namespace, + rbac: rbac + ).execute + end + end + end +end diff --git a/changelogs/unreleased/51716-automatically-create-service-account-to-project-namespace.yml b/changelogs/unreleased/51716-automatically-create-service-account-to-project-namespace.yml new file mode 100644 index 00000000000..be9a09b2013 --- /dev/null +++ b/changelogs/unreleased/51716-automatically-create-service-account-to-project-namespace.yml @@ -0,0 +1,5 @@ +--- +title: Limits GCP Kubernetes service to use project's namespace +merge_request: 21867 +author: +type: added diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb index 588238de608..81332da09ef 100644 --- a/lib/gitlab/kubernetes/kube_client.rb +++ b/lib/gitlab/kubernetes/kube_client.rb @@ -45,6 +45,11 @@ module Gitlab :update_cluster_role_binding, to: :rbac_client + delegate :create_role_binding, + :get_role_binding, + :update_role_binding, + to: :rbac_client + # Deployments resource is currently on the apis/extensions api group delegate :get_deployments, to: :extensions_client diff --git a/lib/gitlab/kubernetes/role_binding.rb b/lib/gitlab/kubernetes/role_binding.rb new file mode 100644 index 00000000000..92c7f4dad87 --- /dev/null +++ b/lib/gitlab/kubernetes/role_binding.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Gitlab + module Kubernetes + class RoleBinding + attr_reader :role_name, :namespace, :service_account_name + + def initialize(role_name:, namespace:, service_account_name:) + @role_name = role_name + @namespace = namespace + @service_account_name = service_account_name + end + + def generate + ::Kubeclient::Resource.new.tap do |resource| + resource.metadata = metadata + resource.roleRef = role_ref + resource.subjects = subjects + end + end + + private + + def metadata + { name: "gitlab-#{role_name}", namespace: namespace } + end + + def role_ref + { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'Role', + name: role_name + } + end + + def subjects + [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + end + end + end +end diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb new file mode 100644 index 00000000000..aab1ca4087c --- /dev/null +++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Kubernetes::RoleBinding, '#generate' do + let(:role_name) { 'edit' } + let(:namespace) { 'my-namespace' } + let(:service_account_name) { 'my-service-account' } + + let(:subjects) do + [ + { + kind: 'ServiceAccount', + name: service_account_name, + namespace: namespace + } + ] + end + + let(:role_ref) do + { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'RoleBinding', + name: role_name + } + end + + let(:resource) do + ::Kubeclient::Resource.new( + metadata: { name: 'gitlab-edit' }, + roleRef: role_ref, + subjects: subjects + ) + end + + subject do + described_class.new( + role_name: role_name, + namespace: namespace, + service_account_name: service_account_name + ).generate + end + + it 'should build a Kubeclient Resource' do + is_expected.to eq(resource) + end +end diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb index 0f484222228..75d5e57fd83 100644 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb @@ -1,156 +1,168 @@ +# frozen_string_literal: true + require 'spec_helper' -describe Clusters::Gcp::FinalizeCreationService do +describe Clusters::Gcp::FinalizeCreationService, '#execute' do include GoogleApi::CloudPlatformHelpers include KubernetesHelpers - describe '#execute' do - let(:cluster) { create(:cluster, :project, :providing_by_gcp) } - let(:provider) { cluster.provider } - let(:platform) { cluster.platform } - let(:gcp_project_id) { provider.gcp_project_id } - let(:zone) { provider.zone } - let(:cluster_name) { cluster.name } + let(:cluster) { create(:cluster, :project, :providing_by_gcp) } + let(:provider) { cluster.provider } + let(:platform) { cluster.platform } + let(:gcp_project_id) { provider.gcp_project_id } + let(:zone) { provider.zone } + let(:cluster_name) { cluster.name } + let(:endpoint) { '111.111.111.111' } + let(:api_url) { 'https://' + endpoint } + let(:username) { 'sample-username' } + let(:password) { 'sample-password' } + let(:secret_name) { 'gitlab-token' } + + subject { described_class.new.execute(provider) } + + shared_examples 'success' do + it 'configures provider and kubernetes' do + subject + + expect(provider).to be_created + end - subject { described_class.new.execute(provider) } + it 'properly configures database models' do + subject - shared_examples 'success' do - it 'configures provider and kubernetes' do - subject + cluster.reload - expect(provider).to be_created - end + expect(provider.endpoint).to eq(endpoint) + expect(platform.api_url).to eq(api_url) + expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert)) + expect(platform.username).to eq(username) + expect(platform.password).to eq(password) + expect(platform.token).to eq(token) end + end + + shared_examples 'error' do + it 'sets an error to provider object' do + subject - shared_examples 'error' do - it 'sets an error to provider object' do - subject + expect(provider.reload).to be_errored + end + end - expect(provider.reload).to be_errored + shared_examples 'kubernetes information not successfully fetched' do + context 'when failed to fetch gke cluster info' do + before do + stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name) end + + it_behaves_like 'error' end - context 'when suceeded to fetch gke cluster info' do - let(:endpoint) { '111.111.111.111' } - let(:api_url) { 'https://' + endpoint } - let(:username) { 'sample-username' } - let(:password) { 'sample-password' } - let(:secret_name) { 'gitlab-token' } + context 'when token is empty' do + let(:token) { '' } + it_behaves_like 'error' + end + + context 'when failed to fetch kubernetes token' do before do - stub_cloud_platform_get_zone_cluster( - gcp_project_id, zone, cluster_name, - { - endpoint: endpoint, - username: username, - password: password - } - ) + stub_kubeclient_get_secret_error(api_url, secret_name, namespace: namespace) end - context 'service account and token created' do - before do - stub_kubeclient_discover(api_url) - stub_kubeclient_create_service_account(api_url) - stub_kubeclient_create_secret(api_url) - end - - shared_context 'kubernetes token successfully fetched' do - let(:token) { 'sample-token' } - - before do - stub_kubeclient_get_secret( - api_url, - { - metadata_name: secret_name, - token: Base64.encode64(token) - } ) - end - end - - context 'provider legacy_abac is enabled' do - include_context 'kubernetes token successfully fetched' - - it_behaves_like 'success' - - it 'properly configures database models' do - subject - - cluster.reload - - expect(provider.endpoint).to eq(endpoint) - expect(platform.api_url).to eq(api_url) - expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert)) - expect(platform.username).to eq(username) - expect(platform.password).to eq(password) - expect(platform).to be_abac - expect(platform.authorization_type).to eq('abac') - expect(platform.token).to eq(token) - end - end - - context 'provider legacy_abac is disabled' do - before do - provider.legacy_abac = false - end - - include_context 'kubernetes token successfully fetched' - - context 'cluster role binding created' do - before do - stub_kubeclient_create_cluster_role_binding(api_url) - end - - it_behaves_like 'success' - - it 'properly configures database models' do - subject - - cluster.reload - - expect(provider.endpoint).to eq(endpoint) - expect(platform.api_url).to eq(api_url) - expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert)) - expect(platform.username).to eq(username) - expect(platform.password).to eq(password) - expect(platform).to be_rbac - expect(platform.token).to eq(token) - end - end - end - - context 'when token is empty' do - before do - stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name) - end - - it_behaves_like 'error' - end - - context 'when failed to fetch kubernetes token' do - before do - stub_kubeclient_get_secret_error(api_url, secret_name) - end - - it_behaves_like 'error' - end - - context 'when service account fails to create' do - before do - stub_kubeclient_create_service_account_error(api_url) - end - - it_behaves_like 'error' - end - end + it_behaves_like 'error' end - context 'when failed to fetch gke cluster info' do + context 'when service account fails to create' do before do - stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name) + stub_kubeclient_create_service_account_error(api_url) end it_behaves_like 'error' end end + + shared_context 'kubernetes information successfully fetched' do + before do + stub_cloud_platform_get_zone_cluster( + gcp_project_id, zone, cluster_name, + { + endpoint: endpoint, + username: username, + password: password + } + ) + + stub_kubeclient_discover(api_url) + stub_kubeclient_create_service_account(api_url) + stub_kubeclient_create_secret(api_url) + + stub_kubeclient_get_secret( + api_url, + { + metadata_name: secret_name, + token: Base64.encode64(token) + } + ) + end + end + + context 'With a legacy ABAC cluster' do + let(:token) { 'sample-token' } + let(:namespace) { 'default' } + + before do + provider.legacy_abac = true + end + + include_context 'kubernetes information successfully fetched' + + it_behaves_like 'success' + + it 'uses ABAC authorization type' do + subject + cluster.reload + + expect(platform).to be_abac + expect(platform.authorization_type).to eq('abac') + end + + it_behaves_like 'kubernetes information not successfully fetched' + end + + context 'With an RBAC cluster' do + let(:token) { 'sample-token' } + let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" } + + include_context 'kubernetes information successfully fetched' + + before do + provider.legacy_abac = false + + stub_kubeclient_create_service_account(api_url, namespace: namespace) + stub_kubeclient_create_secret(api_url, namespace: namespace) + stub_kubeclient_create_role_binding(api_url, namespace: namespace) + stub_kubeclient_create_namespace(api_url) + stub_kubeclient_get_namespace(api_url, namespace: namespace) + + options = { + metadata_name: secret_name, + token: Base64.encode64(token), + namespace: namespace + } + + stub_kubeclient_get_secret(api_url, options) + end + + it_behaves_like 'success' + + it 'uses RBAC authorization type' do + subject + cluster.reload + + expect(platform).to be_rbac + expect(platform.authorization_type).to eq('rbac') + end + + it_behaves_like 'kubernetes information not successfully fetched' + end end diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb index 065d021db5e..1b26735df16 100644 --- a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb +++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do include KubernetesHelpers - let(:service) { described_class.new(kubeclient, rbac: rbac) } + let(:service) { described_class.new(kubeclient, name: name, namespace: namespace, rbac: rbac) } describe '#execute' do let(:rbac) { false } @@ -13,6 +13,17 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do let(:username) { 'admin' } let(:password) { 'xxx' } + let(:cluster) do + create(:cluster, + :project, :provided_by_gcp, + platform_kubernetes: create(:cluster_platform_kubernetes, :configured) + ) + end + + let(:platform_kubernetes) { cluster.platform_kubernetes } + let(:namespace) { platform_kubernetes.actual_namespace } + let(:name) { platform_kubernetes.service_account_name } + let(:kubeclient) do Gitlab::Kubernetes::KubeClient.new( api_url, @@ -26,18 +37,18 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do context 'when params are correct' do before do stub_kubeclient_discover(api_url) - stub_kubeclient_create_service_account(api_url) - stub_kubeclient_create_secret(api_url) + stub_kubeclient_create_service_account(api_url, namespace: namespace) + stub_kubeclient_create_secret(api_url, namespace: namespace) end shared_examples 'creates service account and token' do it 'creates a kubernetes service account' do subject - expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/serviceaccounts').with( + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with( body: hash_including( kind: 'ServiceAccount', - metadata: { name: 'gitlab', namespace: 'default' } + metadata: { name: name, namespace: namespace } ) ) end @@ -45,12 +56,12 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do it 'creates a kubernetes secret of type ServiceAccountToken' do subject - expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with( + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( body: hash_including( kind: 'Secret', metadata: { name: 'gitlab-token', - namespace: 'default', + namespace: namespace, annotations: { 'kubernetes.io/service-account.name': 'gitlab' } @@ -69,24 +80,24 @@ describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do let(:rbac) { true } before do - stub_kubeclient_create_cluster_role_binding(api_url) + stub_kubeclient_create_role_binding(api_url, namespace: namespace) end it_behaves_like 'creates service account and token' - it 'creates a kubernetes cluster role binding' do + it 'creates a kubernetes role binding with edit access' do subject - expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with( + expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with( body: hash_including( - kind: 'ClusterRoleBinding', - metadata: { name: 'gitlab-admin' }, + kind: 'RoleBinding', + metadata: { name: 'gitlab-edit', namespace: namespace }, roleRef: { apiGroup: 'rbac.authorization.k8s.io', - kind: 'ClusterRole', - name: 'cluster-admin' + kind: 'Role', + name: 'edit' }, - subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }] + subjects: [{ kind: 'ServiceAccount', name: 'gitlab', namespace: namespace }] ) ) end diff --git a/spec/services/clusters/gcp/services_account_service_spec.rb b/spec/services/clusters/gcp/services_account_service_spec.rb new file mode 100644 index 00000000000..f6f08eae666 --- /dev/null +++ b/spec/services/clusters/gcp/services_account_service_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Clusters::Gcp::ServicesAccountService, '#execute' do + include GoogleApi::CloudPlatformHelpers + include KubernetesHelpers + + let(:endpoint) { '111.111.111.111' } + let(:api_url) { 'https://' + endpoint } + let(:cluster) { create(:cluster, :project, :providing_by_gcp, platform_kubernetes: create(:cluster_platform_kubernetes)) } + let(:username) { 'sample-username' } + let(:password) { 'sample-password' } + + let(:kubeclient) do + Gitlab::Kubernetes::KubeClient.new( + api_url, + ['api', 'apis/rbac.authorization.k8s.io'], + auth_options: { username: username, password: password } + ) + end + + subject { described_class.new(kubeclient, cluster).execute } + + context 'With an ABAC cluster' do + before do + stub_kubeclient_discover(api_url) + stub_kubeclient_create_service_account(api_url) + stub_kubeclient_create_secret(api_url) + end + + it 'creates default service account' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/default/serviceaccounts").with( + body: hash_including( + kind: 'ServiceAccount', + metadata: { name: 'gitlab', namespace: 'default' } + ) + ) + end + end + + context 'With an RBAC cluster' do + let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" } + + before do + cluster.platform_kubernetes.rbac! + + stub_kubeclient_discover(api_url) + stub_kubeclient_create_service_account(api_url) + stub_kubeclient_create_secret(api_url) + + stub_kubeclient_create_namespace(api_url) + stub_kubeclient_get_namespace(api_url, namespace: namespace) + + stub_kubeclient_create_service_account(api_url, namespace: namespace) + stub_kubeclient_create_secret(api_url, namespace: namespace) + stub_kubeclient_create_role_binding(api_url, namespace: namespace) + end + + it 'creates namespaced service account' do + subject + + expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with( + body: hash_including( + kind: 'ServiceAccount', + metadata: { name: "gitlab-#{namespace}", namespace: namespace } + ) + ) + end + end +end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index c077ca9f15b..a03d9c4045f 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -33,10 +33,11 @@ module KubernetesHelpers WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response) end - def stub_kubeclient_get_secret(api_url, namespace: 'default', **options) + def stub_kubeclient_get_secret(api_url, **options) options[:metadata_name] ||= "default-token-1" + options[:namespace] ||= "default" - WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{options[:metadata_name]}") + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{options[:namespace]}/secrets/#{options[:metadata_name]}") .to_return(kube_response(kube_v1_secret_body(options))) end @@ -65,6 +66,21 @@ module KubernetesHelpers .to_return(kube_response({})) end + def stub_kubeclient_create_role_binding(api_url, namespace: 'default') + WebMock.stub_request(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings") + .to_return(kube_response({})) + end + + def stub_kubeclient_create_namespace(api_url) + WebMock.stub_request(:post, api_url + "/api/v1/namespaces") + .to_return(kube_response({})) + end + + def stub_kubeclient_get_namespace(api_url, namespace: 'default') + WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}") + .to_return(kube_response({})) + end + def kube_v1_secret_body(**options) { "kind" => "SecretList", @@ -87,7 +103,8 @@ module KubernetesHelpers { "name" => "deployments", "namespaced" => true, "kind" => "Deployment" }, { "name" => "secrets", "namespaced" => true, "kind" => "Secret" }, { "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" }, - { "name" => "services", "namespaced" => true, "kind" => "Service" } + { "name" => "services", "namespaced" => true, "kind" => "Service" }, + { "name" => "namespaces", "namespaced" => true, "kind" => "Namespace" } ] } end |