From b475643c112f49701db2fb3d1493987888c97c8a Mon Sep 17 00:00:00 2001
From: James Kirsch <generalfuzz@gmail.com>
Date: Thu, 19 Mar 2020 14:25:07 -0700
Subject: [PATCH] Add support for encrypting backend Keystone HAProxy traffic

This patch introduces an optional backend encryption for Keystone
service. When used in conjunction with enabling TLS for service API
endpoints, network communcation will be encrypted end to end, from
client through HAProxy to the Keystone service.

Change-Id: I6351147ddaff8b2ae629179a9bc3bae2ebac9519
Partially-Implements: blueprint add-ssl-internal-network
---
 ansible/certificates.yml                      |  3 +
 ansible/group_vars/all.yml                    | 15 ++-
 ansible/inventory/all-in-one                  |  3 +
 ansible/inventory/multinode                   |  3 +
 ansible/roles/certificates/defaults/main.yml  |  3 -
 ansible/roles/certificates/tasks/generate.yml | 92 ++++++++++++++-----
 .../templates/openssl-kolla-backend.cnf.j2    | 18 ++++
 .../roles/haproxy-config/defaults/main.yml    |  1 +
 .../haproxy_single_service_listen.cfg.j2      | 18 +++-
 .../haproxy_single_service_split.cfg.j2       | 18 +++-
 ansible/roles/haproxy/tasks/config.yml        | 14 +++
 .../haproxy/templates/haproxy_main.cfg.j2     |  3 +
 ansible/roles/keystone/defaults/main.yml      |  8 ++
 ansible/roles/keystone/tasks/config.yml       | 14 +--
 ansible/roles/keystone/tasks/copy-certs.yml   |  6 ++
 .../roles/keystone/templates/keystone.json.j2 | 14 ++-
 .../keystone/templates/wsgi-keystone.conf.j2  | 15 +++
 .../roles/service-cert-copy/tasks/main.yml    | 54 +++++++++++
 doc/source/admin/advanced-configuration.rst   | 11 ++-
 etc/kolla/globals.yml                         | 15 ++-
 ...rity-into-containers-860cbda3384dd731.yaml | 12 +--
 ...end-haproxy-keystone-fb96285d74fb464c.yaml |  7 ++
 tests/check-config.sh                         |  2 +
 tests/templates/globals-default.j2            |  2 +-
 tests/templates/inventory.j2                  |  3 +
 25 files changed, 290 insertions(+), 64 deletions(-)
 delete mode 100644 ansible/roles/certificates/defaults/main.yml
 create mode 100644 ansible/roles/certificates/templates/openssl-kolla-backend.cnf.j2
 create mode 100644 ansible/roles/keystone/tasks/copy-certs.yml
 create mode 100644 ansible/roles/service-cert-copy/tasks/main.yml
 create mode 100644 releasenotes/notes/encrypt-backend-haproxy-keystone-fb96285d74fb464c.yaml

diff --git a/ansible/certificates.yml b/ansible/certificates.yml
index f8021fcfc3..4b6d2528d9 100644
--- a/ansible/certificates.yml
+++ b/ansible/certificates.yml
@@ -1,4 +1,7 @@
 ---
+- import_playbook: gather-facts.yml
+  when: kolla_enable_tls_backend | default(false) | bool
+
 - name: Apply role certificates
   hosts: localhost
   roles:
diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index d065f3bee8..3d69bfb449 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -747,11 +747,18 @@ haproxy_user: "openstack"
 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/ca/haproxy.crt"
-kolla_internal_fqdn_cacert: "{{ node_config }}/certificates/ca/haproxy-internal.crt"
+kolla_certificates_dir: "{{ node_config }}/certificates"
+kolla_external_fqdn_cert: "{{ kolla_certificates_dir }}/haproxy.pem"
+kolla_internal_fqdn_cert: "{{ kolla_certificates_dir }}/haproxy-internal.pem"
+kolla_external_fqdn_cacert: "{{ kolla_certificates_dir }}/ca/haproxy.crt"
+kolla_internal_fqdn_cacert: "{{ kolla_certificates_dir }}/ca/haproxy-internal.crt"
 kolla_copy_ca_into_containers: "no"
+kolla_verify_tls_backend: "yes"
+haproxy_backend_cacert: "{{ 'ca-certificates.crt' if kolla_base_distro in ['debian', 'ubuntu'] else 'ca-bundle.trust.crt' }}"
+haproxy_backend_cacert_dir: "/etc/ssl/certs"
+kolla_enable_tls_backend: "no"
+kolla_tls_backend_cert: "{{ kolla_certificates_dir }}/backend-cert.pem"
+kolla_tls_backend_key: "{{ kolla_certificates_dir }}/backend-key.pem"
 
 ####################
 # Kibana options
diff --git a/ansible/inventory/all-in-one b/ansible/inventory/all-in-one
index 483bc94491..65cc85e67a 100644
--- a/ansible/inventory/all-in-one
+++ b/ansible/inventory/all-in-one
@@ -35,6 +35,9 @@ compute
 [baremetal:children]
 control
 
+[tls-backend:children]
+control
+
 [grafana:children]
 monitoring
 
diff --git a/ansible/inventory/multinode b/ansible/inventory/multinode
index 98e4f1bb18..cbd415c3bb 100644
--- a/ansible/inventory/multinode
+++ b/ansible/inventory/multinode
@@ -39,6 +39,9 @@ compute
 storage
 monitoring
 
+[tls-backend:children]
+control
+
 # You can explicitly specify which hosts run each project by updating the
 # groups in the sections below. Common services are grouped together.
 [chrony-server:children]
diff --git a/ansible/roles/certificates/defaults/main.yml b/ansible/roles/certificates/defaults/main.yml
deleted file mode 100644
index a741e6a32a..0000000000
--- a/ansible/roles/certificates/defaults/main.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-# Directory on deploy node (localhost) in which certificates are generated.
-certificates_dir: "{{ node_config }}/certificates"
diff --git a/ansible/roles/certificates/tasks/generate.yml b/ansible/roles/certificates/tasks/generate.yml
index 6865c12393..0647bfe3e9 100644
--- a/ansible/roles/certificates/tasks/generate.yml
+++ b/ansible/roles/certificates/tasks/generate.yml
@@ -1,21 +1,33 @@
 ---
 - name: Ensuring private internal directory exist
   file:
-    path: "{{ certificates_dir }}/private/internal"
+    path: "{{ kolla_certificates_dir }}/private/internal"
     state: "directory"
     recurse: yes
     mode: "0770"
 
 - name: Ensuring private external directory exist
   file:
-    path: "{{ certificates_dir }}/private/external"
+    path: "{{ kolla_certificates_dir }}/private/external"
     state: "directory"
     recurse: yes
     mode: "0770"
 
+- name: Ensuring backend certificate and key directories exist
+  file:
+    path: "{{ item | dirname }}"
+    state: "directory"
+    recurse: yes
+    mode: "0770"
+  when:
+    - kolla_enable_tls_backend | bool
+  with_items:
+    - "{{ kolla_tls_backend_cert }}"
+    - "{{ kolla_tls_backend_key }}"
+
 - name: Ensuring ca directory exist
   file:
-    path: "{{ certificates_dir }}/ca"
+    path: "{{ kolla_certificates_dir }}/ca"
     state: "directory"
     recurse: yes
     mode: "0770"
@@ -24,36 +36,36 @@
     - name: Creating external SSL configuration file
       template:
         src: "{{ item }}.j2"
-        dest: "{{ certificates_dir }}/{{ item }}"
+        dest: "{{ kolla_certificates_dir }}/{{ item }}"
         mode: "0660"
       with_items:
         - "openssl-kolla.cnf"
     - name: Creating external Key
       command: creates="{{ item }}" openssl genrsa -out {{ item }}
       with_items:
-        - "{{ certificates_dir }}/private/external/external.key"
+        - "{{ kolla_certificates_dir }}/private/external/external.key"
     - name: Setting permissions on external key
       file:
-        path: "{{ certificates_dir }}/private/external/external.key"
+        path: "{{ kolla_certificates_dir }}/private/external/external.key"
         mode: "0660"
         state: file
     - name: Creating external Server Certificate
       command: creates="{{ item }}" openssl req -new -nodes -sha256 -x509 \
-        -config {{ certificates_dir }}/openssl-kolla.cnf \
+        -config {{ kolla_certificates_dir }}/openssl-kolla.cnf \
         -days 3650 \
         -extensions v3_req \
-        -key {{ certificates_dir }}/private/external/external.key \
+        -key {{ kolla_certificates_dir }}/private/external/external.key \
         -out {{ item }}
       with_items:
-        - "{{ certificates_dir }}/private/external/external.crt"
+        - "{{ kolla_certificates_dir }}/private/external/external.crt"
     - name: Creating external CA Certificate File
       copy:
-        src: "{{ certificates_dir }}/private/external/external.crt"
+        src: "{{ kolla_certificates_dir }}/private/external/external.crt"
         dest: "{{ kolla_external_fqdn_cacert }}"
         mode: "0660"
     - name: Creating external Server PEM File
       assemble:
-        src: "{{ certificates_dir }}/private/external"
+        src: "{{ kolla_certificates_dir }}/private/external"
         dest: "{{ kolla_external_fqdn_cert }}"
         mode: "0660"
   when:
@@ -62,14 +74,14 @@
 - block:
     - name: Copy the external certificate crt to be the internal when internal + external are same network
       copy:
-        src: "{{ certificates_dir }}/private/external/external.crt"
-        dest: "{{ certificates_dir }}/private/internal/internal.crt"
+        src: "{{ kolla_certificates_dir }}/private/external/external.crt"
+        dest: "{{ kolla_certificates_dir }}/private/internal/internal.crt"
         remote_src: yes
         mode: "0660"
     - name: Copy the external certificate key to be the internal when internal + external are same network
       copy:
