summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-09-17 08:43:48 +0000
committerKamil Trzciński <ayufan@ayufan.eu>2018-09-17 08:43:48 +0000
commit5a8908bf587a0723b07e510dd6118a686d49af98 (patch)
treea19af868a1b0a7c676b56a43372811a662754b6f
parentba40c7f1c32f62ad5370621f049500aa904149cd (diff)
parent528b060b89c2d6a6be611e88ceed28cfe86e167c (diff)
downloadgitlab-ce-5a8908bf587a0723b07e510dd6118a686d49af98.tar.gz
Merge branch '29398-support-rbac-for-gitlab-provisioned-clusters' into 'master'
Support Kubernetes RBAC for GitLab Managed Apps for creating new clusters Closes #29398 See merge request gitlab-org/gitlab-ce!21401
-rw-r--r--app/controllers/projects/clusters_controller.rb3
-rw-r--r--app/services/ci/fetch_kubernetes_token_service.rb75
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb50
-rw-r--r--app/services/clusters/gcp/kubernetes.rb13
-rw-r--r--app/services/clusters/gcp/kubernetes/create_service_account_service.rb51
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb30
-rw-r--r--app/services/clusters/gcp/provision_service.rb4
-rw-r--r--app/views/projects/clusters/gcp/_form.html.haml10
-rw-r--r--app/views/projects/clusters/gcp/_show.html.haml9
-rw-r--r--app/views/projects/clusters/user/_form.html.haml1
-rw-r--r--changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml5
-rw-r--r--db/migrate/20180907015926_add_legacy_abac_to_cluster_providers_gcp.rb17
-rw-r--r--db/schema.rb3
-rw-r--r--doc/user/project/clusters/index.md91
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb2
-rw-r--r--lib/gitlab/kubernetes/service_account_token.rb36
-rw-r--r--lib/google_api/cloud_platform/client.rb4
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb16
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb52
-rw-r--r--spec/features/projects/clusters/user_spec.rb55
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/service_account_token_spec.rb35
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb47
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb18
-rw-r--r--spec/services/ci/fetch_kubernetes_token_service_spec.rb64
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb123
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb96
-rw-r--r--spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb60
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb54
-rw-r--r--spec/support/services/clusters/create_service_shared.rb8
30 files changed, 767 insertions, 267 deletions
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index b4fd09c06e5..eb0fad6cbb2 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -141,7 +141,8 @@ class Projects::ClustersController < Projects::ApplicationController
:gcp_project_id,
:zone,
:num_nodes,
- :machine_type
+ :machine_type,
+ :legacy_abac
]).merge(
provider_type: :gcp,
platform_type: :kubernetes
diff --git a/app/services/ci/fetch_kubernetes_token_service.rb b/app/services/ci/fetch_kubernetes_token_service.rb
deleted file mode 100644
index 15eda56cac6..00000000000
--- a/app/services/ci/fetch_kubernetes_token_service.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-# frozen_string_literal: true
-
-##
-# TODO:
-# Almost components in this class were copied from app/models/project_services/kubernetes_service.rb
-# We should dry up those classes not to repeat the same code.
-# Maybe we should have a special facility (e.g. lib/kubernetes_api) to maintain all Kubernetes API caller.
-module Ci
- class FetchKubernetesTokenService
- attr_reader :api_url, :ca_pem, :username, :password
-
- def initialize(api_url, ca_pem, username, password)
- @api_url = api_url
- @ca_pem = ca_pem
- @username = username
- @password = password
- end
-
- def execute
- read_secrets.each do |secret|
- name = secret.dig('metadata', 'name')
- if /default-token/ =~ name
- token_base64 = secret.dig('data', 'token')
- return Base64.decode64(token_base64) if token_base64
- end
- end
-
- nil
- end
-
- private
-
- def read_secrets
- kubeclient = build_kubeclient!
-
- kubeclient.get_secrets.as_json
- rescue Kubeclient::HttpError => err
- raise err unless err.error_code == 404
-
- []
- end
-
- def build_kubeclient!(api_path: 'api', api_version: 'v1')
- raise "Incomplete settings" unless api_url && username && password
-
- ::Kubeclient::Client.new(
- join_api_url(api_path),
- api_version,
- auth_options: { username: username, password: password },
- ssl_options: kubeclient_ssl_options,
- http_proxy_uri: ENV['http_proxy']
- )
- end
-
- def join_api_url(api_path)
- url = URI.parse(api_url)
- prefix = url.path.sub(%r{/+\z}, '')
-
- url.path = [prefix, api_path].join("/")
-
- url.to_s
- end
-
- def kubeclient_ssl_options
- opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
-
- if ca_pem.present?
- opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
- end
-
- opts
- end
- end
-end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 264419501dc..3ae0a4a19d0 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -9,17 +9,24 @@ module Clusters
@provider = provider
configure_provider
+ create_gitlab_service_account!
configure_kubernetes
cluster.save!
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
+ rescue Kubeclient::HttpError => e
+ provider.make_errored!("Failed to run Kubeclient: #{e.message}")
rescue ActiveRecord::RecordInvalid => e
provider.make_errored!("Failed to configure Google Kubernetes Engine Cluster: #{e.message}")
end
private
+ def create_gitlab_service_account!
+ Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute
+ end
+
def configure_provider
provider.endpoint = gke_cluster.endpoint
provider.status_event = :make_created
@@ -32,15 +39,54 @@ 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)
end
def request_kubernetes_token
- Ci::FetchKubernetesTokenService.new(
+ Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute
+ end
+
+ def authorization_type
+ create_rbac_cluster? ? 'rbac' : 'abac'
+ end
+
+ def create_rbac_cluster?
+ !provider.legacy_abac?
+ end
+
+ def kube_client
+ @kube_client ||= build_kube_client!(
'https://' + gke_cluster.endpoint,
Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
gke_cluster.master_auth.username,
- gke_cluster.master_auth.password).execute
+ gke_cluster.master_auth.password,
+ api_groups: ['api', 'apis/rbac.authorization.k8s.io']
+ )
+ end
+
+ def build_kube_client!(api_url, ca_pem, username, password, api_groups: ['api'], api_version: 'v1')
+ raise "Incomplete settings" unless api_url && username && password
+
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ api_groups,
+ api_version,
+ auth_options: { username: username, password: password },
+ ssl_options: kubeclient_ssl_options(ca_pem),
+ http_proxy_uri: ENV['http_proxy']
+ )
+ end
+
+ def kubeclient_ssl_options(ca_pem)
+ opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
+
+ if ca_pem.present?
+ opts[:cert_store] = OpenSSL::X509::Store.new
+ opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
+ end
+
+ opts
end
def gke_cluster
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
new file mode 100644
index 00000000000..d014d73b3e8
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ SERVICE_ACCOUNT_NAME = 'gitlab'
+ SERVICE_ACCOUNT_NAMESPACE = 'default'
+ SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token'
+ CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
+ CLUSTER_ROLE_NAME = 'cluster-admin'
+ 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
new file mode 100644
index 00000000000..d17744591e6
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class CreateServiceAccountService
+ attr_reader :kubeclient, :rbac
+
+ def initialize(kubeclient, rbac:)
+ @kubeclient = kubeclient
+ @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
+ end
+
+ private
+
+ def service_account_resource
+ Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate
+ end
+
+ def service_account_token_resource
+ Gitlab::Kubernetes::ServiceAccountToken.new(
+ SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_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
+ end
+
+ def service_account_name
+ SERVICE_ACCOUNT_NAME
+ end
+
+ def service_account_namespace
+ SERVICE_ACCOUNT_NAMESPACE
+ end
+ end
+ 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
new file mode 100644
index 00000000000..9e09345c8dc
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class FetchKubernetesTokenService
+ attr_reader :kubeclient
+
+ def initialize(kubeclient)
+ @kubeclient = kubeclient
+ end
+
+ def execute
+ token_base64 = get_secret&.dig('data', 'token')
+ Base64.decode64(token_base64) if token_base64
+ end
+
+ private
+
+ def get_secret
+ kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json
+ rescue Kubeclient::HttpError => err
+ raise err unless err.error_code == 404
+
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/gcp/provision_service.rb b/app/services/clusters/gcp/provision_service.rb
index ab1bf9c64f6..80040511ec2 100644
--- a/app/services/clusters/gcp/provision_service.rb
+++ b/app/services/clusters/gcp/provision_service.rb
@@ -27,7 +27,9 @@ module Clusters
provider.zone,
provider.cluster.name,
provider.num_nodes,
- machine_type: provider.machine_type)
+ machine_type: provider.machine_type,
+ legacy_abac: provider.legacy_abac
+ )
unless operation.status == 'PENDING' || operation.status == 'RUNNING'
return provider.make_errored!("Operation status is unexpected; #{operation.status_message}")
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml
index 9133de6559d..0222bbf7338 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/projects/clusters/gcp/_form.html.haml
@@ -61,5 +61,15 @@
%p.form-text.text-muted
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true
+ = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
+
.form-group
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml
index 877e0cc876c..be84f2ae67c 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/projects/clusters/gcp/_show.html.haml
@@ -37,5 +37,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
= platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace')
+ - if rbac_clusters_feature_enabled?
+ .form-group
+ .form-check
+ = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac'
+ = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold'
+ .form-text.text-muted
+ = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
+ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml
index 1f81e024ab9..f497f5b606c 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/projects/clusters/user/_form.html.haml
@@ -33,6 +33,7 @@
.form-text.text-muted
= s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).')
= s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.')
+ = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank'
.form-group
= field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml b/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml
new file mode 100644
index 00000000000..973dcd0496a
--- /dev/null
+++ b/changelogs/unreleased/29398-support-rbac-for-gitlab-provisioned-clusters.yml
@@ -0,0 +1,5 @@
+---
+title: Support Kubernetes RBAC for GitLab Managed Apps when creating new clusters
+merge_request: 21401
+author:
+type: changed
diff --git a/db/migrate/20180907015926_add_legacy_abac_to_cluster_providers_gcp.rb b/db/migrate/20180907015926_add_legacy_abac_to_cluster_providers_gcp.rb
new file mode 100644
index 00000000000..933047e32de
--- /dev/null
+++ b/db/migrate/20180907015926_add_legacy_abac_to_cluster_providers_gcp.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddLegacyAbacToClusterProvidersGcp < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:cluster_providers_gcp, :legacy_abac, :boolean, default: true)
+ end
+
+ def down
+ remove_column(:cluster_providers_gcp, :legacy_abac)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2528a0c1b72..95df735cd37 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180906101639) do
+ActiveRecord::Schema.define(version: 20180907015926) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -620,6 +620,7 @@ ActiveRecord::Schema.define(version: 20180906101639) do
t.string "endpoint"
t.text "encrypted_access_token"
t.string "encrypted_access_token_iv"
+ t.boolean "legacy_abac", default: true, null: false
end
add_index "cluster_providers_gcp", ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true, using: :btree
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 1edc82ee9ef..41768998a59 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -127,8 +127,81 @@ applications running on the cluster.
When GitLab creates the cluster, it enables and uses the legacy
[Attribute-based access control (ABAC)](https://kubernetes.io/docs/admin/authorization/abac/).
The newer [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/)
-authorization will be supported in a
-[future release](https://gitlab.com/gitlab-org/gitlab-ce/issues/29398).
+authorization is [experimental](#role-based-access-control-rbac).
+
+### Role-based access control (RBAC) **[CORE ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21401) in GitLab 11.4.
+
+CAUTION: **Warning:**
+The RBAC authorization is experimental. To enable it you need access to the
+server where GitLab is installed.
+
+The support for RBAC-enabled clusters is hidden behind a feature flag. Once
+the feature flag is enabled, GitLab will create the necessary service accounts
+and privileges in order to install and run [GitLab managed applications](#installing-applications).
+
+To enable the feature flag:
+
+1. SSH into the server where GitLab is installed.
+1. Enter the Rails console:
+
+ **For Omnibus GitLab**
+
+ ```sh
+ sudo gitlab-rails console
+ ```
+
+ **For installations from source**
+
+ ```sh
+ sudo -u git -H bundle exec rails console
+ ```
+
+1. Enable the RBAC authorization:
+
+ ```ruby
+ Feature.enable('rbac_clusters')
+ ```
+
+If you are creating a [new GKE cluster via
+GitLab](#adding-and-creating-a-new-gke-cluster-via-gitlab), you will be
+asked if you would like to create an RBAC-enabled cluster. Enabling this
+setting will create a `gitlab` service account which will be used by
+GitLab to manage the newly created cluster. To enable this, this service
+account will have the `cluster-admin` privilege.
+
+If you are [adding an existing Kubernetes
+cluster](#adding-an-existing-kubernetes-cluster), you will be asked if
+the cluster you are adding is a RBAC-enabled cluster. Ensure the
+token of the account has administrator privileges for the cluster.
+
+In both cases above, when you install Helm Tiller into your cluster, an
+RBAC-enabled cluster will create a `tiller` service account, with `cluster-admin`
+privileges in the `gitlab-managed-apps` namespace. This service account will be
+added to the installed Helm Tiller and will be used by Helm to install and run
+[GitLab managed applications](#installing-applications).
+
+The table below summarizes which resources will be created in a
+RBAC-enabled cluster :
+
+| Name | Kind | Details | Created when |
+| --- | --- | --- | --- |
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+
+
+Helm Tiller will also create additional service accounts and other RBAC
+resources for each installed application. Consult the documentation for the
+Helm charts for each application for details.
+
+NOTE: **Note:**
+Auto DevOps will not successfully complete in a cluster that only has RBAC
+authorization enabled. RBAC support for Auto DevOps is planned in a
+[future release](https://gitlab.com/gitlab-org/gitlab-ce/issues/44597).
### Security of GitLab Runners
@@ -161,13 +234,13 @@ with Tiller already installed, you should be careful as GitLab cannot
detect it. By installing it via the applications will result into having it
twice, which can lead to confusion during deployments.
-| Application | GitLab version | Description |
-| ----------- | :------------: | ----------- |
-| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. |
-| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. |
-| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. |
+| Application | GitLab version | Description | Helm Chart |
+| ----------- | :------------: | ----------- | --------------- |
+| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
+| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
+| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
+| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
+| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
## Getting the external IP address
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index 8312b901524..588238de608 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -25,12 +25,14 @@ module Gitlab
:get_config_map,
:get_namespace,
:get_pod,
+ :get_secret,
:get_service,
:get_service_account,
:delete_pod,
:create_config_map,
:create_namespace,
:create_pod,
+ :create_secret,
:create_service_account,
:update_config_map,
:update_service_account,
diff --git a/lib/gitlab/kubernetes/service_account_token.rb b/lib/gitlab/kubernetes/service_account_token.rb
new file mode 100644
index 00000000000..2e912b26c09
--- /dev/null
+++ b/lib/gitlab/kubernetes/service_account_token.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ class ServiceAccountToken
+ attr_reader :name, :service_account_name, :namespace_name
+
+ def initialize(name, service_account_name, namespace_name)
+ @name = name
+ @service_account_name = service_account_name
+ @namespace_name = namespace_name
+ end
+
+ def generate
+ ::Kubeclient::Resource.new(metadata: metadata, type: service_acount_token_type)
+ end
+
+ private
+
+ # as per https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#to-create-additional-api-tokens
+ def service_acount_token_type
+ 'kubernetes.io/service-account-token'
+ end
+
+ def metadata
+ {
+ name: name,
+ namespace: namespace_name,
+ annotations: {
+ "kubernetes.io/service-account.name": service_account_name
+ }
+ }
+ end
+ end
+ end
+end
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index 36859b4d025..77b6610286f 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -50,7 +50,7 @@ module GoogleApi
service.get_zone_cluster(project_id, zone, cluster_id, options: user_agent_header)
end
- def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:)
+ def projects_zones_clusters_create(project_id, zone, cluster_name, cluster_size, machine_type:, legacy_abac:)
service = Google::Apis::ContainerV1::ContainerService.new
service.authorization = access_token
@@ -63,7 +63,7 @@ module GoogleApi
"machine_type": machine_type
},
"legacy_abac": {
- "enabled": true
+ "enabled": legacy_abac
}
}
}
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 26a532ee01d..97ac11fd171 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -170,12 +170,14 @@ describe Projects::ClustersController do
end
describe 'POST create for new cluster' do
+ let(:legacy_abac_param) { 'true' }
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_gcp_attributes: {
- gcp_project_id: 'gcp-project-12345'
+ gcp_project_id: 'gcp-project-12345',
+ legacy_abac: legacy_abac_param
}
}
}
@@ -201,6 +203,18 @@ describe Projects::ClustersController do
expect(response).to redirect_to(project_cluster_path(project, project.clusters.first))
expect(project.clusters.first).to be_gcp
expect(project.clusters.first).to be_kubernetes
+ expect(project.clusters.first.provider_gcp).to be_legacy_abac
+ end
+
+ context 'when legacy_abac param is false' do
+ let(:legacy_abac_param) { 'false' }
+
+ it 'creates a new cluster with legacy_abac_disabled' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+ expect { go }.to change { Clusters::Cluster.count }
+ .and change { Clusters::Providers::Gcp.count }
+ expect(project.clusters.first.provider_gcp).not_to be_legacy_abac
+ end
end
end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 31e3ebf675d..edc763ad0ad 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -33,6 +33,32 @@ describe 'Gcp Cluster', :js do
context 'when user filled form with valid parameters' do
subject { click_button 'Create Kubernetes cluster' }
+ shared_examples 'valid cluster gcp form' do
+ it 'users sees a form with the GCP token' do
+ expect(page).to have_selector(:css, 'form[data-token="token"]')
+ end
+
+ it 'user sees a cluster details page and creation status' do
+ subject
+
+ expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
+
+ Clusters::Cluster.last.provider.make_created!
+
+ expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine')
+ end
+
+ it 'user sees a error if something wrong during creation' do
+ subject
+
+ expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
+
+ Clusters::Cluster.last.provider.make_errored!('Something wrong!')
+
+ expect(page).to have_content('Something wrong!')
+ end
+ end
+
before do
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
.to receive(:projects_zones_clusters_create) do
@@ -56,28 +82,16 @@ describe 'Gcp Cluster', :js do
fill_in 'cluster[provider_gcp_attributes][machine_type]', with: 'n1-standard-2'
end
- it 'users sees a form with the GCP token' do
- expect(page).to have_selector(:css, 'form[data-token="token"]')
- end
-
- it 'user sees a cluster details page and creation status' do
- subject
-
- expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
-
- Clusters::Cluster.last.provider.make_created!
-
- expect(page).to have_content('Kubernetes cluster was successfully created on Google Kubernetes Engine')
- end
-
- it 'user sees a error if something wrong during creation' do
- subject
+ it_behaves_like 'valid cluster gcp form'
- expect(page).to have_content('Kubernetes cluster is being created on Google Kubernetes Engine...')
+ context 'rbac_clusters feature flag is enabled' do
+ before do
+ stub_feature_flags(rbac_clusters: true)
- Clusters::Cluster.last.provider.make_errored!('Something wrong!')
+ check 'cluster_provider_gcp_attributes_legacy_abac'
+ end
- expect(page).to have_content('Something wrong!')
+ it_behaves_like 'valid cluster gcp form'
end
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index ec968bfcf7d..00941efc8c8 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -21,42 +21,43 @@ describe 'User Cluster', :js do
end
context 'when user filled form with valid parameters' do
+ shared_examples 'valid cluster user form' do
+ it 'user sees a cluster details page' do
+ subject
+
+ expect(page).to have_content('Kubernetes cluster integration')
+ expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
+ .to have_content('http://example.com')
+ expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
+ .to have_content('my-token')
+ end
+ end
+
before do
fill_in 'cluster_name', with: 'dev-cluster'
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token'
- click_button 'Add Kubernetes cluster'
end
- it 'user sees a cluster details page' do
- expect(page).to have_content('Kubernetes cluster integration')
- expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
- expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
- .to have_content('http://example.com')
- expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
- .to have_content('my-token')
- end
- end
+ subject { click_button 'Add Kubernetes cluster' }
- context 'rbac_clusters feature flag is enabled' do
- before do
- stub_feature_flags(rbac_clusters: true)
+ it_behaves_like 'valid cluster user form'
- fill_in 'cluster_name', with: 'dev-cluster'
- fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'http://example.com'
- fill_in 'cluster_platform_kubernetes_attributes_token', with: 'my-token'
- check 'cluster_platform_kubernetes_attributes_authorization_type'
- click_button 'Add Kubernetes cluster'
- end
+ context 'rbac_clusters feature flag is enabled' do
+ before do
+ stub_feature_flags(rbac_clusters: true)
+
+ check 'cluster_platform_kubernetes_attributes_authorization_type'
+ end
+
+ it_behaves_like 'valid cluster user form'
- it 'user sees a cluster details page' do
- expect(page).to have_content('Kubernetes cluster integration')
- expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
- expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
- .to have_content('http://example.com')
- expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
- .to have_content('my-token')
- expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked
+ it 'user sees a cluster details page with RBAC enabled' do
+ subject
+
+ expect(page.find_field('cluster[platform_kubernetes_attributes][authorization_type]', disabled: true)).to be_checked
+ end
end
end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 9146729d139..53c5a4e7c94 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -116,12 +116,14 @@ describe Gitlab::Kubernetes::KubeClient do
:get_config_map,
:get_pod,
:get_namespace,
+ :get_secret,
:get_service,
:get_service_account,
:delete_pod,
:create_config_map,
:create_namespace,
:create_pod,
+ :create_secret,
:create_service_account,
:update_config_map,
:update_service_account
diff --git a/spec/lib/gitlab/kubernetes/service_account_token_spec.rb b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
new file mode 100644
index 00000000000..0773d3d9aec
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/service_account_token_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::ServiceAccountToken do
+ let(:name) { 'token-name' }
+ let(:service_account_name) { 'a_service_account' }
+ let(:namespace_name) { 'a_namespace' }
+ let(:service_account_token) { described_class.new(name, service_account_name, namespace_name) }
+
+ it { expect(service_account_token.name).to eq(name) }
+ it { expect(service_account_token.service_account_name).to eq(service_account_name) }
+ it { expect(service_account_token.namespace_name).to eq(namespace_name) }
+
+ describe '#generate' do
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: {
+ name: name,
+ namespace: namespace_name,
+ annotations: {
+ 'kubernetes.io/service-account.name': service_account_name
+ }
+ },
+ type: 'kubernetes.io/service-account-token'
+ )
+ end
+
+ subject { service_account_token.generate }
+
+ it 'should build a Kubeclient Resource' do
+ is_expected.to eq(resource)
+ end
+ end
+end
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index 27cb3198e5b..e2134dc279c 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -66,25 +66,30 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_clusters_create' do
subject do
client.projects_zones_clusters_create(
- spy, spy, cluster_name, cluster_size, machine_type: machine_type)
+ project_id, zone, cluster_name, cluster_size, machine_type: machine_type, legacy_abac: legacy_abac)
end
+ let(:project_id) { 'project-123' }
+ let(:zone) { 'us-central1-a' }
let(:cluster_name) { 'test-cluster' }
let(:cluster_size) { 1 }
let(:machine_type) { 'n1-standard-2' }
+ let(:legacy_abac) { true }
+ let(:create_cluster_request_body) { double('Google::Apis::ContainerV1::CreateClusterRequest') }
let(:operation) { double }
before do
allow_any_instance_of(Google::Apis::ContainerV1::ContainerService)
- .to receive(:create_cluster).with(any_args, options: user_agent_options)
+ .to receive(:create_cluster).with(any_args)
.and_return(operation)
end
- it { is_expected.to eq(operation) }
-
it 'sets corresponded parameters' do
- expect_any_instance_of(Google::Apis::ContainerV1::CreateClusterRequest)
- .to receive(:initialize).with(
+ expect_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
+
+ expect(Google::Apis::ContainerV1::CreateClusterRequest)
+ .to receive(:new).with(
{
"cluster": {
"name": cluster_name,
@@ -96,9 +101,35 @@ describe GoogleApi::CloudPlatform::Client do
"enabled": true
}
}
- } )
+ } ).and_return(create_cluster_request_body)
+
+ expect(subject).to eq operation
+ end
+
+ context 'create without legacy_abac' do
+ let(:legacy_abac) { false }
+
+ it 'sets corresponded parameters' do
+ expect_any_instance_of(Google::Apis::ContainerV1::ContainerService)
+ .to receive(:create_cluster).with(project_id, zone, create_cluster_request_body, options: user_agent_options)
+
+ expect(Google::Apis::ContainerV1::CreateClusterRequest)
+ .to receive(:new).with(
+ {
+ "cluster": {
+ "name": cluster_name,
+ "initial_node_count": cluster_size,
+ "node_config": {
+ "machine_type": machine_type
+ },
+ "legacy_abac": {
+ "enabled": false
+ }
+ }
+ } ).and_return(create_cluster_request_body)
- subject
+ expect(subject).to eq operation
+ end
end
end
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
index b38b5e6bcad..d134608b538 100644
--- a/spec/models/clusters/providers/gcp_spec.rb
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -74,6 +74,24 @@ describe Clusters::Providers::Gcp do
end
end
+ describe '#legacy_abac?' do
+ let(:gcp) { build(:cluster_provider_gcp) }
+
+ subject { gcp }
+
+ it 'should default to true' do
+ is_expected.to be_legacy_abac
+ end
+
+ context 'legacy_abac is set to false' do
+ let(:gcp) { build(:cluster_provider_gcp, legacy_abac: false) }
+
+ it 'is false' do
+ is_expected.not_to be_legacy_abac
+ end
+ end
+ end
+
describe '#state_machine' do
context 'when any => [:created]' do
let(:gcp) { build(:cluster_provider_gcp, :creating) }
diff --git a/spec/services/ci/fetch_kubernetes_token_service_spec.rb b/spec/services/ci/fetch_kubernetes_token_service_spec.rb
deleted file mode 100644
index 1d05c9671a9..00000000000
--- a/spec/services/ci/fetch_kubernetes_token_service_spec.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-require 'spec_helper'
-
-describe Ci::FetchKubernetesTokenService do
- describe '#execute' do
- subject { described_class.new(api_url, ca_pem, username, password).execute }
-
- let(:api_url) { 'http://111.111.111.111' }
- let(:ca_pem) { '' }
- let(:username) { 'admin' }
- let(:password) { 'xxx' }
-
- context 'when params correct' do
- let(:token) { 'xxx.token.xxx' }
-
- let(:secrets_json) do
- [
- {
- 'metadata': {
- name: metadata_name
- },
- 'data': {
- 'token': Base64.encode64(token)
- }
- }
- ]
- end
-
- before do
- allow_any_instance_of(Kubeclient::Client)
- .to receive(:get_secrets).and_return(secrets_json)
- end
-
- context 'when default-token exists' do
- let(:metadata_name) { 'default-token-123' }
-
- it { is_expected.to eq(token) }
- end
-
- context 'when default-token does not exist' do
- let(:metadata_name) { 'another-token-123' }
-
- it { is_expected.to be_nil }
- end
- end
-
- context 'when api_url is nil' do
- let(:api_url) { nil }
-
- it { expect { subject }.to raise_error("Incomplete settings") }
- end
-
- context 'when username is nil' do
- let(:username) { nil }
-
- it { expect { subject }.to raise_error("Incomplete settings") }
- end
-
- context 'when password is nil' do
- let(:password) { nil }
-
- it { expect { subject }.to raise_error("Incomplete settings") }
- end
- 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 0cf91307589..0f484222228 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -12,9 +12,11 @@ describe Clusters::Gcp::FinalizeCreationService do
let(:zone) { provider.zone }
let(:cluster_name) { cluster.name }
+ subject { described_class.new.execute(provider) }
+
shared_examples 'success' do
it 'configures provider and kubernetes' do
- described_class.new.execute(provider)
+ subject
expect(provider).to be_created
end
@@ -22,7 +24,7 @@ describe Clusters::Gcp::FinalizeCreationService do
shared_examples 'error' do
it 'sets an error to provider object' do
- described_class.new.execute(provider)
+ subject
expect(provider.reload).to be_errored
end
@@ -33,6 +35,7 @@ describe Clusters::Gcp::FinalizeCreationService do
let(:api_url) { 'https://' + endpoint }
let(:username) { 'sample-username' }
let(:password) { 'sample-password' }
+ let(:secret_name) { 'gitlab-token' }
before do
stub_cloud_platform_get_zone_cluster(
@@ -43,60 +46,102 @@ describe Clusters::Gcp::FinalizeCreationService do
password: password
}
)
-
- stub_kubeclient_discover(api_url)
end
- context 'when suceeded to fetch kuberenetes token' do
- let(:token) { 'sample-token' }
-
+ context 'service account and token created' do
before do
- stub_kubeclient_get_secrets(
- api_url,
- {
- token: Base64.encode64(token)
- } )
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_create_service_account(api_url)
+ stub_kubeclient_create_secret(api_url)
end
- it_behaves_like 'success'
+ 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 'has corresponded data' do
- described_class.new.execute(provider)
- cluster.reload
- provider.reload
- platform.reload
+ it 'properly configures database models' do
+ subject
- 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)
+ 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
- end
- context 'when default-token is not found' do
- before do
- stub_kubeclient_get_secrets(api_url, metadata_name: 'aaaa')
+ 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
- it_behaves_like 'error'
- end
+ context 'when token is empty' do
+ before do
+ stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name)
+ end
- context 'when token is empty' do
- before do
- stub_kubeclient_get_secrets(api_url, token: '')
+ it_behaves_like 'error'
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
- context 'when failed to fetch kuberenetes token' do
- before do
- stub_kubeclient_get_secrets_error(api_url)
+ it_behaves_like 'error'
end
- it_behaves_like 'error'
+ 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
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
new file mode 100644
index 00000000000..065d021db5e
--- /dev/null
+++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
+ include KubernetesHelpers
+
+ let(:service) { described_class.new(kubeclient, rbac: rbac) }
+
+ describe '#execute' do
+ let(:rbac) { false }
+ let(:api_url) { 'http://111.111.111.111' }
+ let(:username) { 'admin' }
+ let(:password) { 'xxx' }
+
+ let(:kubeclient) do
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ ['api', 'apis/rbac.authorization.k8s.io'],
+ auth_options: { username: username, password: password }
+ )
+ end
+
+ subject { service.execute }
+
+ 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)
+ 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(
+ body: hash_including(
+ kind: 'ServiceAccount',
+ metadata: { name: 'gitlab', namespace: 'default' }
+ )
+ )
+ end
+
+ it 'creates a kubernetes secret of type ServiceAccountToken' do
+ subject
+
+ expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with(
+ body: hash_including(
+ kind: 'Secret',
+ metadata: {
+ name: 'gitlab-token',
+ namespace: 'default',
+ annotations: {
+ 'kubernetes.io/service-account.name': 'gitlab'
+ }
+ },
+ type: 'kubernetes.io/service-account-token'
+ )
+ )
+ end
+ end
+
+ context 'abac enabled cluster' do
+ it_behaves_like 'creates service account and token'
+ end
+
+ context 'rbac enabled cluster' do
+ let(:rbac) { true }
+
+ before do
+ stub_kubeclient_create_cluster_role_binding(api_url)
+ end
+
+ it_behaves_like 'creates service account and token'
+
+ it 'creates a kubernetes cluster role binding' do
+ subject
+
+ expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with(
+ body: hash_including(
+ kind: 'ClusterRoleBinding',
+ metadata: { name: 'gitlab-admin' },
+ roleRef: {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'ClusterRole',
+ name: 'cluster-admin'
+ },
+ subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }]
+ )
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb
new file mode 100644
index 00000000000..c543de21d5b
--- /dev/null
+++ b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
+ describe '#execute' do
+ let(:api_url) { 'http://111.111.111.111' }
+ let(:username) { 'admin' }
+ let(:password) { 'xxx' }
+
+ 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).execute }
+
+ context 'when params correct' do
+ let(:decoded_token) { 'xxx.token.xxx' }
+ let(:token) { Base64.encode64(decoded_token) }
+
+ let(:secret_json) do
+ {
+ 'metadata': {
+ name: 'gitlab-token'
+ },
+ 'data': {
+ 'token': token
+ }
+ }
+ end
+
+ before do
+ allow_any_instance_of(Kubeclient::Client)
+ .to receive(:get_secret).and_return(secret_json)
+ end
+
+ context 'when gitlab-token exists' do
+ let(:metadata_name) { 'gitlab-token' }
+
+ it { is_expected.to eq(decoded_token) }
+ end
+
+ context 'when gitlab-token does not exist' do
+ let(:secret_json) { {} }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when token is nil' do
+ let(:token) { nil }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 994a2aaef90..c077ca9f15b 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -33,31 +33,49 @@ module KubernetesHelpers
WebMock.stub_request(:get, deployments_url).to_return(response || kube_deployments_response)
end
- def stub_kubeclient_get_secrets(api_url, **options)
- WebMock.stub_request(:get, api_url + '/api/v1/secrets')
- .to_return(kube_response(kube_v1_secrets_body(options)))
+ def stub_kubeclient_get_secret(api_url, namespace: 'default', **options)
+ options[:metadata_name] ||= "default-token-1"
+
+ WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{options[:metadata_name]}")
+ .to_return(kube_response(kube_v1_secret_body(options)))
end
- def stub_kubeclient_get_secrets_error(api_url)
- WebMock.stub_request(:get, api_url + '/api/v1/secrets')
+ def stub_kubeclient_get_secret_error(api_url, name, namespace: 'default')
+ WebMock.stub_request(:get, api_url + "/api/v1/namespaces/#{namespace}/secrets/#{name}")
.to_return(status: [404, "Internal Server Error"])
end
- def kube_v1_secrets_body(**options)
+ def stub_kubeclient_create_service_account(api_url, namespace: 'default')
+ WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts")
+ .to_return(kube_response({}))
+ end
+
+ def stub_kubeclient_create_service_account_error(api_url, namespace: 'default')
+ WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts")
+ .to_return(status: [500, "Internal Server Error"])
+ end
+
+ def stub_kubeclient_create_secret(api_url, namespace: 'default')
+ WebMock.stub_request(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets")
+ .to_return(kube_response({}))
+ end
+
+ def stub_kubeclient_create_cluster_role_binding(api_url)
+ WebMock.stub_request(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings')
+ .to_return(kube_response({}))
+ end
+
+ def kube_v1_secret_body(**options)
{
"kind" => "SecretList",
"apiVersion": "v1",
- "items" => [
- {
- "metadata": {
- "name": options[:metadata_name] || "default-token-1",
- "namespace": "kube-system"
- },
- "data": {
- "token": options[:token] || Base64.encode64('token-sample-123')
- }
- }
- ]
+ "metadata": {
+ "name": options[:metadata_name] || "default-token-1",
+ "namespace": "kube-system"
+ },
+ "data": {
+ "token": options[:token] || Base64.encode64('token-sample-123')
+ }
}
end
@@ -68,6 +86,7 @@ module KubernetesHelpers
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
+ { "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
{ "name" => "services", "namespaced" => true, "kind" => "Service" }
]
}
@@ -80,6 +99,7 @@ module KubernetesHelpers
{ "name" => "pods", "namespaced" => true, "kind" => "Pod" },
{ "name" => "deployments", "namespaced" => true, "kind" => "Deployment" },
{ "name" => "secrets", "namespaced" => true, "kind" => "Secret" },
+ { "name" => "serviceaccounts", "namespaced" => true, "kind" => "ServiceAccount" },
{ "name" => "services", "namespaced" => true, "kind" => "Service" }
]
}
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index 43a2fd05498..22f712f3fcf 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -7,7 +7,8 @@ shared_context 'valid cluster create params' do
gcp_project_id: 'gcp-project',
zone: 'us-central1-a',
num_nodes: 1,
- machine_type: 'machine_type-a'
+ machine_type: 'machine_type-a',
+ legacy_abac: 'true'
}
}
end
@@ -29,6 +30,10 @@ shared_context 'invalid cluster create params' do
end
shared_examples 'create cluster service success' do
+ before do
+ stub_feature_flags(rbac_clusters: false)
+ end
+
it 'creates a cluster object and performs a worker' do
expect(ClusterProvisionWorker).to receive(:perform_async)
@@ -44,6 +49,7 @@ shared_examples 'create cluster service success' do
expect(subject.provider.num_nodes).to eq(1)
expect(subject.provider.machine_type).to eq('machine_type-a')
expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
expect(subject.platform).to be_nil
end
end