# frozen_string_literal: true require 'spec_helper' RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do include KubernetesHelpers let(:api_url) { 'http://111.111.111.111' } let(:platform_kubernetes) { cluster.platform_kubernetes } let(:cluster_project) { cluster.cluster_project } let(:project) { cluster_project.project } let(:cluster) do create(:cluster, :project, :provided_by_gcp, platform_kubernetes: create(:cluster_platform_kubernetes, :configured)) end let(:kubeclient) do Gitlab::Kubernetes::KubeClient.new( api_url, auth_options: { username: 'admin', password: 'xxx' } ) 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/#{namespace}/serviceaccounts").with( body: hash_including( kind: 'ServiceAccount', metadata: { name: service_account_name, namespace: namespace } ) ) end it 'creates a kubernetes secret' do subject expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with( body: hash_including( kind: 'Secret', metadata: { name: token_name, namespace: namespace, annotations: { 'kubernetes.io/service-account.name': service_account_name } }, type: 'kubernetes.io/service-account-token' ) ) end end before do stub_kubeclient_discover(api_url) stub_kubeclient_get_namespace(api_url, namespace: namespace) stub_kubeclient_get_service_account_error(api_url, service_account_name, namespace: namespace) stub_kubeclient_create_service_account(api_url, namespace: namespace) stub_kubeclient_get_secret_error(api_url, token_name, namespace: namespace) stub_kubeclient_create_secret(api_url, namespace: namespace) end describe '.gitlab_creator' do let(:namespace) { 'default' } let(:service_account_name) { 'gitlab' } let(:token_name) { 'gitlab-token' } subject { described_class.gitlab_creator(kubeclient, rbac: rbac).execute } context 'with ABAC cluster' do let(:rbac) { false } it_behaves_like 'creates service account and token' end context 'with RBAC cluster' do let(:rbac) { true } let(:cluster_role_binding_name) { 'gitlab-admin' } before do cluster.platform_kubernetes.rbac! stub_kubeclient_put_cluster_role_binding(api_url, cluster_role_binding_name) end it_behaves_like 'creates service account and token' it 'creates a cluster role binding with cluster-admin access' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/gitlab-admin").with( body: hash_including( metadata: { name: 'gitlab-admin' }, roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'cluster-admin' }, subjects: [ { kind: 'ServiceAccount', name: service_account_name, namespace: namespace } ] ) ) end end end describe '.namespace_creator' do let(:namespace) { "#{project.path}-#{project.id}" } let(:namespace_labels) { { app: project.full_path_slug, env: "staging" } } let(:service_account_name) { "#{namespace}-service-account" } let(:token_name) { "#{namespace}-token" } subject do described_class.namespace_creator( kubeclient, service_account_name: service_account_name, service_account_namespace: namespace, service_account_namespace_labels: namespace_labels, rbac: rbac ).execute end context 'with ABAC cluster' do let(:rbac) { false } it_behaves_like 'creates service account and token' end context 'With RBAC enabled cluster' do let(:rbac) { true } let(:role_binding_name) { "gitlab-#{namespace}"} before do cluster.platform_kubernetes.rbac! stub_kubeclient_delete_role_binding(api_url, role_binding_name, namespace: namespace) stub_kubeclient_put_role_binding(api_url, role_binding_name, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace) stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, namespace: namespace) stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, namespace: namespace) end it 'creates a namespace object' do kubernetes_namespace = double(Gitlab::Kubernetes::Namespace) expect(Gitlab::Kubernetes::Namespace).to( receive(:new).with(namespace, kubeclient, labels: namespace_labels).and_return(kubernetes_namespace) ) expect(kubernetes_namespace).to receive(:ensure_exists!) subject end it_behaves_like 'creates service account and token' it 'creates a namespaced role binding with admin access' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{role_binding_name}").with( body: hash_including( metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" }, roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'ClusterRole', name: 'admin' }, subjects: [ { kind: 'ServiceAccount', name: service_account_name, namespace: namespace } ] ) ) end it 'creates a role binding granting crossplane database permissions to the service account' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME}").with( body: hash_including( metadata: { name: Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace }, roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'Role', name: Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME }, subjects: [ { kind: 'ServiceAccount', name: service_account_name, namespace: namespace } ] ) ) end it 'creates a role and role binding granting knative serving permissions to the service account' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with( body: hash_including( metadata: { name: Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace }, rules: [{ apiGroups: %w(serving.knative.dev), resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services), verbs: %w(get list create update delete patch watch) }] ) ) end it 'creates a role and role binding granting crossplane database permissions to the service account' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME}").with( body: hash_including( metadata: { name: Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace }, rules: [{ apiGroups: %w(database.crossplane.io), resources: %w(postgresqlinstances), verbs: %w(get list create watch) }] ) ) end it 'creates a role granting cilium permissions to the service account' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME}").with( body: hash_including( metadata: { name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, namespace: namespace }, rules: [{ apiGroups: %w(cilium.io), resources: %w(ciliumnetworkpolicies), verbs: %w(get list create update patch) }] ) ) end it 'creates a role binding granting cilium permissions to the service account' do subject expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME}").with( body: hash_including( metadata: { name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, namespace: namespace }, roleRef: { apiGroup: 'rbac.authorization.k8s.io', kind: 'Role', name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME }, subjects: [{ kind: 'ServiceAccount', name: service_account_name, namespace: namespace }] ) ) end end end end