-        src: "{{ certificates_dir }}/private/external/external.key"
-        dest: "{{ certificates_dir }}/private/internal/internal.key"
+        src: "{{ kolla_certificates_dir }}/private/external/external.key"
+        dest: "{{ kolla_certificates_dir }}/private/internal/internal.key"
         remote_src: yes
         mode: "0660"
     - name: Copy the external PEM file to be the internal when internal + external are same network
@@ -93,38 +105,72 @@
     - name: Creating internal SSL configuration file
       template:
         src: "{{ item }}.j2"
-        dest: "{{ certificates_dir }}/{{ item }}"
+        dest: "{{ kolla_certificates_dir }}/{{ item }}"
         mode: "0660"
       with_items:
         - "openssl-kolla-internal.cnf"
     - name: Creating internal Key
       command: creates="{{ item }}" openssl genrsa -out {{ item }}
       with_items:
-        - "{{ certificates_dir }}/private/internal/internal.key"
+        - "{{ kolla_certificates_dir }}/private/internal/internal.key"
     - name: Setting permissions on internal key
       file:
-        path: "{{ certificates_dir }}/private/internal/internal.key"
+        path: "{{ kolla_certificates_dir }}/private/internal/internal.key"
         mode: "0660"
         state: file
     - name: Creating internal Server Certificate
       command: creates="{{ item }}" openssl req -new -nodes -sha256 -x509 \
-        -config {{ certificates_dir }}/openssl-kolla-internal.cnf \
+        -config {{ kolla_certificates_dir }}/openssl-kolla-internal.cnf \
         -days 3650 \
         -extensions v3_req \
-        -key {{ certificates_dir }}/private/internal/internal.key \
+        -key {{ kolla_certificates_dir }}/private/internal/internal.key \
         -out {{ item }}
       with_items:
-        - "{{ certificates_dir }}/private/internal/internal.crt"
+        - "{{ kolla_certificates_dir }}/private/internal/internal.crt"
     - name: Creating internal CA Certificate File
       copy:
-        src: "{{ certificates_dir }}/private/internal/internal.crt"
+        src: "{{ kolla_certificates_dir }}/private/internal/internal.crt"
         dest: "{{ kolla_internal_fqdn_cacert }}"
         mode: "0660"
     - name: Creating internal Server PEM File
       assemble:
-        src: "{{ certificates_dir }}/private/internal"
+        src: "{{ kolla_certificates_dir }}/private/internal"
         dest: "{{ kolla_internal_fqdn_cert }}"
         mode: "0660"
   when:
     - kolla_enable_tls_internal | bool
     - not kolla_same_external_internal_vip | bool
+
+- block:
+    - name: Creating backend SSL configuration file
+      template:
+        src: "{{ item }}.j2"
+        dest: "{{ kolla_certificates_dir }}/{{ item }}"
+        mode: "0660"
+      with_items:
+        - "openssl-kolla-backend.cnf"
+    - name: Creating backend Key
+      command: creates="{{ item }}" openssl genrsa -out {{ item }}
+      with_items:
+        - "{{ kolla_tls_backend_key }}"
+    - name: Setting permissions on backend key
+      file:
+        path: "{{ kolla_tls_backend_key }}"
+        mode: "0660"
+        state: file
+    - name: Creating backend Server Certificate
+      command: creates="{{ item }}" openssl req -new -nodes -sha256 -x509 \
+        -config {{ kolla_certificates_dir }}/openssl-kolla-backend.cnf \
+        -days 3650 \
+        -extensions v3_req \
+        -key {{ kolla_tls_backend_key }} \
+        -out {{ item }}
+      with_items:
+        - "{{ kolla_tls_backend_cert }}"
+    - name: Creating backend Certificate file to be included in container trusted ca-certificates
+      copy:
+        src: "{{ kolla_tls_backend_cert }}"
+        dest: "{{ kolla_certificates_dir }}/ca/backend-cert.crt"
+        mode: "0660"
+  when:
+    - kolla_enable_tls_backend | bool
diff --git a/ansible/roles/certificates/templates/openssl-kolla-backend.cnf.j2 b/ansible/roles/certificates/templates/openssl-kolla-backend.cnf.j2
new file mode 100644
index 0000000000..bd862832ad
--- /dev/null
+++ b/ansible/roles/certificates/templates/openssl-kolla-backend.cnf.j2
@@ -0,0 +1,18 @@
+[req]
+prompt = no
+distinguished_name = req_distinguished_name
+req_extensions = v3_req
+
+[req_distinguished_name]
+countryName = US
+stateOrProvinceName = NC
+localityName = RTP
+organizationalUnitName = kolla
+
+[v3_req]
+subjectAltName = @alt_names
+
+[alt_names]
+{% for host in groups['tls-backend']%}
+IP.{{ loop.index }} = {{ 'api' | kolla_address(host) }}
+{% endfor %}
diff --git a/ansible/roles/haproxy-config/defaults/main.yml b/ansible/roles/haproxy-config/defaults/main.yml
index 7c2e54895b..97ef2a0998 100644
--- a/ansible/roles/haproxy-config/defaults/main.yml
+++ b/ansible/roles/haproxy-config/defaults/main.yml
@@ -13,3 +13,4 @@ haproxy_backend_http_extra: []
 haproxy_backend_tcp_extra: []
 
 haproxy_health_check: "check inter 2000 rise 2 fall 5"
