From b0ecd8b67c916225944880c025340dfdfcf48588 Mon Sep 17 00:00:00 2001
From: Krzysztof Klimonda <kklimonda@cloudferro.com>
Date: Fri, 7 Jun 2019 13:56:21 +0000
Subject: [PATCH] Implement TLS encryption for internal endpoints

This review is the first one in a series of patches and it introduces an
optional encryption for internal openstack endpoints, implementing part
of the add-ssl-internal-network spec.

Change-Id: I6589751626486279bf24725f22e71da8cd7f0a43
---
 ansible/group_vars/all.yml                    | 14 +++++---
 .../roles/common/templates/admin-openrc.sh.j2 |  4 ++-
 .../haproxy_single_service_listen.cfg.j2      | 10 ++++--
 .../haproxy_single_service_split.cfg.j2       | 10 ++++--
 ansible/roles/haproxy/tasks/config.yml        | 17 +++++++++
 ansible/roles/haproxy/tasks/precheck.yml      | 20 +++++++++--
 .../roles/haproxy/templates/haproxy.json.j2   |  7 ++++
 .../haproxy/templates/haproxy_main.cfg.j2     |  2 +-
 ansible/roles/horizon/defaults/main.yml       |  8 ++++-
 .../roles/horizon/templates/horizon.conf.j2   |  2 +-
 .../roles/horizon/templates/local_settings.j2 |  2 +-
 ansible/roles/nova/templates/nova.conf.j2     |  2 +-
 .../roles/prechecks/tasks/service_checks.yml  |  9 ++---
 doc/source/admin/advanced-configuration.rst   | 35 +++++++++++++------
 ...rk-tls-configuration-cf9704812f113281.yaml |  7 ++++
 15 files changed, 116 insertions(+), 33 deletions(-)
 create mode 100644 releasenotes/notes/add-internal-network-tls-configuration-cf9704812f113281.yaml

diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index f59803b66e..38d38c9939 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -35,7 +35,8 @@ kolla_install_type: "binary"
 kolla_internal_vip_address: "{{ kolla_internal_address }}"
 kolla_internal_fqdn: "{{ kolla_internal_vip_address }}"
 kolla_external_vip_address: "{{ kolla_internal_vip_address }}"
-kolla_external_fqdn: "{{ kolla_internal_fqdn if kolla_external_vip_address == kolla_internal_vip_address else kolla_external_vip_address }}"
+kolla_same_external_internal_vip: "{{ kolla_external_vip_address == kolla_internal_vip_address }}"
+kolla_external_fqdn: "{{ kolla_internal_fqdn if kolla_same_external_internal_vip | bool else kolla_external_vip_address }}"
 
 kolla_enable_sanity_checks: "no"
 
@@ -457,8 +458,8 @@ opendaylight_websocket_port: "8185"
 vitrage_api_port: "8999"
 
 public_protocol: "{{ 'https' if kolla_enable_tls_external | bool else 'http' }}"
-internal_protocol: "http"
-admin_protocol: "http"
+internal_protocol: "{{ 'https' if kolla_enable_tls_internal | bool else 'http' }}"
+admin_protocol: "{{ 'https' if kolla_enable_tls_internal | bool else 'http' }}"
 
 ####################
 # OpenStack options
@@ -723,10 +724,13 @@ qdrouterd_user: "openstack"
 # HAProxy options
 ####################
 haproxy_user: "openstack"
-haproxy_enable_external_vip: "{{ 'no' if kolla_external_vip_address == kolla_internal_vip_address else 'yes' }}"
-kolla_enable_tls_external: "no"
+haproxy_enable_external_vip: "{{ 'no' if kolla_same_external_internal_vip | bool else 'yes' }}"
+kolla_enable_tls_internal: "no"
+kolla_enable_tls_external: "{{ kolla_enable_tls_internal if kolla_same_external_internal_vip | bool else 'no' }}"
 kolla_external_fqdn_cert: "{{ node_config }}/certificates/haproxy.pem"
+kolla_internal_fqdn_cert: "{{ node_config }}/certificates/haproxy-internal.pem"
 kolla_external_fqdn_cacert: "{{ node_config }}/certificates/haproxy-ca.crt"
+kolla_internal_fqdn_cacert: "{{ node_config }}/certificates/haproxy-ca-internal.crt"
 
 
 ####################
diff --git a/ansible/roles/common/templates/admin-openrc.sh.j2 b/ansible/roles/common/templates/admin-openrc.sh.j2
index c71b5a2e84..dbd3308f5b 100644
--- a/ansible/roles/common/templates/admin-openrc.sh.j2
+++ b/ansible/roles/common/templates/admin-openrc.sh.j2
@@ -9,6 +9,8 @@ export OS_INTERFACE=internal
 export OS_IDENTITY_API_VERSION=3
 export OS_REGION_NAME={{ openstack_region_name }}
 export OS_AUTH_PLUGIN=password
-{% if kolla_enable_tls_external | bool and kolla_external_fqdn_cacert %}
+{% if kolla_enable_tls_internal | bool and kolla_internal_fqdn_cacert %}
+export OS_CACERT={{ kolla_internal_fqdn_cacert }}
+{% elif  kolla_enable_tls_external | bool and kolla_external_fqdn_cacert %}
 export OS_CACERT={{ kolla_external_fqdn_cacert }}
 {% endif %}
diff --git a/ansible/roles/haproxy-config/templates/haproxy_single_service_listen.cfg.j2 b/ansible/roles/haproxy-config/templates/haproxy_single_service_listen.cfg.j2
index 528472616f..c23071a373 100644
--- a/ansible/roles/haproxy-config/templates/haproxy_single_service_listen.cfg.j2
+++ b/ansible/roles/haproxy-config/templates/haproxy_single_service_listen.cfg.j2
@@ -1,5 +1,6 @@
 #jinja2: lstrip_blocks: True
-{%- set tls_bind_info = 'ssl crt /etc/haproxy/haproxy.pem' if kolla_enable_tls_external|bool else '' %}
+{%- set external_tls_bind_info = 'ssl crt /etc/haproxy/haproxy.pem' if kolla_enable_tls_external|bool else '' %}
+{%- set internal_tls_bind_info = 'ssl crt /etc/haproxy/haproxy-internal.pem' if kolla_enable_tls_internal|bool else '' %}
 
 {%- macro userlist_macro(service_name, auth_user, auth_pass) %}
 userlist {{ service_name }}-user
@@ -36,12 +37,17 @@ listen {{ service_name }}
     {% if external|bool %}
         {% set vip_address = kolla_external_vip_address %}
         {% if service_mode == 'http' %}
