From d6251506f7cb4f797f5c957bafbf4814355020b1 Mon Sep 17 00:00:00 2001
From: James Kirsch <generalfuzz@gmail.com>
Date: Thu, 30 Apr 2020 22:43:37 -0700
Subject: [PATCH] Add support for encrypting Nova API

This patch introduces an optional backend encryption for the Nova API
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 Nova service.

Change-Id: I48e1540b973016079d5686b328e82239dcffacfd
Partially-Implements: blueprint add-ssl-internal-network
---
 ansible/roles/nova/defaults/main.yml          |  9 +++
 ansible/roles/nova/tasks/config.yml           | 14 +++-
 .../nova/templates/nova-api-wsgi.conf.j2      | 70 +++++++++++++++++++
 ansible/roles/nova/templates/nova-api.json.j2 | 22 +++++-
 ansible/roles/nova/templates/nova.conf.j2     |  9 ---
 5 files changed, 113 insertions(+), 11 deletions(-)
 create mode 100644 ansible/roles/nova/templates/nova-api-wsgi.conf.j2

diff --git a/ansible/roles/nova/defaults/main.yml b/ansible/roles/nova/defaults/main.yml
index c871c7bdc4..3c13a50a88 100644
--- a/ansible/roles/nova/defaults/main.yml
+++ b/ansible/roles/nova/defaults/main.yml
@@ -17,24 +17,28 @@ nova_services:
         external: false
         port: "{{ nova_api_port }}"
         listen_port: "{{ nova_api_listen_port }}"
+        tls_backend: "{{ nova_enable_tls_backend }}"
       nova_api_external:
         enabled: "{{ enable_nova }}"
         mode: "http"
         external: true
         port: "{{ nova_api_port }}"
         listen_port: "{{ nova_api_listen_port }}"
+        tls_backend: "{{ nova_enable_tls_backend }}"
       nova_metadata:
         enabled: "{{ enable_nova }}"
         mode: "http"
         external: false
         port: "{{ nova_metadata_port }}"
         listen_port: "{{ nova_metadata_listen_port }}"
+        tls_backend: "{{ nova_enable_tls_backend }}"
       nova_metadata_external:
         enabled: "{{ enable_nova }}"
         mode: "http"
         external: true
         port: "{{ nova_metadata_port }}"
         listen_port: "{{ nova_metadata_listen_port }}"
+        tls_backend: "{{ nova_enable_tls_backend }}"
   nova-scheduler:
     container_name: "nova_scheduler"
     group: "nova-scheduler"
@@ -190,3 +194,8 @@ nova_git_repository: "{{ kolla_dev_repos_git }}/{{ project_name }}"
 nova_dev_repos_pull: "{{ kolla_dev_repos_pull }}"
 nova_dev_mode: "{{ kolla_dev_mode }}"
 nova_source_version: "{{ kolla_source_version }}"
+
+####################
+# TLS
+####################
+nova_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
diff --git a/ansible/roles/nova/tasks/config.yml b/ansible/roles/nova/tasks/config.yml
index d077643d96..bfd41be697 100644
--- a/ansible/roles/nova/tasks/config.yml
+++ b/ansible/roles/nova/tasks/config.yml
@@ -33,7 +33,7 @@
 
 - include_tasks: copy-certs.yml
   when:
-    - kolla_copy_ca_into_containers | bool
+    - kolla_copy_ca_into_containers | bool or nova_enable_tls_backend | bool
 
 - name: Copying over config.json files for services
   become: true
@@ -83,5 +83,17 @@
   notify:
     - "Restart {{ item.key }} container"
 
+- name: Copying over nova-api-wsgi.conf
+  template:
+    src: "nova-api-wsgi.conf.j2"
+    dest: "{{ node_config_directory }}/nova-api/nova-api-wsgi.conf"
+    mode: "0660"
+  become: true
+  when:
+    - inventory_hostname in groups["nova-api"]
+    - nova_services["nova-api"].enabled | bool
+  notify:
+    - "Restart nova-api container"
+
 - import_tasks: check-containers.yml
   when: kolla_action != "config"
