Merge "Don't create admin user if there is LDAP backend set to read_only"
diff --git a/README.rst b/README.rst
index 14403eb..89593bd 100644
--- a/README.rst
+++ b/README.rst
@@ -305,6 +305,56 @@
           virtual_host: '/openstack'
         ....
 
+Client-side RabbitMQ TLS configuration:
+
+|
+
+By default system-wide CA certs are used. Nothing should be specified except `ssl.enabled`.
+
+.. code-block:: yaml
+
+  keystone:
+    server:
+      ....
+      message_queue:
+        ssl:
+          enabled: True
+
+Use `cacert_file` option to specify the CA-cert file path explicitly:
+
+.. code-block:: yaml
+
+  keystone:
+    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
+
+  keystone:
+    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 CADF audit notification
 
 .. code-block:: yaml
@@ -614,6 +664,18 @@
         policy:
           admin_or_token_subject: 'rule:admin_required or rule:token_subject'
 
+Setting up default admin project name and domain
+
+.. code-block:: yaml
+
+
+    keystone:
+      server:
+        ....
+        admin_project:
+          name: "admin"
+          domain: "default"
+
 Usage
 =====
 
diff --git a/_modules/keystone_policy.py b/_modules/keystone_policy.py
index 4e3ae6d..2e79f22 100644
--- a/_modules/keystone_policy.py
+++ b/_modules/keystone_policy.py
@@ -2,10 +2,56 @@
 import json
 import logging
 
-import yaml
-
 LOG = logging.getLogger(__name__)
 
+import yaml
+import yaml.constructor
+
+try:
+    # included in standard lib from Python 2.7
+    from collections import OrderedDict
+except ImportError:
+    # try importing the backported drop-in replacement
+    # it's available on PyPI
+    from ordereddict import OrderedDict
+
+# https://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts
+class OrderedDictYAMLLoader(yaml.Loader):
+    """
+    A YAML loader that loads mappings into ordered dictionaries.
+    """
+
+    def __init__(self, *args, **kwargs):
+        yaml.Loader.__init__(self, *args, **kwargs)
+
+        self.add_constructor(u'tag:yaml.org,2002:map', type(self).construct_yaml_map)
+        self.add_constructor(u'tag:yaml.org,2002:omap', type(self).construct_yaml_map)
+
+    def construct_yaml_map(self, node):
+        data = OrderedDict()
+        yield data
+        value = self.construct_mapping(node)
+        data.update(value)
+
+    def construct_mapping(self, node, deep=False):
+        if isinstance(node, yaml.MappingNode):
+            self.flatten_mapping(node)
+        else:
+            raise yaml.constructor.ConstructorError(None, None,
+                'expected a mapping node, but found %s' % node.id, node.start_mark)
+
+        mapping = OrderedDict()
+        for key_node, value_node in node.value:
+            key = self.construct_object(key_node, deep=deep)
+            try:
+                hash(key)
+            except TypeError, exc:
+                raise yaml.constructor.ConstructorError('while constructing a mapping',
+                    node.start_mark, 'found unacceptable key (%s)' % exc, key_node.start_mark)
+            value = self.construct_object(value_node, deep=deep)
+            mapping[key] = value
+        return mapping
+
 
 def __virtual__():
     return True
@@ -14,8 +60,7 @@
 def rule_list(path, **kwargs):
     try:
         with io.open(path, 'r') as file_handle:
-            rules = yaml.safe_load(file_handle) or {}
-        rules = {str(k): str(v) for (k, v) in rules.items()}
+            rules = yaml.load(file_handle, OrderedDictYAMLLoader) or OrderedDict()
     except Exception as e:
         msg = "Unable to load policy file %s: %s" % (path, repr(e))
         LOG.debug(msg)
diff --git a/keystone/files/grafana_dashboards/keystone_prometheus.json b/keystone/files/grafana_dashboards/keystone_prometheus.json
index 1d0e495..6d64799 100755
--- a/keystone/files/grafana_dashboards/keystone_prometheus.json
+++ b/keystone/files/grafana_dashboards/keystone_prometheus.json
@@ -21,8 +21,8 @@
           "colorValue": true,
           "colors": [
             "rgba(245, 54, 54, 0.9)",
-            "rgba(237, 129, 40, 0.89)",
-            "rgba(50, 172, 45, 0.97)"
+            "rgba(50, 172, 45, 0.97)",
+            "rgba(237, 129, 40, 0.89)"
           ],
           "datasource": null,
           "format": "none",
@@ -79,7 +79,7 @@
               "step": 60
             }
           ],
-          "thresholds": "1,0",
+          "thresholds": "0.5,1.5",
           "title": "API Availability",
           "type": "singlestat",
           "valueFontSize": "80%",
@@ -91,13 +91,18 @@
             },
             {
               "op": "=",
+              "text": "DOWN",
+              "value": "0"
+            },
+            {
+              "op": "=",
               "text": "OK",
               "value": "1"
             },
             {
               "op": "=",
-              "text": "DOWN",
-              "value": "0"
+              "text": "UNKNOWN",
+              "value": "2"
             }
           ],
           "valueName": "current"
diff --git a/keystone/files/keystonercv3 b/keystone/files/keystonercv3
index 9da173c..bf2b3ad 100644
--- a/keystone/files/keystonercv3
+++ b/keystone/files/keystonercv3
@@ -9,4 +9,5 @@
 export OS_PASSWORD={{ server.admin_password }}
 export OS_REGION_NAME={{ server.region }}
 export OS_INTERFACE=internal
+export OS_ENDPOINT_TYPE="internal"
 export OS_CACERT="{{ server.cacert }}"
diff --git a/keystone/files/mitaka/keystone.conf.Debian b/keystone/files/mitaka/keystone.conf.Debian
index a526cee..18d6f2b 100644
--- a/keystone/files/mitaka/keystone.conf.Debian
+++ b/keystone/files/mitaka/keystone.conf.Debian
@@ -1,4 +1,4 @@
-{% from "keystone/map.jinja" import server with context %}
+{% from "keystone/map.jinja" import server, system_cacerts_file with context %}
 [DEFAULT]
 
 #
@@ -601,7 +601,7 @@
 # Deprecated group/name - [DATABASE]/sql_connection
 # Deprecated group/name - [sql]/connection
 #connection = <None>
-connection={{ server.database.engine }}://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}
+connection={{ server.database.engine }}://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}{%- if server.database.get('ssl',{}).get('enabled',False) %}?ssl_ca={{ server.database.ssl.get('cacert_file', system_cacerts_file) }}{% endif %}
 
 # The SQLAlchemy connection string to use to connect to the slave database.
 # (string value)
@@ -1577,14 +1577,31 @@
 # 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 %}
+
+{%- 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 %}
 
 # RabbitMQ HA cluster host:port pairs. (list value)
diff --git a/keystone/files/newton/keystone.conf.Debian b/keystone/files/newton/keystone.conf.Debian
index db95287..83f4b13 100644
--- a/keystone/files/newton/keystone.conf.Debian
+++ b/keystone/files/newton/keystone.conf.Debian
@@ -1,4 +1,4 @@
-{% from "keystone/map.jinja" import server with context %}
+{% from "keystone/map.jinja" import server, system_cacerts_file with context %}
 [DEFAULT]
 
 #
@@ -358,14 +358,16 @@
 # A URL representing the messaging driver to use and its full configuration.
 # (string value)
 #transport_url = rabbit://nova:3qVSI7a1m8AdaDQ7BpB0PJu4@192.168.0.4:5673/
+
+{%- 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
@@ -374,7 +376,6 @@
 # Its value may be silently ignored in the future.
 # Reason: Replaced by [DEFAULT]/transport_url
 #rpc_backend = rabbit
-rpc_backend = rabbit
 {%- endif %}
 
 # The default exchange under which topics are scoped. May be overridden by an
@@ -703,7 +704,7 @@
 # Deprecated group/name - [DATABASE]/sql_connection
 # Deprecated group/name - [sql]/connection
 #connection = <None>
-connection={{ server.database.engine }}+pymysql://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}
+connection={{ server.database.engine }}+pymysql://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}{%- if server.database.get('ssl',{}).get('enabled',False) %}?ssl_ca={{ server.database.ssl.get('cacert_file', system_cacerts_file) }}{% endif %}
 
 # The SQLAlchemy connection string to use to connect to the slave database.
 # (string value)
@@ -1865,6 +1866,26 @@
 # From oslo.messaging
 #
 
+{%- if server.notification %}
+
+{%- 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 %}
+
+{%- 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/keystone/files/ocata/keystone.conf.Debian b/keystone/files/ocata/keystone.conf.Debian
index 375935c..c15982f 100644
--- a/keystone/files/ocata/keystone.conf.Debian
+++ b/keystone/files/ocata/keystone.conf.Debian
@@ -1,4 +1,4 @@
-{% from "keystone/map.jinja" import server with context %}
+{% from "keystone/map.jinja" import server, system_cacerts_file with context %}
 [DEFAULT]
 
 #
@@ -425,14 +425,15 @@
 # A URL representing the messaging driver to use and its full configuration.
 # (string value)
 #transport_url = rabbit://nova:3qVSI7a1m8AdaDQ7BpB0PJu4@192.168.0.4:5673/
+{%- 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
@@ -441,7 +442,6 @@
 # Its value may be silently ignored in the future.
 # Reason: Replaced by [DEFAULT]/transport_url
 #rpc_backend = rabbit
-rpc_backend = rabbit
 {%- endif %}
 # The default exchange under which topics are scoped. May be overridden by an
 # exchange name specified in the transport_url option. (string value)
@@ -776,7 +776,7 @@
 # Deprecated group/name - [DATABASE]/sql_connection
 # Deprecated group/name - [sql]/connection
 #connection = <None>
-connection={{ server.database.engine }}+pymysql://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}
+connection={{ server.database.engine }}+pymysql://{{ server.database.user }}:{{ server.database.password }}@{{ server.database.host }}/{{ server.database.name }}{%- if server.database.get('ssl',{}).get('enabled',False) %}?ssl_ca={{ server.database.ssl.get('cacert_file', system_cacerts_file) }}{% endif %}
 
 # The SQLAlchemy connection string to use to connect to the slave database.
 # (string value)
@@ -1962,6 +1962,27 @@
 # From oslo.messaging
 #
 
+
+{%- if server.notification %}
+
+{%- 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 %}
+
+{%- endif %}
+
 # Use durable queues in AMQP. (boolean value)
 # Deprecated group/name - [DEFAULT]/amqp_durable_queues
 # Deprecated group/name - [DEFAULT]/rabbit_durable_queues
@@ -2591,6 +2612,9 @@
 # Name of the domain that owns the `admin_project_name`. If left unset, then
 # there is no admin project. `[resource] admin_project_name` must also be set
 # to use this option. (string value)
+{%- if server.admin_project is defined %}
+admin_project_domain_name = {{ server.admin_project.domain }}
+{%- endif %}
 #admin_project_domain_name = <None>
 
 # This is a special project which represents cloud-level administrator
@@ -2601,6 +2625,9 @@
 # means of cross-project role assignments. `[resource]
 # admin_project_domain_name` must also be set to use this option. (string
 # value)
+{%- if server.admin_project is defined %}
+admin_project_name = {{ server.admin_project.name }}
+{%- endif %}
 #admin_project_name = <None>
 
 # This controls whether the names of projects are restricted from containing
diff --git a/keystone/files/ocata/keystone.conf.RedHat b/keystone/files/ocata/keystone.conf.RedHat
index 663854e..7f4d9f1 100644
--- a/keystone/files/ocata/keystone.conf.RedHat
+++ b/keystone/files/ocata/keystone.conf.RedHat
@@ -2560,6 +2560,9 @@
 # Name of the domain that owns the `admin_project_name`. If left unset, then
 # there is no admin project. `[resource] admin_project_name` must also be set
 # to use this option. (string value)
+{%- if server.admin_project is defined %}
+admin_project_domain_name = {{ server.admin_project.domain }}
+{%- endif %}
 #admin_project_domain_name = <None>
 
 # This is a special project which represents cloud-level administrator
@@ -2570,6 +2573,9 @@
 # means of cross-project role assignments. `[resource]
 # admin_project_domain_name` must also be set to use this option. (string
 # value)
+{%- if server.admin_project is defined %}
+admin_project_name = {{ server.admin_project.name }}
+{%- endif %}
 #admin_project_name = <None>
 
 # This controls whether the names of projects are restricted from containing
@@ -3058,4 +3064,4 @@
 # unless you are providing a custom entry point. (string value)
 #driver = sql
 [extra_headers]
-Distribution = Ubuntu
\ No newline at end of file
+Distribution = Ubuntu
diff --git a/keystone/map.jinja b/keystone/map.jinja
index 1a40274..01dc3f4 100644
--- a/keystone/map.jinja
+++ b/keystone/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': {
@@ -58,6 +62,7 @@
 {% set monitoring = salt['grains.filter_by']({
     'default': {
         'error_log_rate': 0.2,
+        'http_response_time_p90': 0.3,
         'failed_auths': {
             'percentage': 50,
             'all_auths_rate': 0.1,
diff --git a/keystone/meta/grafana.yml b/keystone/meta/grafana.yml
index 75c3ee7..cf147a0 100644
--- a/keystone/meta/grafana.yml
+++ b/keystone/meta/grafana.yml
@@ -7,7 +7,7 @@
     datasource: influxdb
     format: json
     template: keystone/files/grafana_dashboards/keystone_influxdb.json
-  main:
+  main_influxdb:
     datasource: influxdb
     row:
       ost-control-plane:
@@ -23,6 +23,21 @@
               cluster_status:
                 rawQuery: true
                 query: SELECT last(value) FROM cluster_status WHERE cluster_name = 'keystone' AND environment_label = '$environment' AND $timeFilter GROUP BY time($interval) fill(null)
+  main_prometheus:
+    datasource: prometheus
+    row:
+      ost-control-plane:
+        title: OpenStack Control Plane
+        panel:
+          keystone:
+            title: Keystone
+            links:
+            - dashboard: Keystone
+              title: Keystone
+              type: dashboard
+            target:
+              cluster_status:
+                expr: avg(openstack_api_check_status{service=~\"keystone.*public.*\"})
   service_level:
     datasource: influxdb
     row:
diff --git a/keystone/meta/prometheus.yml b/keystone/meta/prometheus.yml
index 572908f..fc3568f 100644
--- a/keystone/meta/prometheus.yml
+++ b/keystone/meta/prometheus.yml
@@ -5,15 +5,26 @@
   alert:
     KeystoneAPIDown:
       if: >-
-        max(openstack_api_check_status{service=~"keystone.+"}) by (service) == 0
+        openstack_api_check_status{service=~"keystone.*"} == 0
       for: 2m
       labels:
         severity: down
         service: "{{ $labels.service }}"
       annotations:
-        summary: "Endpoint check for '{{ $labels.service}}' is down"
+        summary: "Endpoint check for '{{ $labels.service }}' is down"
         description: >-
-            Endpoint check for '{{ $labels.service}}' is down for 2 minutes
+            Endpoint check for '{{ $labels.service }}' is down for 2 minutes
+    KeystoneAPIServiceDown:
+      if: >-
+        http_response_status{service=~"keystone.*"} == 0
+      for: 2m
+      labels:
+        severity: down
+        service: "{{ $labels.service }}"
+      annotations:
+        summary: "HTTP check for '{{ $labels.service }}' down"
+        description: >-
+            The HTTP check for '{{ $labels.service }}' is down on {{ $labels.host }} for 2 minutes.
     KeystoneErrorLogsTooHigh:
 {%- endraw %}
       {%- set log_threshold = monitoring.error_log_rate|float %}
@@ -38,5 +49,17 @@
       annotations:
         summary: 'Too many failed authentications in Keystone'
         description: 'The rate of failed authentications in Keystone over the last 5 minutes is too high (current value={{ $value }}, threshold={%- endraw %}{{ auth_threshold }}).'
+    KeystoneAPITooSlow:
+      {%- set response_time_threshold = monitoring.http_response_time_p90|float %}
+      if: >-
+        max by(host) (openstack_keystone_http_response_times_upper_90{http_method=~"^(GET|POST)$",http_status="2xx"}) >= {{ response_time_threshold }}
+{%- raw %}
+      for: 2m
+      labels:
+        severity: warning
+        service: keystone
+      annotations:
+        summary: 'Keystone API too slow'
+        description: 'The 90th percentile of the Keystone API response times for GET and POST requests is too high on node {{ $labels.host }} (current value={{ $value }}s, threshold={%- endraw %}{{ response_time_threshold }}s).'
 
 {%- endif %}
diff --git a/keystone/server.sls b/keystone/server.sls
index 1548796..60f162d 100644
--- a/keystone/server.sls
+++ b/keystone/server.sls
@@ -1,4 +1,4 @@
-{%- from "keystone/map.jinja" import server with context %}
+{%- from "keystone/map.jinja" import server, system_cacerts_file with context %}
 {%- if server.enabled %}
 
 keystone_packages:
@@ -227,6 +227,9 @@
   - onlyif: /bin/false
   {%- endif %}
   - watch:
+    {%- if server.notification and server.message_queue.get('ssl',{}).get('enabled', False) %}
+    - file: rabbitmq_ca
+    {%- endif %}
     - file: /etc/keystone/keystone.conf
 {%- endif %}
 
@@ -256,7 +259,7 @@
 {%- if not grains.get('noservices', False) %}
 keystone_syncdb:
   cmd.run:
-  - name: keystone-manage db_sync; sleep 1
+  - name: keystone-manage db_sync && sleep 1
   - timeout: 120
   - require:
     - service: {{ keystone_service }}
@@ -434,4 +437,37 @@
 {%- endfor %}
 {%- endif %} {# end noservices #}
 
+{%- if server.database.get('ssl',{}).get('enabled',False)  %}
+mysql_ca:
+{%- if server.database.ssl.cacert is defined %}
+  file.managed:
+    - name: {{ server.database.ssl.cacert_file }}
+    - contents_pillar: keystone:server:database:ssl:cacert
+    - mode: 0444
+    - makedirs: true
+    - require_in:
+      - file: /etc/keystone/keystone.conf
+{%- else %}
+  file.exists:
+   - name: {{ server.database.ssl.get('cacert_file', system_cacerts_file) }}
+   - require_in:
+     - file: /etc/keystone/keystone.conf
+{% endif %}
+{% endif %}
+
+
+{%- if server.notification and 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: keystone: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..f60e5ed
--- /dev/null
+++ b/tests/pillar/ssl.sls
@@ -0,0 +1,53 @@
+# Test case with enabled SSL of the following communication paths:
+# - messaging (rabbitmq)
+
+keystone:
+  server:
+    enabled: true
+    version: liberty
+    service_token: token
+    service_tenant: service
+    admin_tenant: admin
+    admin_name: admin
+    admin_password: passw0rd
+    admin_email: root@localhost
+    bind:
+      address: 0.0.0.0
+      private_address: 127.0.0.1
+      private_port: 35357
+      public_address: 127.0.0.1
+      public_port: 5000
+    region: RegionOne
+    database:
+      engine: mysql
+      host: 127.0.0.1
+      name: keystone
+      password: passw0rd
+      user: keystone
+      ssl:
+        enabled: True
+    tokens:
+      engine: cache
+      expiration: 86400
+      location: /etc/keystone/fernet-keys/
+    notification: true
+    notification_format: cadf
+    message_queue:
+      engine: rabbitmq
+      host: 127.0.0.1
+      port: 5671
+      user: openstack
+      password: passw0rd
+      virtual_host: '/openstack'
+      ha_queues: true
+      ssl:
+        enabled: True
+    cache:
+      engine: memcached
+      members:
+      - host: 127.0.0.1
+        port: 11211
+      - host: 127.0.0.1
+        port: 11211
+      - host: 127.0.0.1
+        port: 11211