Skip to content
Snippets Groups Projects
Commit fc134096 authored by Mayra Cabrera's avatar Mayra Cabrera Committed by Dmitriy Zaporozhets
Browse files

Resolve "Mutual SSL Auth For Helm TIller"

parent b3deca7a
No related branches found
No related tags found
1 merge request!10495Merge Requests - Assignee
Showing
with 262 additions and 52 deletions
# frozen_string_literal: true
 
require 'openssl'
module Clusters
module Applications
class Helm < ActiveRecord::Base
self.table_name = 'clusters_applications_helm'
 
attr_encrypted :ca_key,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
 
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
 
before_create :create_keys_and_certs
def issue_client_cert
ca_cert_obj.issue
end
def set_initial_status
return unless not_installable?
 
Loading
Loading
@@ -17,7 +30,41 @@ module Clusters
end
 
def install_command
Gitlab::Kubernetes::Helm::InitCommand.new(name)
Gitlab::Kubernetes::Helm::InitCommand.new(
name: name,
files: files
)
end
def has_ssl?
ca_key.present? && ca_cert.present?
end
private
def files
{
'ca.pem': ca_cert,
'cert.pem': tiller_cert.cert_string,
'key.pem': tiller_cert.key_string
}
end
def create_keys_and_certs
ca_cert = Gitlab::Kubernetes::Helm::Certificate.generate_root
self.ca_key = ca_cert.key_string
self.ca_cert = ca_cert.cert_string
end
def tiller_cert
@tiller_cert ||= ca_cert_obj.issue(expires_in: Gitlab::Kubernetes::Helm::Certificate::INFINITE_EXPIRY)
end
def ca_cert_obj
return unless has_ssl?
Gitlab::Kubernetes::Helm::Certificate
.from_strings(ca_key, ca_cert)
end
end
end
Loading
Loading
Loading
Loading
@@ -37,10 +37,10 @@ module Clusters
 
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
name: name,
version: VERSION,
chart: chart,
values: values
files: files
)
end
 
Loading
Loading
Loading
Loading
@@ -38,10 +38,10 @@ module Clusters
 
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
name: name,
version: VERSION,
chart: chart,
values: values,
files: files,
repository: repository
)
end
Loading
Loading
Loading
Loading
@@ -46,10 +46,10 @@ module Clusters
 
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
name: name,
version: VERSION,
chart: chart,
values: values
files: files
)
end
 
Loading
Loading
Loading
Loading
@@ -31,10 +31,10 @@ module Clusters
 
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name,
name: name,
version: VERSION,
chart: chart,
values: values,
files: files,
repository: repository
)
end
Loading
Loading
Loading
Loading
@@ -14,8 +14,34 @@ module Clusters
File.read(chart_values_file)
end
 
def files
@files ||= begin
files = { 'values.yaml': values }
files.merge!(certificate_files) if cluster.application_helm.has_ssl?
files
end
end
private
 
def certificate_files
{
'ca.pem': ca_cert,
'cert.pem': helm_cert.cert_string,
'key.pem': helm_cert.key_string
}
end
def ca_cert
cluster.application_helm.ca_cert
end
def helm_cert
@helm_cert ||= cluster.application_helm.issue_client_cert
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
Loading
Loading
---
title: Ensure installed Helm Tiller For GitLab Managed Apps Is protected by mutual
auth
merge_request: 20928
author:
type: changed
# frozen_string_literal: true
class AddColumnsForHelmTillerCertificates < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :clusters_applications_helm, :encrypted_ca_key, :text
add_column :clusters_applications_helm, :encrypted_ca_key_iv, :text
add_column :clusters_applications_helm, :ca_cert, :text
end
end
Loading
Loading
@@ -637,6 +637,9 @@ ActiveRecord::Schema.define(version: 20180726172057) do
t.integer "status", null: false
t.string "version", null: false
t.text "status_reason"
t.text "encrypted_ca_key"
t.text "encrypted_ca_key_iv"
t.text "ca_cert"
end
 
create_table "clusters_applications_ingress", force: :cascade do |t|
Loading
Loading
module Gitlab
module Kubernetes
class ConfigMap
def initialize(name, values = "")
def initialize(name, files)
@name = name
@values = values
@files = files
end
 
def generate
resource = ::Kubeclient::Resource.new
resource.metadata = metadata
resource.data = { values: values }
resource.data = files
resource
end
 
Loading
Loading
@@ -19,7 +19,7 @@ module Gitlab
 
private
 
attr_reader :name, :values
attr_reader :name, :files
 
def metadata
{
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@ module Gitlab
 
def install(command)
namespace.ensure_exists!
create_config_map(command) if command.config_map?
create_config_map(command)
kubeclient.create_pod(command.pod_resource)
end
 
Loading
Loading
module Gitlab
module Kubernetes
module Helm
class BaseCommand
attr_reader :name
def initialize(name)
@name = name
end
module BaseCommand
def pod_resource
Gitlab::Kubernetes::Helm::Pod.new(self, namespace).generate
end
Loading
Loading
@@ -24,16 +18,32 @@ module Gitlab
HEREDOC
end
 
def config_map?
false
end
def pod_name
"install-#{name}"
end
 
def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, files).generate
end
def file_names
files.keys
end
def name
raise "Not implemented"
end
def files
raise "Not implemented"
end
private
 