+haproxy_health_check_ssl: "check check-ssl inter 2000 rise 2 fall 5"
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 b1b133dadf..16076da504 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
@@ -10,7 +10,7 @@ userlist {{ service_name }}-user
 {%- macro listen_macro(service_name, service_port, listen_port,
                        service_mode, external,
                        haproxy_http_extra, haproxy_tcp_extra, host_group,
-                       custom_member_list, auth_user, auth_pass) %}
+                       custom_member_list, auth_user, auth_pass, tls_backend) %}
 listen {{ service_name }}
     {% if service_mode == 'redirect' %}
     mode http
@@ -59,10 +59,21 @@ listen {{ service_name }}
     {{ custom_member }}
             {% endfor %}
         {% else %}
+            {% set backend_tls_info = '' %}
+            {% if tls_backend|bool %}
+                {% set haproxy_health_check_final = haproxy_health_check_ssl %}
+                {% if kolla_verify_tls_backend|bool %}
+                    {% set backend_tls_info = 'ssl verify required ca-file %s'|format(haproxy_backend_cacert) %}
+                {% else %}
+                    {% set backend_tls_info = 'ssl verify none' %}
+                {% endif %}
+            {% else %}
+                {% set haproxy_health_check_final = haproxy_health_check %}
+            {% endif %}
             {% for host in groups[host_group] %}
                 {% set host_name = hostvars[host]['ansible_hostname'] %}
                 {% set host_ip = 'api' | kolla_address(host) %}
-    server {{ host_name }} {{ host_ip }}:{{ listen_port }} {{ haproxy_health_check }}
+    server {{ host_name }} {{ host_ip }}:{{ listen_port }} {{ haproxy_health_check_final }} {{ backend_tls_info }}
             {% endfor %}
         {% endif %}
     {% endif %}
@@ -86,6 +97,7 @@ listen {{ service_name }}
         {# Additional options can be defined in config, and are additive to the global extras #}
         {% set haproxy_tcp_extra = haproxy_service.frontend_tcp_extra|default([]) + haproxy_service.backend_tcp_extra|default([]) + haproxy_frontend_tcp_extra + haproxy_backend_tcp_extra %}
         {% set haproxy_http_extra = haproxy_service.frontend_http_extra|default([]) + haproxy_service.backend_http_extra|default([]) + haproxy_frontend_http_extra + haproxy_backend_http_extra %}
+        {% set tls_backend = haproxy_service.tls_backend|default(false) %}
         {# Allow for basic auth #}
         {% set auth_user = haproxy_service.auth_user|default() %}
         {% set auth_pass = haproxy_service.auth_pass|default() %}
@@ -94,6 +106,6 @@ listen {{ service_name }}
         {% endif %}
 {{ listen_macro(haproxy_name, haproxy_service.port, listen_port,
                 mode, external, haproxy_http_extra, haproxy_tcp_extra,
-                host_group, custom_member_list, auth_user, auth_pass) }}
+                host_group, custom_member_list, auth_user, auth_pass, tls_backend) }}
     {% endif %}
 {%- endfor -%}
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 27b63e4da6..ec269a307a 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
@@ -53,7 +53,7 @@ frontend {{ service_name }}_front
 
 {%- macro backend_macro(service_name, listen_port, service_mode, host_group,
                         custom_member_list, backend_http_extra,
-                        backend_tcp_extra, auth_user, auth_pass) %}
+                        backend_tcp_extra, auth_user, auth_pass, tls_backend) %}
 backend {{ service_name }}_back
     {% if service_mode == 'redirect' %}
     mode http
@@ -79,10 +79,21 @@ backend {{ service_name }}_back
     {{ custom_member }}
         {% endfor %}
     {% else %}
+        {% set backend_tls_info = '' %}
+        {% if tls_backend|bool %}
+            {% set haproxy_health_check_final = haproxy_health_check_ssl %}
+            {% if kolla_verify_tls_backend|bool %}
+                {% set backend_tls_info = 'ssl verify required ca-file %s'|format(haproxy_backend_cacert) %}
+            {% else %}
+                {% set backend_tls_info = 'ssl verify none' %}
+            {% endif %}
+        {% else %}
+            {% set haproxy_health_check_final = haproxy_health_check %}
+        {% endif %}
         {% for host in groups[host_group] %}
             {% set host_name = hostvars[host]['ansible_hostname'] %}
             {% set host_ip = 'api' | kolla_address(host) %}
