diff options
author | Thong Kuah <tkuah@gitlab.com> | 2018-09-06 10:03:38 +0000 |
---|---|---|
committer | Kamil TrzciĆski <ayufan@ayufan.eu> | 2018-09-06 10:03:38 +0000 |
commit | 6f2ad2b6041b8a007df7eb8c4f477c24cc153ac3 (patch) | |
tree | 7b190f17b6da295cf3599174f48c0fbc060ddbb1 /lib | |
parent | a2ea32dd44cc4a104e404325c73a77151913a946 (diff) | |
download | gitlab-ce-6f2ad2b6041b8a007df7eb8c4f477c24cc153ac3.tar.gz |
Enable Kubernetes RBAC for GitLab Managed Apps for existing clusters
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gitlab/kubernetes/cluster_role_binding.rb | 37 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/helm.rb | 3 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/helm/api.rb | 48 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/helm/base_command.rb | 20 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/helm/init_command.rb | 60 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/helm/install_command.rb | 52 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/helm/pod.rb | 6 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/kube_client.rb | 110 | ||||
-rw-r--r-- | lib/gitlab/kubernetes/service_account.rb | 27 |
9 files changed, 342 insertions, 21 deletions
diff --git a/lib/gitlab/kubernetes/cluster_role_binding.rb b/lib/gitlab/kubernetes/cluster_role_binding.rb new file mode 100644 index 00000000000..ebea8aff5be --- /dev/null +++ b/lib/gitlab/kubernetes/cluster_role_binding.rb @@ -0,0 +1,37 @@ +# 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 diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb index 530ccf88053..4a1bdf34c3e 100644 --- a/lib/gitlab/kubernetes/helm.rb +++ b/lib/gitlab/kubernetes/helm.rb @@ -3,6 +3,9 @@ module Gitlab 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 diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb index d65374cc23b..2dd74c68075 100644 --- a/lib/gitlab/kubernetes/helm/api.rb +++ b/lib/gitlab/kubernetes/helm/api.rb @@ -9,7 +9,11 @@ module Gitlab 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 @@ module Gitlab 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 diff --git a/lib/gitlab/kubernetes/helm/base_command.rb b/lib/gitlab/kubernetes/helm/base_command.rb index afcfd109de0..6752f2cff43 100644 --- a/lib/gitlab/kubernetes/helm/base_command.rb +++ b/lib/gitlab/kubernetes/helm/base_command.rb @@ -3,7 +3,9 @@ module Gitlab 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 @@ module Gitlab 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 @@ module Gitlab raise "Not implemented" end + def rbac? + raise "Not implemented" + end + def files raise "Not implemented" end @@ -47,6 +61,10 @@ module Gitlab def namespace Gitlab::Kubernetes::Helm::NAMESPACE end + + def service_account_name + Gitlab::Kubernetes::Helm::SERVICE_ACCOUNT + end end end end diff --git a/lib/gitlab/kubernetes/helm/init_command.rb b/lib/gitlab/kubernetes/helm/init_command.rb index a4546509515..c7046a9ea75 100644 --- a/lib/gitlab/kubernetes/helm/init_command.rb +++ b/lib/gitlab/kubernetes/helm/init_command.rb @@ -6,9 +6,10 @@ module Gitlab 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 @@ module Gitlab ].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 diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb index 9672f80687e..1be7924d6ac 100644 --- a/lib/gitlab/kubernetes/helm/install_command.rb +++ b/lib/gitlab/kubernetes/helm/install_command.rb @@ -6,10 +6,11 @@ module Gitlab attr_reader :name, :files, :chart, :version, :repository - def initialize(name:, chart:, files:, version: nil, repository: nil) + def initialize(name:, chart:, files:, rbac:, version: nil, repository: nil) @name = name @chart = chart @version = version + @rbac = rbac @files = files @repository = repository end @@ -22,6 +23,10 @@ module Gitlab ].compact.join("\n") end + def rbac? + @rbac + end + private def init_command @@ -29,28 +34,51 @@ module Gitlab end def repository_command - "helm repo add #{name} #{repository}" if repository + ['helm', 'repo', 'add', name, repository].shelljoin if repository end def script_command - init_flags = "--name #{name}#{optional_tls_flags}#{optional_version_flag}" \ - " --namespace #{Gitlab::Kubernetes::Helm::NAMESPACE}" \ - " -f /data/helm/#{name}/config/values.yaml" + command = ['helm', 'install', chart] + install_command_flags + + command.shelljoin + " >/dev/null\n" + end + + def install_command_flags + name_flag = ['--name', name] + namespace_flag = ['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE] + value_flag = ['-f', "/data/helm/#{name}/config/values.yaml"] - "helm install #{chart} #{init_flags} >/dev/null\n" + name_flag + + optional_tls_flags + + optional_version_flag + + optional_rbac_create_flag + + namespace_flag + + value_flag + end + + def optional_rbac_create_flag + return [] unless rbac? + + # jupyterhub helm chart is using rbac.enabled + # https://github.com/jupyterhub/zero-to-jupyterhub-k8s/tree/master/jupyterhub + %w[--set rbac.create=true,rbac.enabled=true] end def optional_version_flag - " --version #{version}" if version + return [] unless version + + ['--version', version] end def optional_tls_flags - return unless files.key?(:'ca.pem') + return [] unless files.key?(:'ca.pem') - " --tls" \ - " --tls-ca-cert #{files_dir}/ca.pem" \ - " --tls-cert #{files_dir}/cert.pem" \ - " --tls-key #{files_dir}/key.pem" + [ + '--tls', + '--tls-ca-cert', "#{files_dir}/ca.pem", + '--tls-cert', "#{files_dir}/cert.pem", + '--tls-key', "#{files_dir}/key.pem" + ] end end end diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb index 6e5d3388405..95192b11c0d 100644 --- a/lib/gitlab/kubernetes/helm/pod.rb +++ b/lib/gitlab/kubernetes/helm/pod.rb @@ -2,9 +2,10 @@ module Gitlab module Kubernetes module Helm class Pod - def initialize(command, namespace_name) + def initialize(command, namespace_name, service_account_name: nil) @command = command @namespace_name = namespace_name + @service_account_name = service_account_name end def generate @@ -12,13 +13,14 @@ module Gitlab spec[:volumes] = volumes_specification spec[:containers][0][:volumeMounts] = volume_mounts_specification + spec[:serviceAccountName] = service_account_name if service_account_name ::Kubeclient::Resource.new(metadata: metadata, spec: spec) end private - attr_reader :command, :namespace_name, :kubeclient, :config_map + attr_reader :command, :namespace_name, :service_account_name def container_specification { diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb new file mode 100644 index 00000000000..8312b901524 --- /dev/null +++ b/lib/gitlab/kubernetes/kube_client.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'uri' + +module Gitlab + module Kubernetes + # Wrapper around Kubeclient::Client to dispatch + # the right message to the client that can respond to the message. + # We must have a kubeclient for each ApiGroup as there is no + # other way to use the Kubeclient gem. + # + # See https://github.com/abonas/kubeclient/issues/348. + class KubeClient + include Gitlab::Utils::StrongMemoize + + SUPPORTED_API_GROUPS = [ + 'api', + 'apis/rbac.authorization.k8s.io', + 'apis/extensions' + ].freeze + + # Core API methods delegates to the core api group client + delegate :get_pods, + :get_secrets, + :get_config_map, + :get_namespace, + :get_pod, + :get_service, + :get_service_account, + :delete_pod, + :create_config_map, + :create_namespace, + :create_pod, + :create_service_account, + :update_config_map, + :update_service_account, + to: :core_client + + # RBAC methods delegates to the apis/rbac.authorization.k8s.io api + # group client + delegate :create_cluster_role_binding, + :get_cluster_role_binding, + :update_cluster_role_binding, + to: :rbac_client + + # Deployments resource is currently on the apis/extensions api group + delegate :get_deployments, + to: :extensions_client + + # non-entity methods that can only work with the core client + # as it uses the pods/log resource + delegate :get_pod_log, + :watch_pod_log, + to: :core_client + + def initialize(api_prefix, api_groups = ['api'], api_version = 'v1', **kubeclient_options) + raise ArgumentError unless check_api_groups_supported?(api_groups) + + @api_prefix = api_prefix + @api_groups = api_groups + @api_version = api_version + @kubeclient_options = kubeclient_options + end + + def discover! + clients.each(&:discover) + end + + def clients + hashed_clients.values + end + + def core_client + hashed_clients['api'] + end + + def rbac_client + hashed_clients['apis/rbac.authorization.k8s.io'] + end + + def extensions_client + hashed_clients['apis/extensions'] + end + + def hashed_clients + strong_memoize(:hashed_clients) do + @api_groups.map do |api_group| + api_url = join_api_url(@api_prefix, api_group) + [api_group, ::Kubeclient::Client.new(api_url, @api_version, **@kubeclient_options)] + end.to_h + end + end + + private + + def check_api_groups_supported?(api_groups) + api_groups.all? {|api_group| SUPPORTED_API_GROUPS.include?(api_group) } + end + + def join_api_url(api_prefix, api_path) + url = URI.parse(api_prefix) + prefix = url.path.sub(%r{/+\z}, '') + + url.path = [prefix, api_path].join("/") + + url.to_s + end + end + end +end diff --git a/lib/gitlab/kubernetes/service_account.rb b/lib/gitlab/kubernetes/service_account.rb new file mode 100644 index 00000000000..d58fc1c3976 --- /dev/null +++ b/lib/gitlab/kubernetes/service_account.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module Kubernetes + class ServiceAccount + attr_reader :name, :namespace_name + + def initialize(name, namespace_name) + @name = name + @namespace_name = namespace_name + end + + def generate + ::Kubeclient::Resource.new(metadata: metadata) + end + + private + + def metadata + { + name: name, + namespace: namespace_name + } + end + end + end +end |