diff --git a/ansible/roles/nova/templates/nova-api-wsgi.conf.j2 b/ansible/roles/nova/templates/nova-api-wsgi.conf.j2
new file mode 100644
index 0000000000..e5bf1f0c41
--- /dev/null
+++ b/ansible/roles/nova/templates/nova-api-wsgi.conf.j2
@@ -0,0 +1,70 @@
+{% set nova_log_dir = '/var/log/kolla/nova' %}
+{% set wsgi_directory = '/usr/bin' if nova_install_type == 'binary' else '/var/lib/kolla/venv/bin' %}
+{% if nova_enable_tls_backend | bool %}
+{% if kolla_base_distro in ['centos']  %}
+LoadModule ssl_module /usr/lib64/httpd/modules/mod_ssl.so
+{% else %}
+LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
+{% endif %}
+{% endif %}
+Listen {{ api_interface_address | put_address_in_context('url') }}:{{ nova_api_listen_port }}
+Listen {{ api_interface_address | put_address_in_context('url') }}:{{ nova_metadata_listen_port }}
+
+ServerSignature Off
+ServerTokens Prod
+TraceEnable off
+KeepAliveTimeout {{ kolla_httpd_keep_alive }}
+
+<Directory "{{ wsgi_directory }}">
+    <FilesMatch "^nova-(api-wsgi|metadata-wsgi)$">
+        Options None
+        Require all granted
+    </FilesMatch>
+</Directory>
+
+ErrorLog "{{ nova_log_dir }}/apache-error.log"
+<IfModule log_config_module>
+CustomLog "{{ nova_log_dir }}/apache-access.log" common
+</IfModule>
+
+{% if nova_logging_debug | bool %}
+LogLevel info
+{% endif %}
+
+<VirtualHost *:{{ nova_api_listen_port }}>
+    WSGIDaemonProcess nova-api processes={{ openstack_service_workers }} threads=1 user=nova group=nova display-name=%{GROUP}
+    WSGIProcessGroup nova-api
+    WSGIScriptAlias / {{ wsgi_directory }}/nova-api-wsgi
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+    <IfVersion >= 2.4>
+      ErrorLogFormat "%{cu}t %M"
+    </IfVersion>
+    ErrorLog "{{ nova_log_dir }}/nova-api-error.log"
+    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
+    CustomLog "{{ nova_log_dir }}/nova-api-access.log" logformat
+{% if nova_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/nova/certs/nova-cert.pem
+    SSLCertificateKeyFile /etc/nova/certs/nova-key.pem
+{% endif %}
+</VirtualHost>
+
+<VirtualHost *:{{ nova_metadata_listen_port }}>
+    WSGIDaemonProcess nova-metadata processes={{ openstack_service_workers }} threads=1 user=nova group=nova display-name=%{GROUP}
+    WSGIProcessGroup nova-metadata
+    WSGIScriptAlias / {{ wsgi_directory }}/nova-metadata-wsgi
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+    <IfVersion >= 2.4>
+      ErrorLogFormat "%{cu}t %M"
+    </IfVersion>
+    ErrorLog "{{ nova_log_dir }}/nova-metadata-error.log"
+    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
+    CustomLog "{{ nova_log_dir }}/nova-metadata-access.log" logformat
+{% if nova_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/nova/certs/nova-cert.pem
+    SSLCertificateKeyFile /etc/nova/certs/nova-key.pem
+{% endif %}
+</VirtualHost>
diff --git a/ansible/roles/nova/templates/nova-api.json.j2 b/ansible/roles/nova/templates/nova-api.json.j2
index f52b27ecc0..ea392fcbd9 100644
--- a/ansible/roles/nova/templates/nova-api.json.j2
+++ b/ansible/roles/nova/templates/nova-api.json.j2
@@ -1,17 +1,37 @@
+{% set apache_binary = 'apache2' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd' %}
+{% set apache_conf_dir = 'apache2/conf-enabled' if kolla_base_distro in ['ubuntu', 'debian'] else 'httpd/conf.d' %}
 {
-    "command": "nova-api",
+    "command": "/usr/sbin/{{ apache_binary }} -DFOREGROUND",
     "config_files": [
         {
             "source": "{{ container_config_directory }}/nova.conf",
             "dest": "/etc/nova/nova.conf",
             "owner": "nova",
             "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/nova-api-wsgi.conf",
+            "dest": "/etc/{{ apache_conf_dir }}/nova-api-wsgi.conf",
+            "owner": "nova",
+            "perm": "0600"
         }{% if nova_policy_file is defined %},
         {
             "source": "{{ container_config_directory }}/{{ nova_policy_file }}",
             "dest": "/etc/nova/{{ nova_policy_file }}",
             "owner": "nova",
             "perm": "0600"
+        }{% endif %}{% if nova_enable_tls_backend | bool %},
+        {
+            "source": "{{ container_config_directory }}/nova-cert.pem",
+            "dest": "/etc/nova/certs/nova-cert.pem",
+            "owner": "nova",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/nova-key.pem",
+            "dest": "/etc/nova/certs/nova-key.pem",
+            "owner": "nova",
+            "perm": "0600"
         }{% endif %}
     ],
     "permissions": [
diff --git a/ansible/roles/nova/templates/nova.conf.j2 b/ansible/roles/nova/templates/nova.conf.j2
index 16ef6db36e..d37200a559 100644
--- a/ansible/roles/nova/templates/nova.conf.j2
+++ b/ansible/roles/nova/templates/nova.conf.j2
@@ -8,15 +8,6 @@ log_file = /var/log/kolla/nova/nova-super-conductor.log
 {% endif %}
 
 state_path = /var/lib/nova
-
-osapi_compute_listen = {{ api_interface_address }}
-osapi_compute_listen_port = {{ nova_api_listen_port }}
-osapi_compute_workers = {{ openstack_service_workers }}
-metadata_workers = {{ openstack_service_workers }}
-
-metadata_listen = {{ api_interface_address }}
-metadata_listen_port = {{ nova_metadata_listen_port }}
-
 allow_resize_to_same_host = true
 
 # Though my_ip is not used directly, lots of other variables use $my_ip