-    server {{ host_name }} {{ host_ip }}:{{ listen_port }} {{ haproxy_health_check }}
+    server {{ host_name }} {{ host_ip }}:{{ listen_port }} {{ haproxy_health_check_final }} {{ backend_tls_info }}
         {% endfor %}
     {% endif %}
 {% endmacro %}
@@ -107,6 +118,7 @@ backend {{ service_name }}_back
         {% set backend_tcp_extra = haproxy_service.backend_tcp_extra|default([]) %}
         {% set frontend_http_extra = haproxy_service.frontend_http_extra|default([]) + haproxy_frontend_http_extra %}
         {% set backend_http_extra = haproxy_service.backend_http_extra|default([]) %}
+        {% set tls_backend = haproxy_service.tls_backend|default(false) %}
         {# Allow for basic auth #}
         {% set auth_user = haproxy_service.auth_user|default() %}
         {% set auth_pass = haproxy_service.auth_pass|default() %}
@@ -119,7 +131,7 @@ backend {{ service_name }}_back
         {% if haproxy_service.mode != 'redirect' %}
 {{ backend_macro(haproxy_name, listen_port, mode, host_group,
                  custom_member_list, backend_http_extra, backend_tcp_extra,
-                 auth_user, auth_pass) }}
+                 auth_user, auth_pass, tls_backend) }}
         {% endif %}
     {% endif %}
 {%- endfor -%}
diff --git a/ansible/roles/haproxy/tasks/config.yml b/ansible/roles/haproxy/tasks/config.yml
index fd215a32d0..33c46c143c 100644
--- a/ansible/roles/haproxy/tasks/config.yml
+++ b/ansible/roles/haproxy/tasks/config.yml
@@ -142,6 +142,20 @@
   notify:
     - Restart haproxy container
 
+- name: Copying over extra CA certificates
+  vars:
+    service: "{{ haproxy_services['haproxy'] }}"
+  become: true
+  copy:
+    src: "{{ kolla_certificates_dir }}/ca/"
+    dest: "{{ node_config_directory }}/haproxy/ca-certificates"
+    mode: "0644"
+  when:
+    - inventory_hostname in groups[service.group]
+    - kolla_copy_ca_into_containers | bool
+  notify:
+    - Restart haproxy container
+
 - name: Copying over haproxy start script
   vars:
     service: "{{ haproxy_services['haproxy'] }}"
diff --git a/ansible/roles/haproxy/templates/haproxy_main.cfg.j2 b/ansible/roles/haproxy/templates/haproxy_main.cfg.j2
index 4fa141783e..815fc05364 100644
--- a/ansible/roles/haproxy/templates/haproxy_main.cfg.j2
+++ b/ansible/roles/haproxy/templates/haproxy_main.cfg.j2
@@ -18,6 +18,9 @@ global
     ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
     tune.ssl.default-dh-param 4096
     {% endif %}
+    {% if kolla_enable_tls_internal | bool or kolla_enable_tls_external | bool %}
+    ca-base {{ haproxy_backend_cacert_dir }}
+    {% endif %}
 
 defaults
     log global
diff --git a/ansible/roles/keystone/defaults/main.yml b/ansible/roles/keystone/defaults/main.yml
index 717ee20ffc..f58a7fc498 100644
--- a/ansible/roles/keystone/defaults/main.yml
+++ b/ansible/roles/keystone/defaults/main.yml
@@ -14,18 +14,21 @@ keystone_services:
         enabled: "{{ enable_keystone }}"
         mode: "http"
         external: false
+        tls_backend: "{{ keystone_enable_tls_backend }}"
         port: "{{ keystone_public_port }}"
         listen_port: "{{ keystone_public_listen_port }}"
       keystone_external:
         enabled: "{{ enable_keystone }}"
         mode: "http"
         external: true
+        tls_backend: "{{ keystone_enable_tls_backend }}"
         port: "{{ keystone_public_port }}"
         listen_port: "{{ keystone_public_listen_port }}"
       keystone_admin:
         enabled: "{{ enable_keystone }}"
         mode: "http"
         external: false
+        tls_backend: "{{ keystone_enable_tls_backend }}"
         port: "{{ keystone_admin_port }}"
         listen_port: "{{ keystone_admin_listen_port }}"
   keystone-ssh:
@@ -141,3 +144,8 @@ keystone_ks_services:
       - {'interface': 'admin', 'url': '{{ keystone_admin_url }}'}
       - {'interface': 'internal', 'url': '{{ keystone_internal_url }}'}
       - {'interface': 'public', 'url': '{{ keystone_public_url }}'}
+
+####################
+# TLS
+####################
+keystone_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
diff --git a/ansible/roles/keystone/tasks/config.yml b/ansible/roles/keystone/tasks/config.yml
index d31d1d2017..b47b77f4f0 100644
--- a/ansible/roles/keystone/tasks/config.yml
+++ b/ansible/roles/keystone/tasks/config.yml
@@ -38,19 +38,9 @@
   run_once: True
   register: keystone_domain_directory
 
