Keystone policy module, states and grain
diff --git a/README.rst b/README.rst
index 257037d..6f9326e 100644
--- a/README.rst
+++ b/README.rst
@@ -86,6 +86,17 @@
admin_address: 10.0.0.20
admin_port: 8774
+Keystone with custom policies. Keys with specified rules are created or set to this value if they already exists. Keys with no value (like our "existing_rule") are deleted from the policy file.
+
+.. code-block:: yaml
+
+ keystone:
+ server:
+ enabled: true
+ policy:
+ new_rule: "rule:admin_required"
+ existing_rule:
+
Keystone memcached storage for tokens
.. code-block:: yaml
diff --git a/_grains/keystone_policy.py b/_grains/keystone_policy.py
new file mode 100644
index 0000000..1812c03
--- /dev/null
+++ b/_grains/keystone_policy.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+import salt.config
+import salt.loader
+
+
+def main():
+ path = "/etc/keystone/policy.json"
+ __opts__ = salt.config.minion_config('/etc/salt/minion')
+ keystone_policy_mod = salt.loader.raw_mod(__opts__, 'keystone_policy', None)
+ result = keystone_policy_mod['keystone_policy.rule_list'](path)
+ if result and 'Error' not in result:
+ return {'keystone_policy': result}
+ return {}
+
diff --git a/_modules/keystone_policy.py b/_modules/keystone_policy.py
new file mode 100644
index 0000000..05b9215
--- /dev/null
+++ b/_modules/keystone_policy.py
@@ -0,0 +1,68 @@
+import io
+import json
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+def __virtual__():
+ return True
+
+
+def rule_list(path, **kwargs):
+ try:
+ with io.open(path, 'r') as file_handle:
+ rules = json.loads(file_handle.read())
+ rules = {str(k): str(v) for (k, v) in rules.items()}
+ except Exception as e:
+ msg = "Unable to load policy JSON %s: %s" % (path, repr(e))
+ LOG.debug(msg)
+ rules = {'Error': msg}
+ return rules
+
+
+def rule_delete(name, path, **kwargs):
+ ret = {}
+ rules = __salt__['keystone_policy.rule_list'](path, **kwargs)
+ if 'Error' not in rules:
+ if name not in rules:
+ return ret
+ del rules[name]
+ try:
+ with io.open(path, 'w') as file_handle:
+ file_handle.write(unicode(json.dumps(rules, indent=4)))
+ except Exception as e:
+ msg = "Unable to save policy json: %s" % repr(e)
+ LOG.error(msg)
+ return {'Error': msg}
+ ret = 'Rule {0} deleted'.format(name)
+ return ret
+
+
+def rule_set(name, rule, path, **kwargs):
+ rules = __salt__['keystone_policy.rule_list'](path, **kwargs)
+ if 'Error' not in rules:
+ if name in rules and rules[name] == rule:
+ return {name: 'Rule %s already exists and is in correct state' % name}
+ rules.update({name: rule})
+ try:
+ with io.open(path, 'w') as file_handle:
+ file_handle.write(unicode(json.dumps(rules, indent=4)))
+ except Exception as e:
+ msg = "Unable to save policy JSON %s: %s" % (path, repr(e))
+ LOG.error(msg)
+ return {'Error': msg}
+ return rule_get(name, path, **kwargs)
+ return rules
+
+
+def rule_get(name, path, **kwargs):
+ ret = {}
+ rules = __salt__['keystone_policy.rule_list'](path, **kwargs)
+ if 'Error' in rules:
+ ret['Error'] = rules['Error']
+ elif name in rules:
+ ret[name] = rules.get(name)
+
+ return ret
+
diff --git a/_states/keystone_policy.py b/_states/keystone_policy.py
index 2d34e06..e7a4a6a 100644
--- a/_states/keystone_policy.py
+++ b/_states/keystone_policy.py
@@ -8,46 +8,74 @@
.. code-block:: yaml
-/etc/keystone/policy.json:
- keystone_policy.present:
- - override_data:
- override_key: override_value
- - formatter: json
+ my_rule_present:
+ keystone_policy.rule_present:
+ - name: rule_name
+ - rule: rule
+ - path: /etc/keystone/policy.json
+
+ my_rule_absent:
+ keystone_policy.rule_absent:
+ - name: rule_name
+ - path: /etc/keystone/policy.json
'''
import logging
-import json
log = logging.getLogger(__name__)
-JSON_LOCATION = '/etc/keystone/policy.json'
+
+def __virtual__():
+ return True
-def _deep_merge(dct, merge_dct):
- for k, v in merge_dct.iteritems():
- if (k in dct and isinstance(dct[k], dict)):
- _deep_merge(dct[k], merge_dct[k])
- else:
- dct[k] = merge_dct[k]
-
-
-def present(name, override_data={}, **kwargs):
+def rule_present(name, rule, path, **kwargs):
'''
- Ensures that given key present in policy.json file. This is a wrapper
- around file.serialize state with additional argument: override_data.
- Rest parameters of file.serialize can be safely used as well.
- Function reads contents of existing policy.json file into a python
- dictionary. User defined data populated to this dictionary using deep
- merge procedure.
-
- :param name: Name of the resource
- :param override_data: User defined data with overrides
+ Ensures that the policy rule exists
+
+ :param name: Rule name
+ :param rule: Rule
+ :param path: Path to policy file
'''
- with open(JSON_LOCATION) as policy_json:
- json_content = json.load(policy_json)
-
- _deep_merge(json_content, override_data)
-
- kwargs['dataset'] = json_content
- ret = __states__['file.serialize']('/etc/keystone/policy.json', **kwargs)
+ rule = rule or ""
+ ret = {'name': name,
+ 'changes': {},
+ 'result': True,
+ 'comment': 'Rule "{0}" already exists and is in correct state'.format(name)}
+ rule_check = __salt__['keystone_policy.rule_get'](name, path, **kwargs)
+ if not rule_check:
+ __salt__['keystone_policy.rule_set'](name, rule, path, **kwargs)
+ ret['comment'] = 'Rule {0} has been created'.format(name)
+ ret['changes']['Rule'] = 'Rule %s: "%s" has been created' % (name, rule)
+ elif 'Error' in rule_check:
+ ret['comment'] = rule_check.get('Error')
+ ret['result'] = False
+ elif rule_check[name] != rule:
+ __salt__['keystone_policy.rule_set'](name, rule, path, **kwargs)
+ ret['comment'] = 'Rule %s has been changed' % (name,)
+ ret['changes']['Old Rule'] = '%s: "%s"' % (name, rule_check[name])
+ ret['changes']['New Rule'] = '%s: "%s"' % (name, rule)
return ret
+
+
+def rule_absent(name, path, **kwargs):
+ '''
+ Ensures that the policy rule does not exist
+
+ :param name: Rule name
+ :param path: Path to policy file
+ '''
+ ret = {'name': name,
+ 'changes': {},
+ 'result': True,
+ 'comment': 'Rule "{0}" is already absent'.format(name)}
+ rule_check = __salt__['keystone_policy.rule_get'](name, path, **kwargs)
+ if rule_check:
+ __salt__['keystone_policy.rule_delete'](name, path, **kwargs)
+ ret['comment'] = 'Rule {0} has been deleted'.format(name)
+ ret['changes']['Rule'] = 'Rule %s: "%s" has been deleted' % (name, rule_check[name])
+ elif 'Error' in rule_check:
+ ret['comment'] = rule_check.get('Error')
+ ret['result'] = False
+ return ret
+
diff --git a/keystone/meta/salt.yml b/keystone/meta/salt.yml
index c5f5e3a..d23f40b 100644
--- a/keystone/meta/salt.yml
+++ b/keystone/meta/salt.yml
@@ -6,4 +6,9 @@
priority: 510
control:
priority: 520
+grain:
+ keystone:
+ keystone_policy:
+ {%- set policy_grains = salt['keystone_policy.rule_list'](path="/etc/keystone/policy.json") %}
+ {{ policy_grains|yaml(False)|indent(6) }}
diff --git a/keystone/server.sls b/keystone/server.sls
index 384911f..08251cd 100644
--- a/keystone/server.sls
+++ b/keystone/server.sls
@@ -97,11 +97,15 @@
- service: keystone_service
{%- endif %}
-/etc/keystone/policy.json:
- keystone_policy.present:
- - override_data:
- {{ server.get('policy', {})|yaml }}
- - formatter: json
+{%- for name, rule in server.get('policy', {}).iteritems() %}
+
+{%- if rule != None %}
+
+rule_{{ name }}_present:
+ keystone_policy.rule_present:
+ - path: /etc/keystone/policy.json
+ - name: {{ name }}
+ - rule: {{ rule }}
- require:
- pkg: keystone_packages
{%- if not grains.get('noservices', False) %}
@@ -109,6 +113,23 @@
- service: keystone_service
{%- endif %}
+{%- else %}
+
+rule_{{ name }}_absent:
+ keystone_policy.rule_absent:
+ - path: /etc/keystone/policy.json
+ - name: {{ name }}
+ - require:
+ - pkg: keystone_packages
+ {%- if not grains.get('noservices', False) %}
+ - watch_in:
+ - service: keystone_service
+ {%- endif %}
+
+{%- endif %}
+
+{%- endfor %}
+
{%- if server.get("domain", {}) %}
/etc/keystone/domains: