Implement X.509 auth for MySQL and Nova

cluster:cotrol:
- system.salt.minion.cert.mysql.clients.openstack.nova

nova:
  controller:
    database:
      x509:
        enabled: True
        ca_file: ${_param:mysql_nova_ssl_ca_file}
        cert_file: ${_param:mysql_nova_client_ssl_cert_file}
        key_file: ${_param:mysql_nova_client_ssl_key_file}

cluster:db
- system.galera.server.database.x509.nova

Related-PROD: PROD-19981

Change-Id: I5402a4f4f34ca7472dd537fbceda70a2ca3b6c9a
diff --git a/README.rst b/README.rst
index 1e367dc..b93b061 100644
--- a/README.rst
+++ b/README.rst
@@ -1059,6 +1059,27 @@
 You can read more about it here:
     https://docs.openstack.org/mitaka/config-reference/dashboard/configure.html
 
+Enable x509 and ssl communication between Nova and Galera cluster.
+---------------------
+By default communication between Nova and Galera is unsecure.
+
+You able to set custom certificates in pillar:
+controller:
+  database:
+    x509:
+      enabled: True
+
+nova:
+  controller:
+    database:
+      x509:
+        cacert (certificate content)
+        cert (certificate content)
+        key (certificate content)
+
+You can read more about it here:
+    https://docs.openstack.org/security-guide/databases/database-access-control.html
+
 Documentation and Bugs
 ======================
 
diff --git a/nova/_ssl/mysql.sls b/nova/_ssl/mysql.sls
new file mode 100644
index 0000000..3f44e2a
--- /dev/null
+++ b/nova/_ssl/mysql.sls
@@ -0,0 +1,58 @@
+{% from "nova/map.jinja" import controller with context %}
+
+{%- if controller.database.get('x509',{}).get('enabled',False) %}
+
+  {%- set ca_file=controller.database.x509.get('ca_file') %}
+  {%- set key_file=controller.database.x509.get('key_file') %}
+  {%- set cert_file=controller.database.x509.get('cert_file') %}
+
+mysql_nova_ssl_x509_ca:
+  {%- if controller.database.x509.cacert is defined %}
+  file.managed:
+    - name: {{ ca_file }}
+    - contents_pillar: nova:controller:database:x509:cacert
+    - mode: 444
+    - makedirs: true
+  {%- else %}
+  file.exists:
+    - name: {{ ca_file }}
+  {%- endif %}
+
+mysql_nova_client_ssl_cert:
+  {%- if controller.database.x509.cert is defined %}
+  file.managed:
+    - name: {{ cert_file }}
+    - contents_pillar: nova:controller:database:x509:cert
+    - mode: 440
+    - makedirs: true
+  {%- else %}
+  file.exists:
+    - name: {{ cert_file }}
+  {%- endif %}
+
+mysql_nova_client_ssl_private_key:
+  {%- if controller.database.x509.key is defined %}
+  file.managed:
+    - name: {{ key_file }}
+    - contents_pillar: nova:controller:database:x509:key
+    - mode: 400
+    - makedirs: true
+  {%- else %}
+  file.exists:
+    - name: {{ key_file }}
+  {%- endif %}
+
+{% elif controller.database.get('ssl',{}).get('enabled',False) %}
+mysql_ca_nova_controller:
+  {%- if controller.database.ssl.cacert is defined %}
+  file.managed:
+    - name: {{ controller.database.ssl.cacert_file }}
+    - contents_pillar: nova:controller:database:ssl:cacert
+    - mode: 0444
+    - makedirs: true
+  {%- else %}
+  file.exists:
+    - name: {{ controller.database.ssl.get('cacert_file', controller.cacert_file) }}
+  {%- endif %}
+
+{%- endif %}
diff --git a/nova/controller.sls b/nova/controller.sls
index 1fc2886..f7c9c09 100644
--- a/nova/controller.sls
+++ b/nova/controller.sls
@@ -1,16 +1,21 @@
 {% from "nova/map.jinja" import controller with context %}
 
