Enable secure communication over HTTPS for Grafana

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: Idf59f92f00ed58f3ebce2e0421694ad7c2010fe9
Implements: blueprint support-secure-communication
This commit is contained in:
Guillaume Thouvenin 2016-06-08 10:21:27 +02:00
parent a8a1c08aeb
commit 9c77c2f5fe
10 changed files with 210 additions and 20 deletions

View File

@ -19,12 +19,8 @@ $master_ip = hiera('master_ip')
$vip = hiera('lma::influxdb::vip')
$grafana_port = hiera('lma::influxdb::grafana_port')
$influxdb_port = hiera('lma::influxdb::influxdb_port')
$grafana_link_data = "{\"title\":\"Grafana\",\
\"description\":\"Dashboard for visualizing metrics\",\
\"url\":\"http://${vip}:${grafana_port}/\"}"
$grafana_link_created_file = '/var/cache/grafana_link_created'
$influxdb_grafana = hiera('influxdb_grafana')
$grafana_link_created_file = '/var/cache/grafana_link_created'
$admin_username = $influxdb_grafana['grafana_username']
$admin_password = $influxdb_grafana['grafana_userpass']
$influxdb_username = $influxdb_grafana['influxdb_username']
@ -32,7 +28,6 @@ $influxdb_password = $influxdb_grafana['influxdb_userpass']
$influxdb_database = $influxdb_grafana['influxdb_dbname']
$lma_collector = hiera_hash('lma_collector', {})
$influxdb_mode = $lma_collector['influxdb_mode']
$import_influxdb = $influxdb_mode ? {
'local' => true,
@ -45,6 +40,19 @@ $import_elasticsearch = $elasticsearch_mode ? {
default => false,
}
if hiera('lma::grafana::tls::enabled') {
$protocol = 'https'
$grafana_hostname = hiera('lma::grafana::tls::hostname')
$grafana_link_data = "{\"title\":\"Grafana\",\
\"description\":\"Dashboard for visualizing metrics (${grafana_hostname}: ${vip})\",\
\"url\":\"${protocol}://${grafana_hostname}:${grafana_port}/\"}"
} else {
$protocol = 'http'
$grafana_link_data = "{\"title\":\"Grafana\",\
\"description\":\"Dashboard for visualizing metrics\",\
\"url\":\"${protocol}://${vip}:${grafana_port}/\"}"
}
grafana_datasource { 'lma':
ensure => present,
url => "http://${vip}:${influxdb_port}",
@ -53,7 +61,7 @@ grafana_datasource { 'lma':
database => $influxdb_database,
access_mode => 'proxy',
is_default => true,
grafana_url => "http://${vip}:${grafana_port}",
grafana_url => "${protocol}://${vip}:${grafana_port}",
grafana_user => $admin_username,
grafana_password => $admin_password,
}
@ -61,6 +69,7 @@ grafana_datasource { 'lma':
class {'lma_monitoring_analytics::grafana_dashboards':
admin_username => $admin_username,
admin_password => $admin_password,
protocol => $protocol,
host => $vip,
import_elasticsearch => $import_elasticsearch,
import_influxdb => $import_influxdb,

View File

@ -19,6 +19,7 @@ $nodes_names = prefix(range(1, size($nodes_ips)), 'server_')
$stats_port = '1000'
$influxdb_port = hiera('lma::influxdb::influxdb_port')
$grafana_port = hiera('lma::influxdb::grafana_port')
$influxdb_grafana = hiera_hash('influxdb_grafana')
Openstack::Ha::Haproxy_service {
balancermember_options => 'check',
@ -46,15 +47,30 @@ openstack::ha::haproxy_service { 'influxdb':
# client IP address will always reach the same server (as long as no server
# goes down or up). This is needed to support sticky session and to be able
# to authenticate.
openstack::ha::haproxy_service { 'grafana':
order => '801',
listen_port => $grafana_port,
balancermember_port => $grafana_port,
haproxy_config_options => {
'option' => ['httplog', 'dontlog-normal'],
'balance' => 'source',
'mode' => 'http',
},
if hiera('lma::grafana::tls::enabled') {
openstack::ha::haproxy_service { 'grafana':
order => '801',
internal_ssl => true,
internal_ssl_path => hiera('lma::grafana::tls::cert_file_path'),
listen_port => $grafana_port,
balancermember_port => $grafana_port,
haproxy_config_options => {
'option' => ['httplog', 'dontlog-normal'],
'balance' => 'source',
'mode' => 'http',
},
}
} else {
openstack::ha::haproxy_service { 'grafana':
order => '801',
listen_port => $grafana_port,
balancermember_port => $grafana_port,
haproxy_config_options => {
'option' => ['httplog', 'dontlog-normal'],
'balance' => 'source',
'mode' => 'http',
},
}
}
openstack::ha::haproxy_service { 'stats':

View File

@ -35,6 +35,32 @@ $leader_ip_address = $leader_ip_addresses[0]
$influxdb_others = get_nodes_hash_by_roles($network_metadata, ['influxdb_grafana'])
$others_ip_addresses = sort(values(get_node_to_ipaddr_map_by_network_role($influxdb_others, 'influxdb_vip')))
$tls_enabled = $influxdb_grafana['tls_enabled']
if $tls_enabled {
$grafana_hostname = $influxdb_grafana['grafana_hostname']
$cert_base_dir = '/etc/haproxy'
$cert_dir = "${cert_base_dir}/certs"
$cert_file_path = "${cert_dir}/${influxdb_grafana['grafana_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 => $influxdb_grafana['grafana_ssl_cert']['content'],
require => File[$cert_dir]
}
}
$calculated_content = inline_template('
---
lma::influxdb::data_dir: "/var/lib/influxdb"
@ -51,6 +77,12 @@ lma::influxdb::vip: <%= @influxdb_vip %>
lma::corosync_roles:
- primary-influxdb_grafana
- influxdb_grafana
lma::grafana::tls::enabled: <%= @tls_enabled %>
<% if @tls_enabled -%>
lma::grafana::tls::hostname: "<%= @grafana_hostname %>"
lma::grafana::tls::cert_file_path: "<%= @cert_file_path %>"
<% end -%>
')
file { $hiera_file:

View File

@ -0,0 +1,25 @@
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
notice('fuel-plugin-influxdb-grafana: validate_certificate.pp')
if hiera('lma::grafana::tls::enabled') {
$certificate = hiera('lma::grafana::tls::cert_file_path')
$common_name = hiera('lma::grafana::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)
}

View File

@ -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

View File

@ -78,9 +78,11 @@ class Puppet::Provider::Grafana < Puppet::Provider
request.basic_auth resource[:grafana_user], resource[:grafana_password]
end
return Net::HTTP.start(self.grafana_host, self.grafana_port) do |http|
http.request(request)
end
return Net::HTTP.start(self.grafana_host, self.grafana_port,
:use_ssl => self.grafana_scheme == "https",
:verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
http.request(request)
end
end
end

View File

@ -17,6 +17,7 @@
class lma_monitoring_analytics::grafana_dashboards (
$admin_username,
$admin_password,
$protocol = $lma_monitoring_analytics::params::protocol,
$host = $lma_monitoring_analytics::params::grafana_domain,
$port = $lma_monitoring_analytics::params::grafana_port,
$import_influxdb = false,
@ -25,7 +26,7 @@ class lma_monitoring_analytics::grafana_dashboards (
$dashboard_defaults = {
ensure => present,
grafana_url => "http://${host}:${port}",
grafana_url => "${protocol}://${host}:${port}",
grafana_user => $admin_username,
grafana_password => $admin_password,
}

View File

@ -22,4 +22,5 @@ class lma_monitoring_analytics::params {
$grafana_domain = 'localhost'
$grafana_address = ''
$grafana_port = 8000
$protocol = 'http'
}

View File

@ -13,6 +13,7 @@
- netconfig
- hosts
- influxdb-hiera
- grafana-validate-certificate
- influxdb-firewall
- influxdb-cluster
- influxdb-cluster-haproxy
@ -67,6 +68,19 @@
reexecute_on:
- deploy_changes
- id: grafana-validate-certificate
type: puppet
version: 2.0.0
requires: [influxdb-hiera]
required_for: [influxdb-firewall]
parameters:
puppet_manifest: "puppet/manifests/validate_certificate.pp"
puppet_modules: puppet/modules:/etc/puppet/modules
timeout: 120
# reexecute_on is needed for scale-down operations
reexecute_on:
- deploy_changes
- id: influxdb-firewall
type: puppet
version: 2.0.0

View File

@ -135,3 +135,32 @@ attributes:
regex:
source: '^[\S]{4,}$'
error: "You must provide a password with at least 4 characters"
# TLS Settings: BEGIN
tls_enabled:
value: false
label: 'Enable TLS for Grafana'
description: ''
weight: 140
type: "checkbox"
grafana_hostname:
value: 'grafana.fuel.local'
label: 'DNS hostname for Grafana'
description: 'Your DNS entries should point to this name.'
weight: 150
type: "text"
restrictions:
- condition: "settings:influxdb_grafana.tls_enabled.value == false"
action: "hide"
grafana_ssl_cert:
value: ''
label: 'Certificate for Grafana'
description: 'Certificate and private key data, concatenated into a single file.'
weight: 160
type: "file"
restrictions:
- condition: "settings:influxdb_grafana.tls_enabled.value == false"
action: "hide"
# TLS Settings: END