-- name: Copying over extra CA certificates
-  become: true
-  copy:
-    src: "{{ node_config }}/certificates/ca/"
-    dest: "{{ node_config_directory }}/{{ item.key }}/ca-certificates"
-    mode: "0644"
+- include_tasks: copy-certs.yml
   when:
-    - item.value.enabled | bool
-    - inventory_hostname in groups[item.value.group]
-    - kolla_copy_ca_into_containers | bool
-  with_dict: "{{ keystone_services }}"
-  notify:
-    - "Restart {{ item.key }} container"
+    - kolla_copy_ca_into_containers | bool or keystone_enable_tls_backend | bool
 
 - name: Copying over config.json files for services
   template:
diff --git a/ansible/roles/keystone/tasks/copy-certs.yml b/ansible/roles/keystone/tasks/copy-certs.yml
new file mode 100644
index 0000000000..7fd52ba30a
--- /dev/null
+++ b/ansible/roles/keystone/tasks/copy-certs.yml
@@ -0,0 +1,6 @@
+---
+- name: "Copy certificates and keys for {{ project_name }}"
+  import_role:
+    role: service-cert-copy
+  vars:
+    project_services: "{{ keystone_services }}"
diff --git a/ansible/roles/keystone/templates/keystone.json.j2 b/ansible/roles/keystone/templates/keystone.json.j2
index 4269d7e0fa..6637c5f559 100644
--- a/ansible/roles/keystone/templates/keystone.json.j2
+++ b/ansible/roles/keystone/templates/keystone.json.j2
@@ -34,7 +34,19 @@
             "dest": "/etc/{{ keystone_dir }}/wsgi-keystone.conf",
             "owner": "keystone",
             "perm": "0600"
-        }
+        }{% if keystone_enable_tls_backend | bool %},
+        {
+            "source": "{{ container_config_directory }}/keystone-cert.pem",
+            "dest": "/etc/keystone/certs/keystone-cert.pem",
+            "owner": "keystone",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/keystone-key.pem",
+            "dest": "/etc/keystone/certs/keystone-key.pem",
+            "owner": "keystone",
+            "perm": "0600"
+        }{% endif %}
     ],
     "permissions": [
         {
diff --git a/ansible/roles/keystone/templates/wsgi-keystone.conf.j2 b/ansible/roles/keystone/templates/wsgi-keystone.conf.j2
index 97c12ada51..17399814e0 100644
--- a/ansible/roles/keystone/templates/wsgi-keystone.conf.j2
+++ b/ansible/roles/keystone/templates/wsgi-keystone.conf.j2
@@ -5,6 +5,9 @@
 {% set python_path = '/var/lib/kolla/venv/lib/python' + distro_python_version + '/site-packages' %}
 {% endif %}
 {% set binary_path = '/usr/bin' if keystone_install_type == 'binary' else '/var/lib/kolla/venv/bin' %}
+{% if keystone_enable_tls_backend | bool %}
+LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
+{% endif %}
 Listen {{ api_interface_address | put_address_in_context('url') }}:{{ keystone_public_listen_port }}
 Listen {{ api_interface_address | put_address_in_context('url') }}:{{ keystone_admin_listen_port }}
 
@@ -42,6 +45,12 @@ LogLevel info
     ErrorLog "{{ keystone_log_dir }}/keystone-apache-public-error.log"
     LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
     CustomLog "{{ keystone_log_dir }}/keystone-apache-public-access.log" logformat
+
+{% if keystone_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/keystone/certs/keystone-cert.pem
+    SSLCertificateKeyFile /etc/keystone/certs/keystone-key.pem
+{% endif %}
 </VirtualHost>
 
 <VirtualHost *:{{ keystone_admin_listen_port }}>
@@ -56,4 +65,10 @@ LogLevel info
     ErrorLog "{{ keystone_log_dir }}/keystone-apache-admin-error.log"
     LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
     CustomLog "{{ keystone_log_dir }}/keystone-apache-admin-access.log" logformat
+
+{% if keystone_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/keystone/certs/keystone-cert.pem
+    SSLCertificateKeyFile /etc/keystone/certs/keystone-key.pem
+{% endif %}
 </VirtualHost>
diff --git a/ansible/roles/service-cert-copy/tasks/main.yml b/ansible/roles/service-cert-copy/tasks/main.yml
new file mode 100644
index 0000000000..c8af5d50a6
--- /dev/null
+++ b/ansible/roles/service-cert-copy/tasks/main.yml
@@ -0,0 +1,54 @@
+---
+- name: "{{ project_name }} | Copying over extra CA certificates"
+  become: true
+  copy:
+    src: "{{ kolla_certificates_dir }}/ca/"
+    dest: "{{ node_config_directory }}/{{ item.key }}/ca-certificates"
+    mode: "0644"
+  when:
+    - kolla_copy_ca_into_containers | bool
+  with_dict: "{{ project_services | select_services_enabled_and_mapped_to_host }}"
+  notify:
+    - "Restart {{ item.key }} container"
+
+- name: "{{ project_name }} | Copying over backend internal TLS certificate"
+  vars:
+    certs:
+      - "{{ kolla_certificates_dir }}/{{ inventory_hostname }}/{{ project_name }}-cert.pem"
+      - "{{ kolla_certificates_dir }}/{{ inventory_hostname }}-cert.pem"
+      - "{{ kolla_certificates_dir }}/{{ project_name }}-cert.pem"
+      - "{{ kolla_tls_backend_cert }}"
+    backend_tls_cert: "{{ lookup('first_found', certs) }}"
+  copy:
+    src: "{{ backend_tls_cert }}"
+    dest: "{{ node_config_directory }}/{{ item.key }}/{{ project_name }}-cert.pem"
+    mode: "0644"
+  become: true
+  when:
+    - item.value.haproxy is defined
+    - item.value.haproxy.values() | selectattr('enabled', 'defined') | map(attribute='enabled') | map('bool') | select | list | length > 0
+    - item.value.haproxy.values() | selectattr('tls_backend', 'defined') | map(attribute='tls_backend') | map('bool') | select | list | length > 0
+  with_dict: "{{ project_services | select_services_enabled_and_mapped_to_host }}"
+  notify:
+    - "Restart {{ item.key }} container"
+
+- name: "{{ project_name }} | Copying over backend internal TLS key"
+  vars:
+    keys:
+      - "{{ kolla_certificates_dir }}/{{ inventory_hostname }}/{{ project_name }}-key.pem"
+      - "{{ kolla_certificates_dir }}/{{ inventory_hostname }}-key.pem"
+      - "{{ kolla_certificates_dir }}/{{ project_name }}-key.pem"
+      - "{{ kolla_tls_backend_key }}"
+    backend_tls_key: "{{ lookup('first_found', keys) }}"
+  copy:
+    src: "{{ backend_tls_key }}"
+    dest: "{{ node_config_directory }}/{{ item.key }}/{{ project_name }}-key.pem"
+    mode: "0600"
+  become: true
+  when:
+    - item.value.haproxy is defined
+    - item.value.haproxy.values() | selectattr('enabled', 'defined') | map(attribute='enabled') | map('bool') | select | list | length > 0
+    - item.value.haproxy.values() | selectattr('tls_backend', 'defined') | map(attribute='tls_backend') | map('bool') | select | list | length > 0
+  with_dict: "{{ project_services | select_services_enabled_and_mapped_to_host }}"
+  notify:
+    - "Restart {{ item.key }} container"
diff --git a/doc/source/admin/advanced-configuration.rst b/doc/source/admin/advanced-configuration.rst
index 765ed2f69b..34c21a06ed 100644
--- a/doc/source/admin/advanced-configuration.rst
+++ b/doc/source/admin/advanced-configuration.rst
@@ -99,12 +99,12 @@ The default for TLS is disabled, to enable TLS networking:
 .. code-block:: yaml
 
    kolla_enable_tls_external: "yes"
-   kolla_external_fqdn_cert: "{{ node_config }}/certificates/mycert.pem"
+   kolla_external_fqdn_cert: "{{ kolla_certificates_dir }}/mycert.pem"
 
    and/or
 
    kolla_enable_tls_internal: "yes"
-   kolla_internal_fqdn_cert: "{{ node_config }}/certificates/mycert-internal.pem"
+   kolla_internal_fqdn_cert: "{{ kolla_certificates_dir }}/mycert-internal.pem"
 
 
 .. note::
@@ -181,7 +181,7 @@ service containers to enable trust for those CA certificates. This is required
 for any certificates that are either self-signed or signed by a private CA,
 and are not already present in the service image trust store.
 
-All certificate file names will have the "kolla-customca-" prefix appended to
+All certificate file names will have the "kolla-customca-" prefix prepended to
 it when it is copied into the containers. For example, if a certificate file is
 named "internal.crt", it will be named "kolla-customca-internal.crt" in the
 containers.
@@ -192,6 +192,11 @@ the ``/usr/local/share/ca-certificates/`` directory.
 For Centos and Red Hat Linux containers, the certificate files will be copied
 to the ``/etc/pki/ca-trust/source/anchors/`` directory.
 
+In addition, the ``openstack_cacert`` should be configured with the path to
+the cacert in the container. For example, if the self-signed certificate task
+was used and the deployment is on ubuntu, the path would be:
+"/etc/pki/ca-trust/source/anchors/kolla-customca-haproxy-internal.crt"
+
 .. _service-config:
 
 OpenStack Service Configuration in Kolla
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 524edaa244..f40fe35c04 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -182,11 +182,18 @@
 # allow clients to perform authentication.
 #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/ca/haproxy.crt"
-#kolla_internal_fqdn_cacert: "{{ node_config }}/certificates/ca/haproxy-internal.crt"
+#kolla_certificates_dir: "{{ node_config }}/certificates"
+#kolla_external_fqdn_cert: "{{ kolla_certificates_dir }}/haproxy.pem"
+#kolla_internal_fqdn_cert: "{{ kolla_certificates_dir }}/haproxy-internal.pem"
+#kolla_external_fqdn_cacert: "{{ kolla_certificates_dir }}/ca/haproxy.crt"
+#kolla_internal_fqdn_cacert: "{{ kolla_certificates_dir }}/ca/haproxy-internal.crt"
 #kolla_copy_ca_into_containers: "no"
+#kolla_verify_tls_backend: "yes"
+#haproxy_backend_cacert: "{{ 'ca-certificates.crt' if kolla_base_distro in ['debian', 'ubuntu'] else 'ca-bundle.trust.crt' }}"
+#haproxy_backend_cacert_dir: "/etc/ssl/certs"
+#kolla_enable_tls_backend: "no"
+#kolla_tls_backend_cert: "{{ kolla_certificates_dir }}/backend-cert.pem"
+#kolla_tls_backend_key: "{{ kolla_certificates_dir }}/backend-key.pem"
 
 ################
 # Region options
diff --git a/releasenotes/notes/copy-certificate-authority-into-containers-860cbda3384dd731.yaml b/releasenotes/notes/copy-certificate-authority-into-containers-860cbda3384dd731.yaml
index 78c7e11db8..f5019df18f 100644
--- a/releasenotes/notes/copy-certificate-authority-into-containers-860cbda3384dd731.yaml
+++ b/releasenotes/notes/copy-certificate-authority-into-containers-860cbda3384dd731.yaml
@@ -12,10 +12,8 @@ features:
 
 issues:
   - |
-    Python <= 2.7.9 will not trust self-signed or privately signed CAs even
-    if they are added into the OS trusted CA folder and update-ca-trust is
-    executed. This is also true for the Python Requests library, regardless of
-    Python version. For services that run Python <= 2.7.9 or rely on the
-    Python Requests library, either CA verification must be explicitly disabled
-    in the service or the path to the CA certificate must be configured using
-    the ``openstack_cacert`` parameter.
+    Python Requests library will not trust self-signed or privately signed CAs
+    even if they are added into the OS trusted CA folder and update-ca-trust is
+    executed. For services that rely on the Python Requests library, either CA
+    verification must be explicitly disabled in the service or the path to the
+    CA certificate must be configured using the ``openstack_cacert`` parameter.
diff --git a/releasenotes/notes/encrypt-backend-haproxy-keystone-fb96285d74fb464c.yaml b/releasenotes/notes/encrypt-backend-haproxy-keystone-fb96285d74fb464c.yaml
new file mode 100644
index 0000000000..1b78072702
--- /dev/null
+++ b/releasenotes/notes/encrypt-backend-haproxy-keystone-fb96285d74fb464c.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Added configuration options to enable backend TLS encryption from HAProxy
+    to the Keystone service. When used in conjunction with enabling TLS for
+    service API endpoints, network communcation will be encrypted end to end,
+    from client through HAProxy to the Keystone service.
diff --git a/tests/check-config.sh b/tests/check-config.sh
index 4bc081660c..de6577e08f 100755
--- a/tests/check-config.sh
+++ b/tests/check-config.sh
@@ -16,6 +16,8 @@ function check_config {
     for f in $(sudo find /etc/kolla \
                 -not -regex /etc/kolla/config.* \
                 -not -regex /etc/kolla/certificates.* \
+                -not -regex .*pem \
+                -not -regex .*key \
                 -not -regex ".*ca-certificates.*" \
                 -not -path /etc/kolla \
                 -not -name admin-openrc.sh \
diff --git a/tests/templates/globals-default.j2 b/tests/templates/globals-default.j2
index e1217ba4b2..3b0ac02391 100644
--- a/tests/templates/globals-default.j2
+++ b/tests/templates/globals-default.j2
@@ -117,8 +117,8 @@ ceph_nova_user: "cinder"
 {% if tls_enabled %}
 kolla_enable_tls_external: "yes"
 kolla_enable_tls_internal: "yes"
-kolla_verify_internal_ca_certs: "no"
 kolla_copy_ca_into_containers: "yes"
+kolla_enable_tls_backend: "yes"
 {% if base_distro == "ubuntu" or base_distro == "debian" %}
 openstack_cacert: "/usr/local/share/ca-certificates/kolla-customca-haproxy-internal.crt"
 {% endif %}
diff --git a/tests/templates/inventory.j2 b/tests/templates/inventory.j2
index 6b0c01e8cd..fbc15f1a10 100644
--- a/tests/templates/inventory.j2
+++ b/tests/templates/inventory.j2
@@ -53,6 +53,9 @@ compute
 storage
 monitoring
 
+[tls-backend:children]
+control
+
 # You can explicitly specify which hosts run each project by updating the
 # groups in the sections below. Common services are grouped together.
 [chrony-server:children]