From 15d65ae5b743370bdf7dd19299430c3fb494a135 Mon Sep 17 00:00:00 2001 From: Guillaume Thouvenin Date: Thu, 9 Jun 2016 18:10:35 +0200 Subject: [PATCH] Enable secure communication over HTTPS for Kibana This patch configures HAProxy to terminate SSL connection when the support for SSL/TLS is enabled in StackLight. DocImpact Add support for TLS Change-Id: Icdff278875d7daf928fa036ec1e4905205791546 Implements: blueprint support-secure-communication --- .../check_environment_configuration.pp | 16 +++-- .../puppet/manifests/haproxy.pp | 34 ++++++++--- .../puppet/manifests/hiera_override.pp | 33 ++++++++++ .../puppet/manifests/provision_services.pp | 19 ++++-- .../functions/validate_ssl_certificate.rb | 61 +++++++++++++++++++ deployment_tasks.yaml | 34 +++++------ environment_config.yaml | 29 +++++++++ 7 files changed, 191 insertions(+), 35 deletions(-) create mode 100644 deployment_scripts/puppet/modules/lma_logging_analytics/lib/puppet/parser/functions/validate_ssl_certificate.rb diff --git a/deployment_scripts/puppet/manifests/check_environment_configuration.pp b/deployment_scripts/puppet/manifests/check_environment_configuration.pp index 24991f8..9bd65f2 100644 --- a/deployment_scripts/puppet/manifests/check_environment_configuration.pp +++ b/deployment_scripts/puppet/manifests/check_environment_configuration.pp @@ -14,10 +14,18 @@ notice('fuel-plugin-elasticsearch-kibana: check_environment_configuration.pp') -$elasticsearch_kibana = hiera('elasticsearch_kibana') - # Check that JVM size doesn't exceed the physical RAM size -$jvmsize_mb = ($elasticsearch_kibana['jvm_heap_size'] + 0.0) * 1024 +$jvm_heap_size = hiera('lma::elasticsearch::jvm_size') +$jvmsize_mb = ($jvm_heap_size + 0.0) * 1024 if $jvmsize_mb >= $::memorysize_mb { - fail("The configured JVM size (${ $elasticsearch_kibana['jvm_heap_size'] } GB) is greater than the system RAM (${ ::memorysize }).") + fail("The configured JVM size (${jvm_heap_size} GB) is greater than the system RAM (${::memorysize}).") +} + +if hiera('lma::kibana::tls::enabled') { + $certificate = hiera('lma::kibana::tls::cert_file_path') + $common_name = hiera('lma::kibana::tls::hostname') + + # function validate_ssl_certificate() must be the value of a statement, so + # we must use it in a statement. + $not_used = validate_ssl_certificate($certificate, $common_name) } diff --git a/deployment_scripts/puppet/manifests/haproxy.pp b/deployment_scripts/puppet/manifests/haproxy.pp index f671883..14c36e4 100644 --- a/deployment_scripts/puppet/manifests/haproxy.pp +++ b/deployment_scripts/puppet/manifests/haproxy.pp @@ -44,14 +44,30 @@ openstack::ha::haproxy_service { $es_haproxy_service: } } -openstack::ha::haproxy_service { 'kibana': - order => '921', - listen_port => $kibana_frontend_port, - balancermember_port => $kibana_backend_port, - balancermember_options => 'check inter 10s fastinter 2s downinter 3s rise 3 fall 3', - haproxy_config_options => { - 'option' => ['httplog', 'http-keep-alive', 'prefer-last-server', 'dontlog-normal'], - 'balance' => 'roundrobin', - 'mode' => 'http', +if hiera('lma::kibana::tls::enabled') { + openstack::ha::haproxy_service { 'kibana': + order => '921', + internal_ssl => true, + internal_ssl_path => hiera('lma::kibana::tls::cert_file_path'), + listen_port => $kibana_frontend_port, + balancermember_port => $kibana_backend_port, + balancermember_options => 'check inter 10s fastinter 2s downinter 3s rise 3 fall 3', + haproxy_config_options => { + 'option' => ['httplog', 'http-keep-alive', 'prefer-last-server', 'dontlog-normal'], + 'balance' => 'roundrobin', + 'mode' => 'http', + }, + } +} else { + openstack::ha::haproxy_service { 'kibana': + order => '921', + listen_port => $kibana_frontend_port, + balancermember_port => $kibana_backend_port, + balancermember_options => 'check inter 10s fastinter 2s downinter 3s rise 3 fall 3', + haproxy_config_options => { + 'option' => ['httplog', 'http-keep-alive', 'prefer-last-server', 'dontlog-normal'], + 'balance' => 'roundrobin', + 'mode' => 'http', + } } } diff --git a/deployment_scripts/puppet/manifests/hiera_override.pp b/deployment_scripts/puppet/manifests/hiera_override.pp index c438470..86c9f51 100644 --- a/deployment_scripts/puppet/manifests/hiera_override.pp +++ b/deployment_scripts/puppet/manifests/hiera_override.pp @@ -64,6 +64,33 @@ if is_integer($elasticsearch_kibana['recover_after_nodes']) and $elasticsearch_k $instance_name = 'es-01' $logs_dir = "/var/log/elasticsearch/${instance_name}" +$tls_enabled = $elasticsearch_kibana['tls_enabled'] +if $tls_enabled { + $kibana_hostname = $elasticsearch_kibana['kibana_hostname'] + $cert_base_dir = '/etc/haproxy' + $cert_dir = "${cert_base_dir}/certs" + $cert_file_path = "${cert_dir}/${elasticsearch_kibana['kibana_ssl_cert']['name']}" + + file { $cert_base_dir: + ensure => directory, + mode => '0755' + } + + file { $cert_dir: + ensure => directory, + mode => '0700', + require => File[$cert_base_dir] + } + + file { $cert_file_path: + ensure => present, + mode => '0400', + content => $elasticsearch_kibana['kibana_ssl_cert']['content'], + require => File[$cert_dir] + } + +} + $calculated_content = inline_template(' --- lma::corosync_roles: @@ -91,6 +118,12 @@ lma::elasticsearch::jvm_size: <%= @elasticsearch_kibana["jvm_heap_size"] %> lma::elasticsearch::instance_name: <%= @instance_name %> lma::elasticsearch::node_name: "<%= @fqdn %>_es-01" lma::elasticsearch::cluster_name: lma + +lma::kibana::tls::enabled: <%= @tls_enabled %> +<% if @tls_enabled -%> +lma::kibana::tls::hostname: <%= @kibana_hostname %> +lma::kibana::tls::cert_file_path: <%= @cert_file_path %> +<% end -%> ') file { $hiera_file: diff --git a/deployment_scripts/puppet/manifests/provision_services.pp b/deployment_scripts/puppet/manifests/provision_services.pp index 39056d7..68af84a 100644 --- a/deployment_scripts/puppet/manifests/provision_services.pp +++ b/deployment_scripts/puppet/manifests/provision_services.pp @@ -20,12 +20,21 @@ $vip = hiera('lma::elasticsearch::vip') $kibana_port = hiera('lma::elasticsearch::kibana_frontend_port') $es_port = hiera('lma::elasticsearch::rest_port') $number_of_replicas = hiera('lma::elasticsearch::number_of_replicas') - -$kibana_link_data = "{\"title\":\"Kibana\",\ -\"description\":\"Dashboard for visualizing logs and notifications\",\ -\"url\":\"http://${vip}:${kibana_port}/\"}" -$kibana_link_created_file = '/var/cache/kibana_link_created' $elasticsearch_kibana = hiera_hash('elasticsearch_kibana') +if hiera('lma::kibana::tls::enabled') { + $protocol = 'https' + $kibana_hostname = hiera('lma::kibana::tls::hostname') + $kibana_link_data = "{\"title\":\"Kibana\",\ + \"description\":\"Dashboard for visualizing logs and notifications (${kibana_hostname}: ${vip})\",\ + \"url\":\"${protocol}://${kibana_hostname}:${kibana_port}/\"}" +} else { + $protocol = 'http' + $kibana_link_data = "{\"title\":\"Kibana\",\ + \"description\":\"Dashboard for visualizing logs and notifications\",\ + \"url\":\"${protocol}://${vip}:${kibana_port}/\"}" +} + +$kibana_link_created_file = '/var/cache/kibana_link_created' lma_logging_analytics::es_template { ['log', 'notification']: number_of_replicas => $number_of_replicas, diff --git a/deployment_scripts/puppet/modules/lma_logging_analytics/lib/puppet/parser/functions/validate_ssl_certificate.rb b/deployment_scripts/puppet/modules/lma_logging_analytics/lib/puppet/parser/functions/validate_ssl_certificate.rb new file mode 100644 index 0000000..a57f8fd --- /dev/null +++ b/deployment_scripts/puppet/modules/lma_logging_analytics/lib/puppet/parser/functions/validate_ssl_certificate.rb @@ -0,0 +1,61 @@ +# Inspired by cert_date_valid.rb found at +# https://github.com/camptocamp/puppet-openssl +# +# Function: validate_ssl_certificate() +# +# Checks SSL certificate date and CN validity. It also checks that the private +# key is embedded into the certificate. +# +# It raises an exception if: +# - the certificate has no private key +# - the CN of the certificate and the CN provided as argument don't match +# - the date is not found in the certificate +# +# It returns false if the certificate is expired or not yet valid +# Otherwise it returns the number of seconds before the certificate expires +# +# Parameter: +# - the file path of the SSL certificate +# - the expected CN + +module Puppet::Parser::Functions + newfunction(:validate_ssl_certificate, :type => :rvalue) do |args| + + require 'time' + + certfile = args[0] + + # Check that file is a valid x509 certificate + err_msg = `openssl x509 -noout -in #{certfile}` + raise "'#{certfile}' is not a valid certificate" unless err_msg.chomp() == "" + + dates = `openssl x509 -dates -noout -in #{certfile}`.gsub("\n", '') + subject = `openssl x509 -subject -noout -in #{certfile}`.gsub("\n", '') + pk = `openssl rsa -check -noout -in #{certfile}`.gsub("\n",'') + + cn = subject.match(/CN=([^\/]+)/) + cn_found = cn[1] if cn + certbegin = Time.parse(dates.gsub(/.*notBefore=(.+? GMT).*/, '\1')) + certend = Time.parse(dates.gsub(/.*notAfter=(.+? GMT).*/, '\1')) + now = Time.now.utc + + raise "The certificate file doesn't contain the private key" unless pk == 'RSA key ok' + raise "Found #{cn_found} as CN whereas '#{args[1]}' was expected" unless cn_found == args[1] + raise "Dates not found in the certificate" unless dates.match(/not(Before|After)=/) + + if (now > certend) + Puppet.warning("Certificate has expired. End date: #{certend}") + false + elsif (now < certbegin) + Puppet.warning("Certificate is not yet valid. Start date: #{certbegin}") + false + elsif (certend <= certbegin) + Puppet.warning("Certificate will never be valid") + false + else + # return the number of seconds before the certificate expires + (certend - now).to_i + end + + end +end diff --git a/deployment_tasks.yaml b/deployment_tasks.yaml index 3b03264..decaa30 100644 --- a/deployment_tasks.yaml +++ b/deployment_tasks.yaml @@ -4,7 +4,7 @@ type: group version: 2.0.0 role: [primary-elasticsearch_kibana] - tasks: &common_tasks + tasks: - hiera - setup_repositories - fuel_pkgs @@ -60,26 +60,12 @@ # Tasks definitions for the deployment ###################################### -# This task needs to be reexecuted to recheck that the configuration parameters -# match the node's characteristics (eg JVM size). -- id: elasticsearch-check-configuration - type: puppet - version: 2.0.0 - requires: [netconfig] - required_for: [deploy_end] - parameters: - puppet_manifest: puppet/manifests/check_environment_configuration.pp - puppet_modules: puppet/modules:/etc/puppet/modules - timeout: 120 - reexecute_on: - - deploy_changes - # This task needs to be reexecuted to adapt the configuration parameters which # depend on the number of nodes in the cluster - id: elasticsearch-hiera type: puppet version: 2.0.0 - requires: [elasticsearch-check-configuration] + requires: [netconfig] required_for: [deploy_end] parameters: puppet_manifest: "puppet/manifests/hiera_override.pp" @@ -88,11 +74,25 @@ reexecute_on: - deploy_changes -- id: elasticsearch-firewall +# This task needs to be reexecuted to recheck that the configuration parameters +# match the node's characteristics (eg JVM size). +- id: elasticsearch-check-configuration type: puppet version: 2.0.0 requires: [elasticsearch-hiera] required_for: [deploy_end] + parameters: + puppet_manifest: puppet/manifests/check_environment_configuration.pp + puppet_modules: puppet/modules:/etc/puppet/modules + timeout: 120 + reexecute_on: + - deploy_changes + +- id: elasticsearch-firewall + type: puppet + version: 2.0.0 + requires: [elasticsearch-check-configuration] + required_for: [deploy_end] parameters: puppet_manifest: "puppet/manifests/firewall.pp" puppet_modules: puppet/modules:/etc/puppet/modules diff --git a/environment_config.yaml b/environment_config.yaml index 2270eb1..00af811 100644 --- a/environment_config.yaml +++ b/environment_config.yaml @@ -90,3 +90,32 @@ attributes: restrictions: - condition: "settings:elasticsearch_kibana.advanced_settings.value == false" action: hide + +# TLS Settings: BEGIN + tls_enabled: + value: false + label: 'Enable TLS for Kibana' + description: '' + weight: 30 + type: "checkbox" + + kibana_hostname: + value: 'kibana.fuel.local' + label: 'DNS hostname for Kibana' + description: 'Your DNS entries should point to this name.' + weight: 40 + type: "text" + restrictions: + - condition: "settings:elasticsearch_kibana.tls_enabled.value == false" + action: "hide" + + kibana_ssl_cert: + value: '' + label: 'Certificate for Kibana' + description: 'Certificate and private key data, concatenated into a single file.' + weight: 50 + type: "file" + restrictions: + - condition: "settings:elasticsearch_kibana.tls_enabled.value == false" + action: "hide" + # TLS Settings: END