From 316b0496b3dd7a9b33692b171391d9d17d535116 Mon Sep 17 00:00:00 2001
From: James Kirsch <generalfuzz@gmail.com>
Date: Mon, 17 Aug 2020 14:28:39 -0700
Subject: [PATCH] Add support for encrypting Ironic API

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

Change-Id: I3e82c8ec112e53f907e89fea0c8c849072dcf957
Partially-Implements: blueprint add-ssl-internal-network
Depends-On: https://review.opendev.org/#/c/742776/
---
 ansible/roles/ironic/defaults/main.yml        |  9 ++++
 ansible/roles/ironic/handlers/main.yml        |  1 +
 ansible/roles/ironic/tasks/config.yml         | 26 +++++++++-
 .../ironic/templates/ironic-api-wsgi.conf.j2  | 50 +++++++++++++++++++
 .../roles/ironic/templates/ironic-api.json.j2 | 22 +++++++-
 .../templates/ironic-inspector-wsgi.conf.j2   | 50 +++++++++++++++++++
 .../ironic/templates/ironic-inspector.json.j2 | 22 +++++++-
 ansible/roles/ironic/templates/ironic.conf.j2 |  7 ---
 8 files changed, 177 insertions(+), 10 deletions(-)
 create mode 100644 ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2
 create mode 100644 ansible/roles/ironic/templates/ironic-inspector-wsgi.conf.j2

diff --git a/ansible/roles/ironic/defaults/main.yml b/ansible/roles/ironic/defaults/main.yml
index 2305157d68..f516a58909 100644
--- a/ansible/roles/ironic/defaults/main.yml
+++ b/ansible/roles/ironic/defaults/main.yml
@@ -16,12 +16,14 @@ ironic_services:
         external: false
         port: "{{ ironic_api_port }}"
         listen_port: "{{ ironic_api_listen_port }}"
+        tls_backend: "{{ ironic_enable_tls_backend }}"
       ironic_api_external:
         enabled: "{{ enable_ironic }}"
         mode: "http"
         external: true
         port: "{{ ironic_api_port }}"
         listen_port: "{{ ironic_api_listen_port }}"
+        tls_backend: "{{ ironic_enable_tls_backend }}"
   ironic-conductor:
     container_name: ironic_conductor
     group: ironic-conductor
@@ -45,12 +47,14 @@ ironic_services:
         external: false
         port: "{{ ironic_inspector_port }}"
         listen_port: "{{ ironic_inspector_listen_port }}"
+        tls_backend: "{{ ironic_enable_tls_backend }}"
       ironic_inspector_external:
         enabled: "{{ enable_ironic }}"
         mode: "http"
         external: true
         port: "{{ ironic_inspector_port }}"
         listen_port: "{{ ironic_inspector_listen_port }}"
+        tls_backend: "{{ ironic_enable_tls_backend }}"
   ironic-pxe:
     container_name: ironic_pxe
     group: ironic-pxe
@@ -253,3 +257,8 @@ ironic_ks_users:
     user: "{{ ironic_inspector_keystone_user }}"
     password: "{{ ironic_inspector_keystone_password }}"
     role: "admin"
+
+####################
+# TLS
+####################
+ironic_enable_tls_backend: "{{ kolla_enable_tls_backend }}"
diff --git a/ansible/roles/ironic/handlers/main.yml b/ansible/roles/ironic/handlers/main.yml
index 458d648e1b..a1b701506e 100644
--- a/ansible/roles/ironic/handlers/main.yml
+++ b/ansible/roles/ironic/handlers/main.yml
@@ -40,6 +40,7 @@
     module_name: uri
     module_args:
       url: "{{ ironic_internal_endpoint }}"
+      validate_certs: false
   register: result
   until: result is success
   retries: 12
diff --git a/ansible/roles/ironic/tasks/config.yml b/ansible/roles/ironic/tasks/config.yml
index 26111f48f5..6f0ce9934a 100644
--- a/ansible/roles/ironic/tasks/config.yml
+++ b/ansible/roles/ironic/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 ironic_enable_tls_backend | bool
 
 - name: Copying over config.json files for services
   template:
@@ -244,5 +244,29 @@
   notify:
     - "Restart {{ item.key }} container"
 
+- name: Copying over ironic-api-wsgi.conf
+  template:
+    src: "ironic-api-wsgi.conf.j2"
+    dest: "{{ node_config_directory }}/ironic-api/ironic-api-wsgi.conf"
+    mode: "0660"
+  become: true
+  when:
+    - inventory_hostname in groups["ironic-api"]
+    - ironic_services["ironic-api"].enabled | bool
+  notify:
+    - "Restart ironic-api container"
+
+- name: Copying over ironic-inspector-wsgi.conf
+  template:
+    src: "ironic-inspector-wsgi.conf.j2"
+    dest: "{{ node_config_directory }}/ironic-inspector/ironic-inspector-wsgi.conf"
+    mode: "0660"
+  become: true
+  when:
+    - inventory_hostname in groups["ironic-inspector"]
+    - ironic_services["ironic-inspector"].enabled | bool
+  notify:
+    - "Restart ironic-inspector container"
+
 - import_tasks: check-containers.yml
   when: kolla_action != "config"
diff --git a/ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2 b/ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2
new file mode 100644
index 0000000000..c83138f274
--- /dev/null
+++ b/ansible/roles/ironic/templates/ironic-api-wsgi.conf.j2
@@ -0,0 +1,50 @@
+{% set ironic_log_dir = '/var/log/kolla/ironic' %}
+{% set wsgi_directory = '/usr/bin' if ironic_install_type == 'binary' else '/var/lib/kolla/venv/bin' %}
+{% if ironic_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') }}:{{ ironic_api_listen_port }}
+
+ServerSignature Off
+ServerTokens Prod
+TraceEnable off
+KeepAliveTimeout {{ kolla_httpd_keep_alive }}
+
+<Directory "{{ wsgi_directory }}">
+    <FilesMatch "^ironic-api-wsgi$">
+        Options None
+        Require all granted
+    </FilesMatch>
+</Directory>
+
+ErrorLog "{{ ironic_log_dir }}/apache-error.log"
+<IfModule log_config_module>
+CustomLog "{{ ironic_log_dir }}/apache-access.log" common
+</IfModule>
+
+{% if ironic_logging_debug | bool %}
+LogLevel info
+{% endif %}
+
+<VirtualHost *:{{ ironic_api_listen_port }}>
+    WSGIDaemonProcess ironic-api processes={{ openstack_service_workers }} threads=1 user=ironic group=ironic display-name=%{GROUP}
+    WSGIProcessGroup ironic-api
+    WSGIScriptAlias / {{ wsgi_directory }}/ironic-api-wsgi
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+    <IfVersion >= 2.4>
+      ErrorLogFormat "%{cu}t %M"
+    </IfVersion>
+    ErrorLog "{{ ironic_log_dir }}/ironic-api-error.log"
+    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
+    CustomLog "{{ ironic_log_dir }}/ironic-api-access.log" logformat
+{% if ironic_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/ironic/certs/ironic-cert.pem
+    SSLCertificateKeyFile /etc/ironic/certs/ironic-key.pem
+{% endif %}
+</VirtualHost>
diff --git a/ansible/roles/ironic/templates/ironic-api.json.j2 b/ansible/roles/ironic/templates/ironic-api.json.j2
index 075b0d04ee..a03a6a6719 100644
--- a/ansible/roles/ironic/templates/ironic-api.json.j2
+++ b/ansible/roles/ironic/templates/ironic-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": "ironic-api",
+    "command": "/usr/sbin/{{ apache_binary }} -DFOREGROUND",
     "config_files": [
         {
             "source": "{{ container_config_directory }}/ironic.conf",
             "dest": "/etc/ironic/ironic.conf",
             "owner": "ironic",
             "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ironic-api-wsgi.conf",
+            "dest": "/etc/{{ apache_conf_dir }}/ironic-api-wsgi.conf",
+            "owner": "ironic",
+            "perm": "0600"
         }{% if ironic_policy_file is defined %},
         {
             "source": "{{ container_config_directory }}/{{ ironic_policy_file }}",
             "dest": "/etc/ironic/{{ ironic_policy_file }}",
             "owner": "ironic",
             "perm": "0600"
+        }{% endif %}{% if ironic_enable_tls_backend | bool %},
+        {
+            "source": "{{ container_config_directory }}/ironic-cert.pem",
+            "dest": "/etc/ironic/certs/ironic-cert.pem",
+            "owner": "ironic",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ironic-key.pem",
+            "dest": "/etc/ironic/certs/ironic-key.pem",
+            "owner": "ironic",
+            "perm": "0600"
         }{% endif %}
     ],
     "permissions": [
diff --git a/ansible/roles/ironic/templates/ironic-inspector-wsgi.conf.j2 b/ansible/roles/ironic/templates/ironic-inspector-wsgi.conf.j2
new file mode 100644
index 0000000000..9e0ab3ed2d
--- /dev/null
+++ b/ansible/roles/ironic/templates/ironic-inspector-wsgi.conf.j2
@@ -0,0 +1,50 @@
+{% set ironic_log_dir = '/var/log/kolla/ironic' %}
+{% set wsgi_directory = '/usr/bin' if ironic_install_type == 'binary' else '/var/lib/kolla/venv/bin' %}
+{% if ironic_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') }}:{{ ironic_inspector_listen_port }}
+
+ServerSignature Off
+ServerTokens Prod
+TraceEnable off
+KeepAliveTimeout {{ kolla_httpd_keep_alive }}
+
+<Directory "{{ wsgi_directory }}">
+    <FilesMatch "^ironic-inspector-wsgi$">
+        Options None
+        Require all granted
+    </FilesMatch>
+</Directory>
+
+ErrorLog "{{ ironic_log_dir }}/apache-error-ironic-inspector.log"
+<IfModule log_config_module>
+CustomLog "{{ ironic_log_dir }}/apache-access-ironic-inspector.log" common
+</IfModule>
+
+{% if ironic_logging_debug | bool %}
+LogLevel info
+{% endif %}
+
+<VirtualHost *:{{ ironic_inspector_listen_port }}>
+    WSGIDaemonProcess ironic-inspector processes={{ openstack_service_workers }} threads=1 user=ironic group=ironic display-name=%{GROUP}
+    WSGIProcessGroup ironic-inspector
+    WSGIScriptAlias / {{ wsgi_directory }}/ironic-inspector-wsgi
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+    <IfVersion >= 2.4>
+      ErrorLogFormat "%{cu}t %M"
+    </IfVersion>
+    ErrorLog "{{ ironic_log_dir }}/ironic-inspector-error.log"
+    LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\"" logformat
+    CustomLog "{{ ironic_log_dir }}/ironic-inspector-access.log" logformat
+{% if ironic_enable_tls_backend | bool %}
+    SSLEngine on
+    SSLCertificateFile /etc/ironic/certs/ironic-cert.pem
+    SSLCertificateKeyFile /etc/ironic/certs/ironic-key.pem
+{% endif %}
+</VirtualHost>
diff --git a/ansible/roles/ironic/templates/ironic-inspector.json.j2 b/ansible/roles/ironic/templates/ironic-inspector.json.j2
index d82d506d3d..d07e605be0 100644
--- a/ansible/roles/ironic/templates/ironic-inspector.json.j2
+++ b/ansible/roles/ironic/templates/ironic-inspector.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": "ironic-inspector --config-file /etc/ironic-inspector/inspector.conf",
+    "command": "/usr/sbin/{{ apache_binary }} -DFOREGROUND",
     "config_files": [
         {
             "source": "{{ container_config_directory }}/inspector.conf",
             "dest": "/etc/ironic-inspector/inspector.conf",
             "owner": "ironic-inspector",
             "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ironic-inspector-wsgi.conf",
+            "dest": "/etc/{{ apache_conf_dir }}/ironic-inspector-wsgi.conf",
+            "owner": "ironic",
+            "perm": "0600"
         }{% if ironic_policy_file is defined %},
         {
             "source": "{{ container_config_directory }}/{{ ironic_policy_file }}",
             "dest": "/etc/ironic/{{ ironic_policy_file }}",
             "owner": "ironic",
             "perm": "0600"
+        }{% endif %}{% if ironic_enable_tls_backend | bool %},
+        {
+            "source": "{{ container_config_directory }}/ironic-cert.pem",
+            "dest": "/etc/ironic/certs/ironic-cert.pem",
+            "owner": "ironic",
+            "perm": "0600"
+        },
+        {
+            "source": "{{ container_config_directory }}/ironic-key.pem",
+            "dest": "/etc/ironic/certs/ironic-key.pem",
+            "owner": "ironic",
+            "perm": "0600"
         }{% endif %}
     ]
 }
diff --git a/ansible/roles/ironic/templates/ironic.conf.j2 b/ansible/roles/ironic/templates/ironic.conf.j2
index 7e79cd2789..666adc5934 100644
--- a/ansible/roles/ironic/templates/ironic.conf.j2
+++ b/ansible/roles/ironic/templates/ironic.conf.j2
@@ -35,13 +35,6 @@ driver = noop
 policy_file = {{ ironic_policy_file }}
 {% endif %}
 
-{% if service_name == 'ironic-api' %}
-[api]
-host_ip = {{ api_interface_address }}
-port = {{ ironic_api_listen_port }}
-api_workers = {{ openstack_service_workers }}
-{% endif %}
-
 {% if service_name == 'ironic-conductor' %}
 [conductor]
 automated_clean=false