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