-            {% set tls_option = tls_bind_info %}
+            {% set tls_option = external_tls_bind_info %}
     {# Replace the XFP header for external https requests #}
     http-request set-header X-Forwarded-Proto https if { ssl_fc }
         {% endif %}
     {% else %}
         {% set vip_address = kolla_internal_vip_address %}
+        {% if service_mode == 'http' %}
+            {% set tls_option = internal_tls_bind_info %}
+    {# Replace the XFP header for internal https requests #}
+        http-request set-header X-Forwarded-Proto https if { ssl_fc }
+        {% endif %}
     {% endif %}
     {{ "bind %s:%s %s"|e|format(vip_address, service_port, tls_option)|trim() }}
     {# Redirect mode sets a redirect scheme instead of members #}
diff --git a/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2 b/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2
index 6bd563f0ab..2e3eee74a7 100644
--- a/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2
+++ b/ansible/roles/haproxy-config/templates/haproxy_single_service_split.cfg.j2
@@ -1,5 +1,6 @@
 #jinja2: lstrip_blocks: True
-{%- set tls_bind_info = 'ssl crt /etc/haproxy/haproxy.pem' if kolla_enable_tls_external|bool else '' %}
+{%- set external_tls_bind_info = 'ssl crt /etc/haproxy/haproxy.pem' if kolla_enable_tls_external|bool else '' %}
+{%- set internal_tls_bind_info = 'ssl crt /etc/haproxy/haproxy-internal.pem' if kolla_enable_tls_internal|bool else '' %}
 
 {%- macro userlist_macro(service_name, auth_user, auth_pass) %}
 userlist {{ service_name }}-user
@@ -29,12 +30,17 @@ frontend {{ service_name }}_front
     {% if external|bool %}
         {% set vip_address = kolla_external_vip_address %}
         {% if service_mode == 'http' %}
-            {% set tls_option = tls_bind_info %}
+            {% set tls_option = external_tls_bind_info %}
     {# Replace the XFP header for external https requests #}
     http-request set-header X-Forwarded-Proto https if { ssl_fc }
         {% endif %}
     {% else %}
         {% set vip_address = kolla_internal_vip_address %}
+        {% if service_mode == 'http' %}
+            {% set tls_option = internal_tls_bind_info %}
+    {# Replace the XFP header for internal https requests #}
+    http-request set-header X-Forwarded-Proto https if { ssl_fc }
+        {% endif %}
     {% endif %}
     {{ "bind %s:%s %s"|e|format(vip_address, service_port, tls_option)|trim() }}
     {# Redirect mode sets a redirect scheme instead of a backend #}
diff --git a/ansible/roles/haproxy/tasks/config.yml b/ansible/roles/haproxy/tasks/config.yml
index f6f9caa3ce..bea3e76714 100644
--- a/ansible/roles/haproxy/tasks/config.yml
+++ b/ansible/roles/haproxy/tasks/config.yml
@@ -109,6 +109,23 @@
   notify:
     - Restart haproxy container
 
+- name: Copying over haproxy-internal.pem
+  vars:
+    service: "{{ haproxy_services['haproxy'] }}"
+  copy:
+    src: "{{ kolla_internal_fqdn_cert }}"
+    dest: "{{ node_config_directory }}/haproxy/{{ item }}"
+    mode: "0660"
+  become: true
+  when:
+    - kolla_enable_tls_internal | bool
+    - inventory_hostname in groups[service.group]
+    - service.enabled | bool
+  with_items:
+    - "haproxy-internal.pem"
+  notify:
+    - Restart haproxy container
+
 - name: Copying over haproxy start script
   vars:
     service: "{{ haproxy_services['haproxy'] }}"
diff --git a/ansible/roles/haproxy/tasks/precheck.yml b/ansible/roles/haproxy/tasks/precheck.yml
index 2dde07b845..49a1f3bbd7 100644
--- a/ansible/roles/haproxy/tasks/precheck.yml
+++ b/ansible/roles/haproxy/tasks/precheck.yml
@@ -77,20 +77,34 @@
   check_mode: no
   run_once: true
 
-- name: Checking if haproxy certificate exists
+- name: Checking if external haproxy certificate exists
   run_once: true
   local_action: stat path={{ kolla_external_fqdn_cert }}
   register: haproxy_cert_file
   changed_when: false
   when: kolla_enable_tls_external | bool
 
-- name: Fail if haproxy certificate is absent
+- name: Fail if external haproxy certificate is absent
   run_once: true
-  local_action: fail msg="haproxy certificate file is not found. Ensure it exists as {{ kolla_external_fqdn_cert }}"
+  local_action: fail msg="External haproxy certificate file is not found. It is configured via 'kolla_external_fqdn_cert'"
   when:
     - kolla_enable_tls_external | bool
     - haproxy_cert_file.stat.exists == false
 
+- name: Checking if internal haproxy certificate exists
+  run_once: true
+  local_action: stat path={{ kolla_internal_fqdn_cert }}
+  register: haproxy_internal_cert_file
+  changed_when: false
+  when: kolla_enable_tls_internal | bool
+
+- name: Fail if internal haproxy certificate is absent
+  run_once: true
+  local_action: fail msg="Internal haproxy certificate file is not found. It is configured via 'kolla_internal_fqdn_cert'"
+  when:
+    - kolla_enable_tls_internal | bool
+    - haproxy_internal_cert_file.stat.exists == false
+
 - name: Checking the kolla_external_vip_interface is present
   fail: "msg='Please check the kolla_external_vip_interface property - interface {{ kolla_external_vip_interface }} not found'"
   when:
diff --git a/ansible/roles/haproxy/templates/haproxy.json.j2 b/ansible/roles/haproxy/templates/haproxy.json.j2
index 9cd43adb94..a51a8ed7ab 100644
--- a/ansible/roles/haproxy/templates/haproxy.json.j2
+++ b/ansible/roles/haproxy/templates/haproxy.json.j2
@@ -25,6 +25,13 @@
             "owner": "root",
             "perm": "0600",
             "optional": {{ (not kolla_enable_tls_external | bool) | string | lower }}
+        },
+        {
+            "source": "{{ container_config_directory }}/haproxy-internal.pem",
+            "dest": "/etc/haproxy/haproxy-internal.pem",
+            "owner": "root",
+            "perm": "0600",
+            "optional": {{ (not kolla_enable_tls_internal | bool) | string | lower }}
         }
     ]
 }
diff --git a/ansible/roles/haproxy/templates/haproxy_main.cfg.j2 b/ansible/roles/haproxy/templates/haproxy_main.cfg.j2
index 34f2c453a6..bf960d95b2 100644
--- a/ansible/roles/haproxy/templates/haproxy_main.cfg.j2
+++ b/ansible/roles/haproxy/templates/haproxy_main.cfg.j2
@@ -13,7 +13,7 @@ global
         {% endfor %}
     {% endif %}
     stats socket /var/lib/kolla/haproxy/haproxy.sock group kolla mode 660
-    {% if kolla_enable_tls_external | bool %}
+    {% if kolla_enable_tls_external or kolla_enable_tls_internal | bool %}
     ssl-default-bind-ciphers DEFAULT:!MEDIUM:!3DES
     ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
     tune.ssl.default-dh-param 4096
diff --git a/ansible/roles/horizon/defaults/main.yml b/ansible/roles/horizon/defaults/main.yml
index a0d2b85da7..ced3f46934 100644
--- a/ansible/roles/horizon/defaults/main.yml
+++ b/ansible/roles/horizon/defaults/main.yml
@@ -42,10 +42,16 @@ horizon_services:
         enabled: "{{ enable_horizon }}"
         mode: "http"
         external: false
-        port: "{{ horizon_port }}"
+        port: "{% if kolla_enable_tls_internal|bool %}443{% else %}{{ horizon_port }}{% endif %}"
         listen_port: "{{ horizon_listen_port }}"
         frontend_http_extra:
           - "balance source"
+      horizon_redirect:
+        enabled: "{{ enable_horizon|bool and kolla_enable_tls_internal|bool }}"
+        mode: "redirect"
+        external: false
+        port: "{{ horizon_port }}"
+        listen_port: "{{ horizon_listen_port }}"
       horizon_external:
         enabled: "{{ enable_horizon }}"
         mode: "http"
diff --git a/ansible/roles/horizon/templates/horizon.conf.j2 b/ansible/roles/horizon/templates/horizon.conf.j2
index c839b25d3f..632415e91a 100644
--- a/ansible/roles/horizon/templates/horizon.conf.j2
+++ b/ansible/roles/horizon/templates/horizon.conf.j2
@@ -32,7 +32,7 @@ TraceEnable off
     </Location>
 </VirtualHost>
 
-{% if kolla_enable_tls_external | bool %}
+{% if kolla_enable_tls_external or kolla_enable_tls_internal| bool %}
 Header edit Location ^http://(.*)$ https://$1
 {% endif %}
 
diff --git a/ansible/roles/horizon/templates/local_settings.j2 b/ansible/roles/horizon/templates/local_settings.j2
index eab5185234..3845d56112 100644
--- a/ansible/roles/horizon/templates/local_settings.j2
+++ b/ansible/roles/horizon/templates/local_settings.j2
@@ -55,7 +55,7 @@ DATABASES = {
 #CSRF_COOKIE_SECURE = True
 #SESSION_COOKIE_SECURE = True
 
-{% if kolla_enable_tls_external | bool %}
+{% if kolla_enable_tls_external or kolla_enable_tls_internal | bool %}
 SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
 CSRF_COOKIE_SECURE = True
 SESSION_COOKIE_SECURE = True
diff --git a/ansible/roles/nova/templates/nova.conf.j2 b/ansible/roles/nova/templates/nova.conf.j2
index 60834fff4a..8f2517ef1d 100644
--- a/ansible/roles/nova/templates/nova.conf.j2
+++ b/ansible/roles/nova/templates/nova.conf.j2
@@ -234,7 +234,7 @@ debug = {{ nova_logging_debug }}
 
 [wsgi]
 api_paste_config = /etc/nova/api-paste.ini
-{% if kolla_enable_tls_external | bool %}
+{% if kolla_enable_tls_external or kolla_enable_tls_internal | bool %}
 secure_proxy_ssl_header = HTTP_X_FORWARDED_PROTO
 {% endif %}
 
diff --git a/ansible/roles/prechecks/tasks/service_checks.yml b/ansible/roles/prechecks/tasks/service_checks.yml
index 8a295f93be..14b49fed28 100644
--- a/ansible/roles/prechecks/tasks/service_checks.yml
+++ b/ansible/roles/prechecks/tasks/service_checks.yml
@@ -33,10 +33,11 @@
   when:
     - nscd_status.rc == 0
 
-- name: Checking internal and external VIP addresses differ
+- name: Validate that internal and external vip address are different when TLS is enabled only on either the internal and external network
   run_once: True
-  local_action: fail msg='kolla_external_vip_address and kolla_internal_vip_address must not be the same when TLS is enabled'
+  local_action: fail msg='kolla_external_vip_address and kolla_internal_vip_address must not be the same when only one network has TLS enabled'
   changed_when: false
   when:
-    - kolla_enable_tls_external | bool
-    - kolla_external_vip_address == kolla_internal_vip_address
+    - kolla_enable_tls_external | bool or kolla_enable_tls_internal | bool
+    - not (kolla_enable_tls_external | bool and kolla_enable_tls_internal | bool)
+    - kolla_same_external_internal_vip | bool
diff --git a/doc/source/admin/advanced-configuration.rst b/doc/source/admin/advanced-configuration.rst
index 9a07d043c5..6102671d60 100644
--- a/doc/source/admin/advanced-configuration.rst
+++ b/doc/source/admin/advanced-configuration.rst
@@ -73,19 +73,26 @@ TLS Configuration
 ~~~~~~~~~~~~~~~~~
 
 An additional endpoint configuration option is to enable or disable
-TLS protection for the external VIP. TLS allows a client to authenticate
-the OpenStack service endpoint and allows for encryption of the requests
-and responses.
-
-.. note::
-
-   The kolla_internal_vip_address and kolla_external_vip_address must
-   be different to enable TLS on the external network.
+TLS protection for the internal and/or external VIP. TLS allows a client to
+authenticate the OpenStack service endpoint and allows for encryption of the
+requests and responses.
 
 The configuration variables that control TLS networking are:
 
 - kolla_enable_tls_external
 - kolla_external_fqdn_cert
+- kolla_enable_tls_internal
+- kolla_internal_fqdn_cert
+
+.. note::
+
+   If TLS is enabled only on the internal or the external network
+   the kolla_internal_vip_address and kolla_external_vip_address must
+   be different.
+
+   If there is only a single network configured in your network topology
+   (opposed to configuring seperate internal and external networks), TLS
+   can be enabled using only the internal network configuration variables.
 
 The default for TLS is disabled, to enable TLS networking:
 
@@ -94,6 +101,12 @@ The default for TLS is disabled, to enable TLS networking:
    kolla_enable_tls_external: "yes"
    kolla_external_fqdn_cert: "{{ node_config }}/certificates/mycert.pem"
 
+   and/or
+
+   kolla_enable_tls_internal: "yes"
+   kolla_internal_fqdn_cert: "{{ node_config }}/certificates/mycert-internal.pem"
+
+
 .. note::
 
    TLS authentication is based on certificates that have been
@@ -111,9 +124,9 @@ These two files will be provided by your Certificate Authority. These
 two files are the server certificate with private key and the CA certificate
 with any intermediate certificates. The server certificate needs to be
 installed with the kolla deployment and is configured with the
-``kolla_external_fqdn_cert`` parameter. If the server certificate provided
-is not already trusted by the client, then the CA certificate file will
-need to be distributed to the client.
+``kolla_external_fqdn_cert`` or ``kolla_internal_fqdn_cert`` parameter.
+If the server certificate provided is not already trusted by the client,
+then the CA certificate file will need to be distributed to the client.
 
 When using TLS to connect to a public endpoint, an OpenStack client will
 have settings similar to this:
diff --git a/releasenotes/notes/add-internal-network-tls-configuration-cf9704812f113281.yaml b/releasenotes/notes/add-internal-network-tls-configuration-cf9704812f113281.yaml
new file mode 100644
index 0000000000..be9bdb7fc3
--- /dev/null
+++ b/releasenotes/notes/add-internal-network-tls-configuration-cf9704812f113281.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Added configuration parameters ``kolla_enable_tls_internal``,
+    ``kolla_internal_fqdn_cert``, and ``kolla_internal_fqdn_cacert`` to
+    optionally enable TLS encryption for openstack endpoints on the internal
+    network.