Commit 55477962 authored by Thong Kuah's avatar Thong Kuah Committed by Kamil Trzciński
Browse files

Enable Kubernetes RBAC for GitLab Managed Apps for existing clusters

parent a8bc05ccac7d
......@@ -157,7 +157,8 @@ def create_user_cluster_params
:namespace,
:api_url,
:token,
:ca_cert
:ca_cert,
:authorization_type
]).merge(
provider_type: :user,
platform_type: :kubernetes
......
......@@ -11,4 +11,8 @@ def render_gcp_signup_offer
render 'projects/clusters/gcp_signup_offer_banner'
end
end
def rbac_clusters_feature_enabled?
Feature.enabled?(:rbac_clusters)
end
end
......@@ -32,7 +32,8 @@ def set_initial_status
def install_command
Gitlab::Kubernetes::Helm::InitCommand.new(
name: name,
files: files
files: files,
rbac: cluster.platform_kubernetes_rbac?
)
end
......
......@@ -39,6 +39,7 @@ def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files
)
......
......@@ -40,6 +40,7 @@ def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
repository: repository
......
......@@ -48,6 +48,7 @@ def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files
)
......@@ -71,7 +72,7 @@ def prometheus_client
private
def kube_client
cluster&.kubeclient
cluster&.kubeclient&.core_client
end
end
end
......
......@@ -33,6 +33,7 @@ def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
version: VERSION,
rbac: cluster.platform_kubernetes_rbac?,
chart: chart,
files: files,
repository: repository
......
......@@ -42,6 +42,7 @@ class Cluster < ActiveRecord::Base
delegate :on_creation?, to: :provider, allow_nil: true
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :installed?, to: :application_helm, prefix: true, allow_nil: true
delegate :installed?, to: :application_ingress, prefix: true, allow_nil: true
......
......@@ -5,6 +5,7 @@ module Platforms
class Kubernetes < ActiveRecord::Base
include Gitlab::Kubernetes
include ReactiveCaching
include EnumWithNil
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
......@@ -47,6 +48,12 @@ class Kubernetes < ActiveRecord::Base
alias_method :active?, :enabled?
enum_with_nil authorization_type: {
unknown_authorization: nil,
rbac: 1,
abac: 2
}
def actual_namespace
if namespace.present?
namespace
......@@ -95,7 +102,7 @@ def calculate_reactive_cache
end
def kubeclient
@kubeclient ||= build_kubeclient!
@kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io'])
end
private
......@@ -115,15 +122,16 @@ def default_namespace
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end
def build_kubeclient!(api_path: 'api', api_version: 'v1')
def build_kube_client!(api_groups: ['api'], api_version: 'v1')
raise "Incomplete settings" unless api_url && actual_namespace
unless (username && password) || token
raise "Either username/password or token is required to access API"
end
::Kubeclient::Client.new(
join_api_url(api_path),
Gitlab::Kubernetes::KubeClient.new(
api_url,
api_groups,
api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
......@@ -133,7 +141,7 @@ def build_kubeclient!(api_path: 'api', api_version: 'v1')
# Returns a hash of all pods in the namespace
def read_pods
kubeclient = build_kubeclient!
kubeclient = build_kube_client!
kubeclient.get_pods(namespace: actual_namespace).as_json
rescue Kubeclient::HttpError => err
......@@ -157,15 +165,6 @@ def kubeclient_auth_options
{ bearer_token: token }
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 terminal_auth
{
token: token,
......
......@@ -96,10 +96,10 @@ def actual_namespace
# Check we can connect to the Kubernetes API
def test(*args)
kubeclient = build_kubeclient!
kubeclient = build_kube_client!
kubeclient.discover
{ success: kubeclient.discovered, result: "Checked API discovery endpoint" }
kubeclient.core_client.discover
{ success: kubeclient.core_client.discovered, result: "Checked API discovery endpoint" }
rescue => err
{ success: false, result: err }
end
......@@ -144,7 +144,7 @@ def calculate_reactive_cache
end
def kubeclient
@kubeclient ||= build_kubeclient!
@kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io'])
end
def deprecated?
......@@ -182,11 +182,12 @@ def default_namespace
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end
def build_kubeclient!(api_path: 'api', api_version: 'v1')
def build_kube_client!(api_groups: ['api'], api_version: 'v1')
raise "Incomplete settings" unless api_url && actual_namespace && token
::Kubeclient::Client.new(
join_api_url(api_path),
Gitlab::Kubernetes::KubeClient.new(
api_url,
api_groups,
api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
......@@ -196,7 +197,7 @@ def build_kubeclient!(api_path: 'api', api_version: 'v1')
# Returns a hash of all pods in the namespace
def read_pods
kubeclient = build_kubeclient!
kubeclient = build_kube_client!
kubeclient.get_pods(namespace: actual_namespace).as_json
rescue Kubeclient::HttpError => err
......@@ -220,15 +221,6 @@ def kubeclient_auth_options
{ bearer_token: token }
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 terminal_auth
{
token: token,
......
......@@ -25,5 +25,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
= 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' }, '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|Add Kubernetes cluster'), class: 'btn btn-success'
......@@ -26,5 +26,14 @@
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold'
= 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'
---
title: Support Kubernetes RBAC for GitLab Managed Apps when adding a existing cluster
merge_request: 21127
author:
type: changed
# frozen_string_literal: true
class AddAuthorizationTypeToClusterPlatformsKubernetes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :cluster_platforms_kubernetes, :authorization_type, :integer, limit: 2
end
end
......@@ -588,6 +588,7 @@
t.string "encrypted_password_iv"
t.text "encrypted_token"
t.string "encrypted_token_iv"
t.integer "authorization_type", limit: 2
end
add_index "cluster_platforms_kubernetes", ["cluster_id"], name: "index_cluster_platforms_kubernetes_on_cluster_id", unique: true, using: :btree
......
# frozen_string_literal: true
module Gitlab
module Kubernetes
class ClusterRoleBinding
attr_reader :name, :cluster_role_name, :subjects
def initialize(name, cluster_role_name, subjects)
@name = name
@cluster_role_name = cluster_role_name
@subjects = subjects
end
def generate
::Kubeclient::Resource.new.tap do |resource|
resource.metadata = metadata
resource.roleRef = role_ref
resource.subjects = subjects
end
end
private
def metadata
{ name: name }
end
def role_ref
{
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: cluster_role_name
}
end
end
end
end
......@@ -3,6 +3,9 @@ module Kubernetes
module Helm
HELM_VERSION = '2.7.2'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
SERVICE_ACCOUNT = 'tiller'.freeze
CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze
CLUSTER_ROLE = 'cluster-admin'.freeze
end
end
end
......@@ -9,7 +9,11 @@ def initialize(kubeclient)
def install(command)
namespace.ensure_exists!
create_service_account(command)
create_cluster_role_binding(command)
create_config_map(command)
kubeclient.create_pod(command.pod_resource)
end
......@@ -41,6 +45,50 @@ def create_config_map(command)
kubeclient.create_config_map(config_map_resource)
end
end
def create_service_account(command)
command.service_account_resource.tap do |service_account_resource|
break unless service_account_resource
if service_account_exists?(service_account_resource)
kubeclient.update_service_account(service_account_resource)
else
kubeclient.create_service_account(service_account_resource)
end
end
end
def create_cluster_role_binding(command)
command.cluster_role_binding_resource.tap do |cluster_role_binding_resource|
break unless cluster_role_binding_resource
if cluster_role_binding_exists?(cluster_role_binding_resource)
kubeclient.update_cluster_role_binding(cluster_role_binding_resource)
else
kubeclient.create_cluster_role_binding(cluster_role_binding_resource)
end
end
end
def service_account_exists?(resource)
resource_exists? do
kubeclient.get_service_account(resource.metadata.name, resource.metadata.namespace)
end
end
def cluster_role_binding_exists?(resource)
resource_exists? do
kubeclient.get_cluster_role_binding(resource.metadata.name)
end
end
def resource_exists?
yield
rescue ::Kubeclient::HttpError => e
raise e unless e.error_code == 404
false
end
end
end
end
......
......@@ -3,7 +3,9 @@ module Kubernetes
module Helm
module BaseCommand
def pod_resource
Gitlab::Kubernetes::Helm::Pod.new(self, namespace).generate
pod_service_account_name = rbac? ? service_account_name : nil
Gitlab::Kubernetes::Helm::Pod.new(self, namespace, service_account_name: pod_service_account_name).generate
end
def generate_script
......@@ -26,6 +28,14 @@ def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, files).generate
end
def service_account_resource
nil
end
def cluster_role_binding_resource
nil
end
def file_names
files.keys
end
......@@ -34,6 +44,10 @@ def name
raise "Not implemented"
end
def rbac?
raise "Not implemented"
end
def files
raise "Not implemented"
end
......@@ -47,6 +61,10 @@ def files_dir
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
def service_account_name
Gitlab::Kubernetes::Helm::SERVICE_ACCOUNT
end
end
end
end
......
......@@ -6,9 +6,10 @@ class InitCommand
attr_reader :name, :files
def initialize(name:, files:)
def initialize(name:, files:, rbac:)
@name = name
@files = files
@rbac = rbac
end
def generate_script
......@@ -17,15 +18,62 @@ def generate_script
].join("\n")
end
def rbac?
@rbac
end
def service_account_resource
return unless rbac?
Gitlab::Kubernetes::ServiceAccount.new(service_account_name, namespace).generate
end
def cluster_role_binding_resource
return unless rbac?
subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: namespace }]
Gitlab::Kubernetes::ClusterRoleBinding.new(
cluster_role_binding_name,
cluster_role_name,
subjects
).generate
end
private
def init_helm_command
tls_flags = "--tiller-tls" \
" --tiller-tls-verify --tls-ca-cert #{files_dir}/ca.pem" \
" --tiller-tls-cert #{files_dir}/cert.pem" \
" --tiller-tls-key #{files_dir}/key.pem"
command = %w[helm init] + init_command_flags
command.shelljoin + " >/dev/null\n"
end
def init_command_flags
tls_flags + optional_service_account_flag
end
def tls_flags
[
'--tiller-tls',
'--tiller-tls-verify',
'--tls-ca-cert', "#{files_dir}/ca.pem",
'--tiller-tls-cert', "#{files_dir}/cert.pem",
'--tiller-tls-key', "#{files_dir}/key.pem"
]
end
def optional_service_account_flag
return [] unless rbac?
['--service-account', service_account_name]
end
def cluster_role_binding_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE_BINDING
end
"helm init #{tls_flags} >/dev/null"
def cluster_role_name
Gitlab::Kubernetes::Helm::CLUSTER_ROLE
end
end
end
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment