RabbitMQ TLS support

OSCORE-380
Change-Id: I93ead9105820fe7462b7bd9b76d51f89ce5950c6
Releases: Mitaka, Newton, Ocata
Usage: see README.rst
diff --git a/README.rst b/README.rst
index 04f22eb..af03680 100644
--- a/README.rst
+++ b/README.rst
@@ -125,6 +125,60 @@
           virtual_host: '/openstack'
         ....
 
+Client-side RabbitMQ TLS configuration:
+---------------------------------------
+
+To enable TLS for oslo.messaging you need to provide the CA certificate.
+
+By default system-wide CA certs are used. Nothing should be specified except `ssl.enabled`.
+
+.. code-block:: yaml
+
+  glance:
+    server:
+      ....
+      message_queue:
+        ssl:
+          enabled: True
+
+
+
+Use `cacert_file` option to specify the CA-cert file path explicitly:
+
+.. code-block:: yaml
+
+  glance:
+    server:
+      ....
+      message_queue:
+        ssl:
+          enabled: True
+          cacert_file: /etc/ssl/rabbitmq-ca.pem
+
+To manage content of the `cacert_file` use the `cacert` option:
+
+.. code-block:: yaml
+
+  glance:
+    server:
+      ....
+      message_queue:
+        ssl:
+          enabled: True
+          cacert: |
+
+          -----BEGIN CERTIFICATE-----
+                    ...
+          -----END CERTIFICATE-------
+
+          cacert_file: /etc/openstack/rabbitmq-ca.pem
+
+
+Notice:
+ * The `message_queue.port` is set to **5671** (AMQPS) by default if `ssl.enabled=True`.
+ * Use `message_queue.ssl.version` if you need to specify protocol version. By default is TLSv1 for python < 2.7.9 and TLSv1_2 for version above.
+
+
 Enable Glance Image Cache:
 
 .. code-block:: yaml
diff --git a/glance/files/mitaka/glance-api.conf.Debian b/glance/files/mitaka/glance-api.conf.Debian
index b4f1b69..7776dfe 100644
--- a/glance/files/mitaka/glance-api.conf.Debian
+++ b/glance/files/mitaka/glance-api.conf.Debian
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 {% set storage_engines = server.storage.engine.split(',') %}
 [DEFAULT]
 
@@ -1607,19 +1607,36 @@
 # Allowed values: round-robin, shuffle
 #kombu_failover_strategy = round-robin
 
+{%- set rabbit_port = server.message_queue.get('port', 5671 if server.message_queue.get('ssl',{}).get('enabled', False)  else 5672) %}
 {%- if server.message_queue.members is defined %}
 rabbit_hosts = {% for member in server.message_queue.members -%}
-                   {{ member.host }}:{{ member.get('port', 5672) }}
+                   {{ member.host }}:{{ member.get('port', rabbit_port) }}
                    {%- if not loop.last -%},{%- endif -%}
                {%- endfor -%}
 {%- else %}
 rabbit_host = {{ server.message_queue.host }}
-rabbit_port = {{ server.message_queue.port }}
+rabbit_port = {{ rabbit_port }}
 {%- endif %}
 
 # Connect over SSL for RabbitMQ. (boolean value)
 # Deprecated group/name - [DEFAULT]/rabbit_use_ssl
-#rabbit_use_ssl = false
+
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbit_use_ssl=true
+
+{%- if server.message_queue.ssl.version is defined %}
+kombu_ssl_version = {{ server.message_queue.ssl.version }}
+{%- elif salt['grains.get']('pythonversion') > [2,7,8] %}
+kombu_ssl_version = TLSv1_2
+{%- endif %}
+
+{%- if server.message_queue.ssl.cacert_file is defined %}
+kombu_ssl_ca_certs = {{ server.message_queue.ssl.cacert_file }}
+{%- else %}
+kombu_ssl_ca_certs={{ system_cacerts_file }}
+{%- endif %}
+{%- endif %}
+
 rabbit_userid = {{ server.message_queue.user }}
 rabbit_password = {{ server.message_queue.password }}
 
diff --git a/glance/files/mitaka/glance-registry.conf.Debian b/glance/files/mitaka/glance-registry.conf.Debian
index d1684e9..e85c990 100644
--- a/glance/files/mitaka/glance-registry.conf.Debian
+++ b/glance/files/mitaka/glance-registry.conf.Debian
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 [DEFAULT]
 
 #
@@ -1149,20 +1149,36 @@
 
 
 [oslo_messaging_rabbit]
-
+{%- set rabbit_port = server.message_queue.get('port', 5671 if server.message_queue.get('ssl',{}).get('enabled', False)  else 5672) %}
 {%- if server.message_queue.members is defined %}
 rabbit_hosts = {% for member in server.message_queue.members -%}
-                   {{ member.host }}:{{ member.get('port', 5672) }}
+                   {{ member.host }}:{{ member.get('port', rabbit_port) }}
                    {%- if not loop.last -%},{%- endif -%}
                {%- endfor -%}
 {%- else %}
 rabbit_host = {{ server.message_queue.host }}
-rabbit_port = {{ server.message_queue.port }}
+rabbit_port = {{ rabbit_port }}
 {%- endif %}
 rabbit_userid = {{ server.message_queue.user }}
 rabbit_password = {{ server.message_queue.password }}
 rabbit_virtual_host = {{ server.message_queue.virtual_host }}
 
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbit_use_ssl=true
+
+{%- if server.message_queue.ssl.version is defined %}
+kombu_ssl_version = {{ server.message_queue.ssl.version }}
+{%- elif salt['grains.get']('pythonversion') > [2,7,8] %}
+kombu_ssl_version = TLSv1_2
+{%- endif %}
+
+{%- if server.message_queue.ssl.cacert_file is defined %}
+kombu_ssl_ca_certs = {{ server.message_queue.ssl.cacert_file }}
+{%- else %}
+kombu_ssl_ca_certs={{ system_cacerts_file }}
+{%- endif %}
+{%- endif %}
+
 #
 # From oslo.messaging
 #
diff --git a/glance/files/newton/glance-api.conf.Debian b/glance/files/newton/glance-api.conf.Debian
index 90f6117..7f77ba8 100644
--- a/glance/files/newton/glance-api.conf.Debian
+++ b/glance/files/newton/glance-api.conf.Debian
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 {% set storage_engines = server.storage.engine.split(',') %}
 [DEFAULT]
 
@@ -1678,14 +1678,16 @@
 # A URL representing the messaging driver to use and its full configuration.
 # (string value)
 #transport_url = <None>
+
+{%- set rabbit_port = server.message_queue.get('port', 5671 if server.message_queue.get('ssl',{}).get('enabled', False)  else 5672) %}
 {%- if server.message_queue.members is defined %}
 transport_url = rabbit://{% for member in server.message_queue.members -%}
-                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', 5672) }}
+                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', rabbit_port) }}
                              {%- if not loop.last -%},{%- endif -%}
                          {%- endfor -%}
                              /{{ server.message_queue.virtual_host }}
 {%- else %}
-transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ server.message_queue.port }}/{{ server.message_queue.virtual_host }}
+transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ rabbit_port }}/{{ server.message_queue.virtual_host }}
 {%- endif %}
 
 # DEPRECATED: The messaging driver to use, defaults to rabbit. Other drivers
@@ -1694,7 +1696,6 @@
 # Its value may be silently ignored in the future.
 # Reason: Replaced by [DEFAULT]/transport_url
 #rpc_backend = rabbit
-rpc_backend = rabbit
 
 # The default exchange under which topics are scoped. May be overridden by an
 # exchange name specified in the transport_url option. (string value)
@@ -3738,6 +3739,23 @@
 # From oslo.messaging
 #
 
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbit_use_ssl=true
+
+{%- if server.message_queue.ssl.version is defined %}
+kombu_ssl_version = {{ server.message_queue.ssl.version }}
+{%- elif salt['grains.get']('pythonversion') > [2,7,8] %}
+kombu_ssl_version = TLSv1_2
+{%- endif %}
+
+{%- if server.message_queue.ssl.cacert_file is defined %}
+kombu_ssl_ca_certs = {{ server.message_queue.ssl.cacert_file }}
+{%- else %}
+kombu_ssl_ca_certs={{ system_cacerts_file }}
+{%- endif %}
+{%- endif %}
+
+
 # Use durable queues in AMQP. (boolean value)
 # Deprecated group/name - [DEFAULT]/amqp_durable_queues
 # Deprecated group/name - [DEFAULT]/rabbit_durable_queues
diff --git a/glance/files/newton/glance-registry.conf.Debian b/glance/files/newton/glance-registry.conf.Debian
index f736108..915ff6b 100644
--- a/glance/files/newton/glance-registry.conf.Debian
+++ b/glance/files/newton/glance-registry.conf.Debian
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 [DEFAULT]
 
 #
@@ -1004,22 +1004,24 @@
 # A URL representing the messaging driver to use and its full configuration.
 # (string value)
 #transport_url = <None>
+
+{%- set rabbit_port = server.message_queue.get('port', 5671 if server.message_queue.get('ssl',{}).get('enabled', False)  else 5672) %}
 {%- if server.message_queue.members is defined %}
 transport_url = rabbit://{% for member in server.message_queue.members -%}
-                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', 5672) }}
+                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', rabbit_port) }}
                              {%- if not loop.last -%},{%- endif -%}
                          {%- endfor -%}
                              {{ server.message_queue.virtual_host }}
 {%- else %}
-transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ server.message_queue.port }}/{{ server.message_queue.virtual_host }}
+transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ rabbit_port }}/{{ server.message_queue.virtual_host }}
 {%- endif %}
+
 # DEPRECATED: The messaging driver to use, defaults to rabbit. Other drivers
 # include amqp and zmq. (string value)
 # This option is deprecated for removal.
 # Its value may be silently ignored in the future.
 # Reason: Replaced by [DEFAULT]/transport_url
 #rpc_backend = rabbit
-rpc_backend = rabbit
 
 # The default exchange under which topics are scoped. May be overridden by an
 # exchange name specified in the transport_url option. (string value)
@@ -1566,6 +1568,22 @@
 # From oslo.messaging
 #
 
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbit_use_ssl=true
+
+{%- if server.message_queue.ssl.version is defined %}
+kombu_ssl_version = {{ server.message_queue.ssl.version }}
+{%- elif salt['grains.get']('pythonversion') > [2,7,8] %}
+kombu_ssl_version = TLSv1_2
+{%- endif %}
+
+{%- if server.message_queue.ssl.cacert_file is defined %}
+kombu_ssl_ca_certs = {{ server.message_queue.ssl.cacert_file }}
+{%- else %}
+kombu_ssl_ca_certs={{ system_cacerts_file }}
+{%- endif %}
+{%- endif %}
+
 # Use durable queues in AMQP. (boolean value)
 # Deprecated group/name - [DEFAULT]/amqp_durable_queues
 # Deprecated group/name - [DEFAULT]/rabbit_durable_queues
diff --git a/glance/files/ocata/glance-api.conf.Debian b/glance/files/ocata/glance-api.conf.Debian
index 7ae7ded..9175c78 100644
--- a/glance/files/ocata/glance-api.conf.Debian
+++ b/glance/files/ocata/glance-api.conf.Debian
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 {% set storage_engines = server.storage.engine.split(',') %}
 [DEFAULT]
 
@@ -1745,14 +1745,16 @@
 # A URL representing the messaging driver to use and its full configuration.
 # (string value)
 #transport_url = <None>
+
+{%- set rabbit_port = server.message_queue.get('port', 5671 if server.message_queue.get('ssl',{}).get('enabled', False)  else 5672) %}
 {%- if server.message_queue.members is defined %}
 transport_url = rabbit://{% for member in server.message_queue.members -%}
-                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', 5672) }}
+                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', rabbit_port) }}
                              {%- if not loop.last -%},{%- endif -%}
                          {%- endfor -%}
                              /{{ server.message_queue.virtual_host }}
 {%- else %}
-transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ server.message_queue.port }}/{{ server.message_queue.virtual_host }}
+transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ rabbit_port }}/{{ server.message_queue.virtual_host }}
 {%- endif %}
 
 # DEPRECATED: The messaging driver to use, defaults to rabbit. Other drivers
@@ -1761,7 +1763,6 @@
 # Its value may be silently ignored in the future.
 # Reason: Replaced by [DEFAULT]/transport_url
 #rpc_backend = rabbit
-rpc_backend = rabbit
 
 # The default exchange under which topics are scoped. May be overridden by an
 # exchange name specified in the transport_url option. (string value)
@@ -3804,6 +3805,22 @@
 # From oslo.messaging
 #
 
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbit_use_ssl=true
+
+{%- if server.message_queue.ssl.version is defined %}
+kombu_ssl_version = {{ server.message_queue.ssl.version }}
+{%- elif salt['grains.get']('pythonversion') > [2,7,8] %}
+kombu_ssl_version = TLSv1_2
+{%- endif %}
+
+{%- if server.message_queue.ssl.cacert_file is defined %}
+kombu_ssl_ca_certs = {{ server.message_queue.ssl.cacert_file }}
+{%- else %}
+kombu_ssl_ca_certs={{ system_cacerts_file }}
+{%- endif %}
+{%- endif %}
+
 # Use durable queues in AMQP. (boolean value)
 # Deprecated group/name - [DEFAULT]/amqp_durable_queues
 # Deprecated group/name - [DEFAULT]/rabbit_durable_queues
diff --git a/glance/files/ocata/glance-registry.conf.Debian b/glance/files/ocata/glance-registry.conf.Debian
index f736108..a6a6044 100644
--- a/glance/files/ocata/glance-registry.conf.Debian
+++ b/glance/files/ocata/glance-registry.conf.Debian
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 [DEFAULT]
 
 #
@@ -1004,22 +1004,24 @@
 # A URL representing the messaging driver to use and its full configuration.
 # (string value)
 #transport_url = <None>
+
+{%- set rabbit_port = server.message_queue.get('port', 5671 if server.message_queue.get('ssl',{}).get('enabled', False)  else 5672) %}
 {%- if server.message_queue.members is defined %}
 transport_url = rabbit://{% for member in server.message_queue.members -%}
-                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', 5672) }}
+                             {{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ member.host }}:{{ member.get('port', rabbit_port) }}
                              {%- if not loop.last -%},{%- endif -%}
                          {%- endfor -%}
-                             {{ server.message_queue.virtual_host }}
+                             /{{ server.message_queue.virtual_host }}
 {%- else %}
-transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ server.message_queue.port }}/{{ server.message_queue.virtual_host }}
+transport_url = rabbit://{{ server.message_queue.user }}:{{ server.message_queue.password }}@{{ server.message_queue.host }}:{{ rabbit_port }}/{{ server.message_queue.virtual_host }}
 {%- endif %}
+
 # DEPRECATED: The messaging driver to use, defaults to rabbit. Other drivers
 # include amqp and zmq. (string value)
 # This option is deprecated for removal.
 # Its value may be silently ignored in the future.
 # Reason: Replaced by [DEFAULT]/transport_url
 #rpc_backend = rabbit
-rpc_backend = rabbit
 
 # The default exchange under which topics are scoped. May be overridden by an
 # exchange name specified in the transport_url option. (string value)
@@ -1566,6 +1568,22 @@
 # From oslo.messaging
 #
 
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbit_use_ssl=true
+
+{%- if server.message_queue.ssl.version is defined %}
+kombu_ssl_version = {{ server.message_queue.ssl.version }}
+{%- elif salt['grains.get']('pythonversion') > [2,7,8] %}
+kombu_ssl_version = TLSv1_2
+{%- endif %}
+
+{%- if server.message_queue.ssl.cacert_file is defined %}
+kombu_ssl_ca_certs = {{ server.message_queue.ssl.cacert_file }}
+{%- else %}
+kombu_ssl_ca_certs={{ system_cacerts_file }}
+{%- endif %}
+{%- endif %}
+
 # Use durable queues in AMQP. (boolean value)
 # Deprecated group/name - [DEFAULT]/amqp_durable_queues
 # Deprecated group/name - [DEFAULT]/rabbit_durable_queues
diff --git a/glance/map.jinja b/glance/map.jinja
index 0125457..5563fe4 100644
--- a/glance/map.jinja
+++ b/glance/map.jinja
@@ -1,3 +1,7 @@
+{%- set system_cacerts_file = salt['grains.filter_by']({
+    'Debian': '/etc/ssl/certs/ca-certificates.crt',
+    'RedHat': '/etc/pki/tls/certs/ca-bundle.crt'
+})%}
 
 {% set server = salt['grains.filter_by']({
     'Debian': {
diff --git a/glance/server.sls b/glance/server.sls
index 20f23d9..dab4dc8 100644
--- a/glance/server.sls
+++ b/glance/server.sls
@@ -1,4 +1,4 @@
-{%- from "glance/map.jinja" import server with context %}
+{%- from "glance/map.jinja" import server, system_cacerts_file with context %}
 {%- if server.enabled %}
 
 glance_packages:
@@ -97,6 +97,9 @@
     - cmd: glance_load_metadatafs
   - watch:
     - file: /etc/glance/glance-glare.conf
+    {%- if server.message_queue.get('ssl',{}).get('enabled',False) %}
+    - file: rabbitmq_ca
+    {% endif %}
 
 {%- endif %}
 {%- endif %}
@@ -122,6 +125,9 @@
     - file: /etc/glance/glance-api.conf
     - file: /etc/glance/glance-registry.conf
     - file: /etc/glance/glance-api-paste.ini
+    {%- if server.message_queue.get('ssl',{}).get('enabled',False) %}
+    - file: rabbitmq_ca
+    {% endif %}
 
 glance_install_database:
   cmd.run:
@@ -258,4 +264,18 @@
 
 {%- endfor %}
 
+{%- if server.message_queue.get('ssl',{}).get('enabled', False) %}
+rabbitmq_ca:
+{%- if server.message_queue.ssl.cacert is defined %}
+  file.managed:
+    - name: {{ server.message_queue.ssl.cacert_file }}
+    - contents_pillar: glance:server:message_queue:ssl:cacert
+    - mode: 0444
+    - makedirs: true
+{%- else %}
+  file.exists:
+   - name: {{ server.message_queue.ssl.get('cacert_file', system_cacerts_file) }}
+{%- endif %}
+{%- endif %}
+
 {%- endif %}
diff --git a/tests/pillar/ssl.sls b/tests/pillar/ssl.sls
new file mode 100644
index 0000000..c0bb4af
--- /dev/null
+++ b/tests/pillar/ssl.sls
@@ -0,0 +1,9 @@
+include:
+  - .cluster
+
+glance:
+  server:
+    message_queue:
+      port: 5671
+      ssl:
+        enabled: True