+{%- set mysql_x509_ssl_enabled = controller.database.get('x509',{}).get('enabled',False) or controller.database.get('ssl',{}).get('enabled',False) %}
+
 {%- if controller.get('enabled') %}
 
 include:
 {#- Always include apache when horizon/apache formulas doesn't intersect #}
-{%- if pillar.get('apache', {}).get('server', {}).get('site', {}).nova_placement is defined %}
- - apache
-{%- endif %}
- - nova.db.offline_sync
- # TODO(vsaienko) we need to run online dbsync only once after upgrade
- # Move to appropriate upgrade phase
- - nova.db.online_sync
+  {%- if pillar.get('apache', {}).get('server', {}).get('site', {}).nova_placement is defined %}
+  - apache
+  {%- endif %}
+  - nova.db.offline_sync
+  # TODO(vsaienko) we need to run online dbsync only once after upgrade
+  # Move to appropriate upgrade phase
+  - nova.db.online_sync
+  {%- if mysql_x509_ssl_enabled %}
+  - nova._ssl.mysql
+  {%- endif %}
 
 {%- if grains.os_family == 'Debian' %}
 debconf-set-prerequisite:
@@ -449,16 +454,15 @@
   {%- endif %}
   - require:
     - sls: nova.db.offline_sync
+    {%- if mysql_x509_ssl_enabled %}
+    - sls: nova._ssl.mysql
+    {%- endif %}
   - watch:
     - file: /etc/nova/nova.conf
     - file: /etc/nova/api-paste.ini
     - nova_placement_apache_conf_file
-    {%- if controller.database.get('ssl',{}).get('enabled',False)  %}
-    - file: mysql_ca_nova_controller
-    {% endif %}
 
 {%- endif %}
-
 nova_controller_services:
   service.running:
   - enable: true
@@ -470,15 +474,15 @@
     - sls: nova.db.offline_sync
   - require_in:
     - sls: nova.db.online_sync
+    {%- if mysql_x509_ssl_enabled %}
+    - sls: nova._ssl.mysql
+    {%- endif %}
   - watch:
     - file: /etc/nova/nova.conf
     - file: /etc/nova/api-paste.ini
     {%- if controller.message_queue.get('ssl',{}).get('enabled',False) %}
     - file: rabbitmq_ca_nova_controller
     {%- endif %}
-    {%- if controller.database.get('ssl',{}).get('enabled',False)  %}
-    - file: mysql_ca_nova_controller
-    {% endif %}
 
 {%- if grains.get('virtual_subtype', None) == "Docker" %}
 
@@ -491,22 +495,4 @@
 
 {%- endif %}
 
-{%- if controller.database.get('ssl',{}).get('enabled',False)  %}
-mysql_ca_nova_controller:
-{%- if controller.database.ssl.cacert is defined %}
-  file.managed:
-    - name: {{ controller.database.ssl.cacert_file }}
-    - contents_pillar: nova:controller:database:ssl:cacert
-    - mode: 0444
-    - makedirs: true
-    - require_in:
-      - file: /etc/nova/nova.conf
-{%- else %}
-  file.exists:
-   - name: {{ controller.database.ssl.get('cacert_file', controller.cacert_file) }}
-   - require_in:
-     - file: /etc/nova/nova.conf
-{%- endif %}
-{%- endif %}
-
 {%- endif %}
diff --git a/nova/files/pike/nova-controller.conf.Debian b/nova/files/pike/nova-controller.conf.Debian
index f6979f4..c2bdc87 100644
--- a/nova/files/pike/nova-controller.conf.Debian
+++ b/nova/files/pike/nova-controller.conf.Debian
@@ -1,6 +1,13 @@
 {%- from "nova/map.jinja" import controller,compute_driver_mapping with context %}
-[DEFAULT]
 
+{%- set connection_x509_ssl_option = '' %}
+{%- if controller.database.get('x509',{}).get('enabled',False) %}
+  {%- set connection_x509_ssl_option = '&ssl_ca=' ~ controller.database.x509.get('ca_file') ~ '&ssl_cert=' ~ controller.database.x509.get('cert_file') ~ '&ssl_key=' ~ controller.database.x509.get('key_file') %}
+{%- elif controller.database.get('ssl',{}).get('enabled',False) %}
+  {%- set connection_x509_ssl_option = '&ssl_ca=' ~ controller.database.ssl.get('cacert_file', controller.cacert_file) %}
+{%- endif %}
+
+[DEFAULT]
 #
 # From nova.conf
 #
@@ -3438,7 +3445,7 @@
 db_retry_interval = 1
 connection_debug = 10
 pool_timeout = 120
-connection = {{ controller.database.engine }}+pymysql://{{ controller.database.user }}:{{ controller.database.password }}@{{ controller.database.host }}/{{ controller.database.name }}_api?charset=utf8{%- if controller.database.get('ssl',{}).get('enabled',False) %}&ssl_ca={{ controller.database.ssl.get('cacert_file', controller.cacert_file) }}{% endif %}
+connection = {{ controller.database.engine }}+pymysql://{{ controller.database.user }}:{{ controller.database.password }}@{{ controller.database.host }}/{{ controller.database.name }}_api?charset=utf8{{ connection_x509_ssl_option }}
 
 # The SQLAlchemy connection string to use to connect to the database. (string
 # value)
@@ -4487,7 +4494,7 @@
 db_retry_interval = 1
 connection_debug = 10
 pool_timeout = 120
-connection = {{ controller.database.engine }}+pymysql://{{ controller.database.user }}:{{ controller.database.password }}@{{ controller.database.host }}/{{ controller.database.name }}?charset=utf8{%- if controller.database.get('ssl',{}).get('enabled',False) %}&ssl_ca={{ controller.database.ssl.get('cacert_file', controller.cacert_file) }}{% endif %}
+connection = {{ controller.database.engine }}+pymysql://{{ controller.database.user }}:{{ controller.database.password }}@{{ controller.database.host }}/{{ controller.database.name }}?charset=utf8{{ connection_x509_ssl_option }}
 
 # If True, SQLite uses synchronous mode. (boolean value)
 # Deprecated group/name - [DEFAULT]/sqlite_synchronous