From 61fd6b6e14363b7a6b4802dbc6b3f02a987cd9f4 Mon Sep 17 00:00:00 2001
From: Damian Dabrowski <damian.dabrowski@cleura.com>
Date: Fri, 14 Apr 2023 00:46:19 +0200
Subject: [PATCH] Add TLS support to ironic backends

By overriding the variable `ironic_backend_ssl: True` HTTPS will
be enabled, disabling HTTP support on the ironic backend api.

The ansible-role-pki is used to generate the required TLS
certificates if this functionality is enabled.

Depends-On: https://review.opendev.org/c/openstack/openstack-ansible/+/879085
Change-Id: If97a857c36e9e3e7ad8a18926bb9cbf04189c7cb
---
 defaults/main.yml           | 52 +++++++++++++++++++++++++++++++++++++
 handlers/main.yml           |  2 ++
 tasks/main.yml              | 21 +++++++++++++++
 templates/inspector.conf.j2 |  9 +++++++
 4 files changed, 84 insertions(+)

diff --git a/defaults/main.yml b/defaults/main.yml
index 82e43f4b..8c15ef0f 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -66,6 +66,7 @@ ironic_services:
     uwsgi_overrides: "{{ ironic_api_uwsgi_ini_overrides }}"
     uwsgi_port: "{{ ironic_service_port }}"
     uwsgi_bind_address: "{{ ironic_uwsgi_bind_address }}"
+    uwsgi_tls: "{{ ironic_backend_ssl | ternary(ironic_uwsgi_tls, {}) }}"
   ironic-conductor:
     group: ironic_conductor
     service_name: ironic-conductor
@@ -299,6 +300,9 @@ ironic_wsgi_threads: 1
 ironic_wsgi_processes_max: 16
 ironic_wsgi_processes: "{{ [[(ansible_facts['processor_vcpus']//ansible_facts['processor_threads_per_core'])|default(1), 1] | max * 2, ironic_wsgi_processes_max] | min }}"
 ironic_uwsgi_bind_address: "{{ openstack_service_bind_address | default('0.0.0.0') }}"
+ironic_uwsgi_tls:
+  crt: "{{ ironic_ssl_cert }}"
+  key: "{{ ironic_ssl_key }}"
 
 ### OpenStack Services to integrate with
 
@@ -491,3 +495,51 @@ ironic_inspector_oslomsg_notify_ssl_ca_file: "{{ oslomsg_notify_ssl_ca_file | de
 ironic_inspector_optional_oslomsg_amqp1_pip_packages:
   - oslo.messaging[amqp1]
 ironic_inspector_oslomsg_amqp1_enabled: True
+
+###
+### Backend TLS
+###
+
+# Define if communication between haproxy and service backends should be
+# encrypted with TLS.
+ironic_backend_ssl: "{{ openstack_service_backend_ssl | default(False) }}"
+
+# Storage location for SSL certificate authority
+ironic_pki_dir: "{{ openstack_pki_dir | default('/etc/openstack_deploy/pki') }}"
+
+# Delegated host for operating the certificate authority
+ironic_pki_setup_host: "{{ openstack_pki_setup_host | default('localhost') }}"
+
+# ironic server certificate
+ironic_pki_keys_path: "{{ ironic_pki_dir ~ '/certs/private/' }}"
+ironic_pki_certs_path: "{{ ironic_pki_dir ~ '/certs/certs/' }}"
+ironic_pki_intermediate_cert_name: "{{ openstack_pki_service_intermediate_cert_name | default('ExampleCorpIntermediate') }}"
+ironic_pki_regen_cert: ''
+ironic_pki_san: "{{ openstack_pki_san | default('DNS:' ~ ansible_facts['hostname'] ~ ',IP:' ~ management_address) }}"
+ironic_pki_certificates:
+  - name: "ironic_{{ ansible_facts['hostname'] }}"
+    provider: ownca
+    cn: "{{ ansible_facts['hostname'] }}"
+    san: "{{ ironic_pki_san }}"
+    signed_by: "{{ ironic_pki_intermediate_cert_name }}"
+
+# ironic destination files for SSL certificates
+ironic_ssl_cert: /etc/ironic/ironic.pem
+ironic_ssl_key: /etc/ironic/ironic.key
+
+# Installation details for SSL certificates
+ironic_pki_install_certificates:
+  - src: "{{ ironic_user_ssl_cert | default(ironic_pki_certs_path ~ 'ironic_' ~ ansible_facts['hostname'] ~ '-chain.crt') }}"
+    dest: "{{ ironic_ssl_cert }}"
+    owner: "{{ ironic_system_user_name }}"
+    group: "{{ ironic_system_user_name }}"
+    mode: "0644"
+  - src: "{{ ironic_user_ssl_key | default(ironic_pki_keys_path ~ 'ironic_' ~ ansible_facts['hostname'] ~ '.key.pem') }}"
+    dest: "{{ ironic_ssl_key }}"
+    owner: "{{ ironic_system_user_name }}"
+    group: "{{ ironic_system_user_name }}"
+    mode: "0600"
+
+# Define user-provided SSL certificates
+#ironic_user_ssl_cert: <path to cert on ansible deployment host>
+#ironic_user_ssl_key: <path to cert on ansible deployment host>
diff --git a/handlers/main.yml b/handlers/main.yml
index 9cfd70d2..226d786f 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -23,6 +23,7 @@
   listen:
     - "venv changed"
     - "systemd service changed"
+    - "cert installed"
 
 - name: Restart tftpd
   service:
@@ -56,3 +57,4 @@
   delay: 2
   listen:
     - "venv changed"
+    - "cert installed"
diff --git a/tasks/main.yml b/tasks/main.yml
index 9c0fa35c..9787ca85 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -108,6 +108,27 @@
   tags:
     - ironic-install
 
+- name: Create and install SSL certificates
+  include_role:
+    name: pki
+    tasks_from: main_certs.yml
+    apply:
+      tags:
+        - ironic-config
+        - pki
+  vars:
+    pki_setup_host: "{{ ironic_pki_setup_host }}"
+    pki_dir: "{{ ironic_pki_dir }}"
+    pki_create_certificates: "{{ ironic_user_ssl_cert is not defined and ironic_user_ssl_key is not defined }}"
+    pki_regen_cert: "{{ ironic_pki_regen_cert }}"
+    pki_certificates: "{{ ironic_pki_certificates }}"
+    pki_install_certificates: "{{ ironic_pki_install_certificates }}"
+  when:
+    - ironic_backend_ssl
+    - "'ironic_api' in group_names or 'ironic_inspector' in group_names"
+  tags:
+    - always
+
 - name: Install the python venv
   import_role:
     name: "python_venv_build"
diff --git a/templates/inspector.conf.j2 b/templates/inspector.conf.j2
index 4d40adc4..277badaf 100644
--- a/templates/inspector.conf.j2
+++ b/templates/inspector.conf.j2
@@ -3,6 +3,9 @@
 [DEFAULT]
 listen_address = {{ ironic_inspector_service_address }}
 listen_port = {{ ironic_inspector_service_port }}
+{% if ironic_backend_ssl | bool %}
+use_ssl = True
+{% endif %}
 
 rootwrap_config = /etc/ironic-inspector/rootwrap.conf
 auth_strategy = keystone
@@ -11,6 +14,12 @@ debug = {{ debug }}
 # RPC Backend
 transport_url = {{ ironic_inspector_oslomsg_rpc_transport }}://{% for host in ironic_inspector_oslomsg_rpc_servers.split(',') %}{{ ironic_inspector_oslomsg_rpc_userid }}:{{ ironic_oslomsg_rpc_password }}@{{ host }}:{{ ironic_inspector_oslomsg_rpc_port }}{% if not loop.last %},{% else %}/{{ ironic_inspector_oslomsg_rpc_vhost }}{% if ironic_inspector_oslomsg_rpc_use_ssl | bool %}?ssl=1{% else %}?ssl=0{% endif %}{% endif %}{% endfor %}
 
+{% if ironic_backend_ssl | bool %}
+[ssl]
+cert_file = {{ ironic_ssl_cert }}
+key_file = {{ ironic_ssl_key }}
+{% endif %}
+
 [capabilities]
 
 [cors]