def files_dir
"/data/helm/#{name}/config"
end
def namespace
Gitlab::Kubernetes::Helm::NAMESPACE
end
Loading
Loading
# frozen_string_literal: true
module Gitlab
module Kubernetes
module Helm
class Certificate
INFINITE_EXPIRY = 1000.years
SHORT_EXPIRY = 30.minutes
attr_reader :key, :cert
def key_string
@key.to_s
end
def cert_string
@cert.to_pem
end
def self.from_strings(key_string, cert_string)
key = OpenSSL::PKey::RSA.new(key_string)
cert = OpenSSL::X509::Certificate.new(cert_string)
new(key, cert)
end
def self.generate_root
_issue(signed_by: nil, expires_in: INFINITE_EXPIRY, certificate_authority: true)
end
def issue(expires_in: SHORT_EXPIRY)
self.class._issue(signed_by: self, expires_in: expires_in, certificate_authority: false)
end
private
def self._issue(signed_by:, expires_in:, certificate_authority:)
key = OpenSSL::PKey::RSA.new(4096)
public_key = key.public_key
subject = OpenSSL::X509::Name.parse("/C=US")
cert = OpenSSL::X509::Certificate.new
cert.subject = subject
cert.issuer = signed_by&.cert&.subject || subject
cert.not_before = Time.now
cert.not_after = expires_in.from_now
cert.public_key = public_key
cert.serial = 0x0
cert.version = 2
if certificate_authority
extension_factory = OpenSSL::X509::ExtensionFactory.new
extension_factory.subject_certificate = cert
extension_factory.issuer_certificate = cert
cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
end
cert.sign(signed_by&.key || key, OpenSSL::Digest::SHA256.new)
new(key, cert)
end
def initialize(key, cert)
@key = key
@cert = cert
end
end
end
end
end
module Gitlab
module Kubernetes
module Helm
class InitCommand < BaseCommand
class InitCommand
include BaseCommand
attr_reader :name, :files
def initialize(name:, files:)
@name = name
@files = files
end
def generate_script
super + [
init_helm_command
Loading
Loading
@@ -11,7 +20,12 @@ module Gitlab
private
 
def init_helm_command
"helm init >/dev/null"
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"
"helm init #{tls_flags} >/dev/null"
end
end
end
Loading
Loading
module Gitlab
module Kubernetes
module Helm
class InstallCommand < BaseCommand
attr_reader :name, :chart, :version, :repository, :values
class InstallCommand
include BaseCommand
 
def initialize(name, chart:, values:, version: nil, repository: nil)
attr_reader :name, :files, :chart, :version, :repository
def initialize(name:, chart:, files:, version: nil, repository: nil)
@name = name
@chart = chart
@version = version
@values = values
@files = files
@repository = repository
end
 
Loading
Loading
@@ -20,14 +22,6 @@ module Gitlab
].compact.join("\n")
end
 
def config_map?
true
end
def config_map_resource
Gitlab::Kubernetes::ConfigMap.new(name, values).generate
end
private
 
def init_command
Loading
Loading
@@ -39,14 +33,25 @@ module Gitlab
end
 
def script_command
<<~HEREDOC
helm install #{chart} --name #{name}#{optional_version_flag} --namespace #{Gitlab::Kubernetes::Helm::NAMESPACE} -f /data/helm/#{name}/config/values.yaml >/dev/null
HEREDOC
init_flags = "--name #{name}#{optional_tls_flags}#{optional_version_flag}" \
" --namespace #{Gitlab::Kubernetes::Helm::NAMESPACE}" \
" -f /data/helm/#{name}/config/values.yaml"
"helm install #{chart} #{init_flags} >/dev/null\n"
end
 
def optional_version_flag
" --version #{version}" if version
end
def optional_tls_flags
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"
end
end
end
end
Loading
Loading
Loading
Loading
@@ -10,10 +10,8 @@ module Gitlab
def generate
spec = { containers: [container_specification], restartPolicy: 'Never' }
 
if command.config_map?
spec[:volumes] = volumes_specification
spec[:containers][0][:volumeMounts] = volume_mounts_specification
end
spec[:volumes] = volumes_specification
spec[:containers][0][:volumeMounts] = volume_mounts_specification
 
::Kubeclient::Resource.new(metadata: metadata, spec: spec)
end
Loading
Loading
@@ -61,7 +59,7 @@ module Gitlab
name: 'configuration-volume',
configMap: {
name: "values-content-configuration-#{command.name}",
items: [{ key: 'values', path: 'values.yaml' }]
items: command.file_names.map { |name| { key: name, path: name } }
}
}
]
Loading
Loading
Loading
Loading
@@ -44,10 +44,11 @@ module QA
page.await_installed(:helm)
 
page.install!(:ingress) if @install_ingress
page.await_installed(:ingress) if @install_ingress
page.install!(:prometheus) if @install_prometheus
page.await_installed(:prometheus) if @install_prometheus
page.install!(:runner) if @install_runner
page.await_installed(:ingress) if @install_ingress
page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
end
end
Loading
Loading
Loading
Loading
@@ -16,6 +16,7 @@ module QA
 
def install!(application_name)
within(".js-cluster-application-row-#{application_name}") do
page.has_button?('Install', wait: 30)
click_on 'Install'
end
end
Loading
Loading
Loading
Loading
@@ -32,11 +32,21 @@ FactoryBot.define do
updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago
end
 
factory :clusters_applications_ingress, class: Clusters::Applications::Ingress
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus
factory :clusters_applications_runner, class: Clusters::Applications::Runner
factory :clusters_applications_ingress, class: Clusters::Applications::Ingress do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_runner, class: Clusters::Applications::Runner do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
factory :clusters_applications_jupyter, class: Clusters::Applications::Jupyter do
oauth_application factory: :oauth_application
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
end
end
Loading
Loading
@@ -36,5 +36,9 @@ FactoryBot.define do
trait :production_environment do
sequence(:environment_scope) { |n| "production#{n}/*" }
end
trait :with_installed_helm do
application_helm factory: %i(clusters_applications_helm installed)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment