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
This commit is contained in:
commit
5a8908bf58
30 changed files with 770 additions and 270 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
13
app/services/clusters/gcp/kubernetes.rb
Normal file
13
app/services/clusters/gcp/kubernetes.rb
Normal file
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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}")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support Kubernetes RBAC for GitLab Managed Apps when creating new clusters
|
||||
merge_request: 21401
|
||||
author:
|
||||
type: changed
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
36
lib/gitlab/kubernetes/service_account_token.rb
Normal file
36
lib/gitlab/kubernetes/service_account_token.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"]')
|
||||
it_behaves_like 'valid cluster gcp form'
|
||||
|
||||
context 'rbac_clusters feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(rbac_clusters: true)
|
||||
|
||||
check 'cluster_provider_gcp_attributes_legacy_abac'
|
||||
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!')
|
||||
it_behaves_like 'valid cluster gcp form'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,14 +21,10 @@ describe 'User Cluster', :js do
|
|||
end
|
||||
|
||||
context 'when user filled form with valid parameters' do
|
||||
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
|
||||
|
||||
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)
|
||||
|
@ -38,27 +34,32 @@ describe 'User Cluster', :js do
|
|||
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'
|
||||
end
|
||||
|
||||
subject { click_button 'Add Kubernetes cluster' }
|
||||
|
||||
it_behaves_like 'valid cluster user form'
|
||||
|
||||
context 'rbac_clusters feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(rbac_clusters: true)
|
||||
|
||||
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
|
||||
|
||||
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')
|
||||
it_behaves_like 'valid cluster user form'
|
||||
|
||||
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
|
||||
|
||||
context 'when user filled form with invalid parameters' do
|
||||
before do
|
||||
|
|
|
@ -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
|
||||
|
|
35
spec/lib/gitlab/kubernetes/service_account_token_spec.rb
Normal file
35
spec/lib/gitlab/kubernetes/service_account_token_spec.rb
Normal file
|
@ -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
|
|
@ -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)
|
||||
|
||||
subject
|
||||
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)
|
||||
|
||||
expect(subject).to eq operation
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
|
@ -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,61 +46,103 @@ describe Clusters::Gcp::FinalizeCreationService do
|
|||
password: password
|
||||
}
|
||||
)
|
||||
|
||||
stub_kubeclient_discover(api_url)
|
||||
end
|
||||
|
||||
context 'when suceeded to fetch kuberenetes token' do
|
||||
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_secrets(
|
||||
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)
|
||||
it 'properly configures database models' do
|
||||
subject
|
||||
|
||||
cluster.reload
|
||||
provider.reload
|
||||
platform.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 'when default-token is not found' do
|
||||
context 'provider legacy_abac is disabled' do
|
||||
before do
|
||||
stub_kubeclient_get_secrets(api_url, metadata_name: 'aaaa')
|
||||
provider.legacy_abac = false
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
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_secrets(api_url, token: '')
|
||||
stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name)
|
||||
end
|
||||
|
||||
it_behaves_like 'error'
|
||||
end
|
||||
|
||||
context 'when failed to fetch kuberenetes token' do
|
||||
context 'when failed to fetch kubernetes token' do
|
||||
before do
|
||||
stub_kubeclient_get_secrets_error(api_url)
|
||||
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
|
||||
end
|
||||
|
||||
context 'when failed to fetch gke cluster info' do
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -33,22 +33,42 @@ 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"
|
||||
|
@ -57,8 +77,6 @@ module KubernetesHelpers
|
|||
"token": options[:token] || Base64.encode64('token-sample-123')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def kube_v1_discovery_body
|
||||
|
@ -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" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue