Merge "Set default TTL for memcache item expiration" into release/2019.2.0
diff --git a/README.rst b/README.rst
index 5a0fedf..6ce524f 100644
--- a/README.rst
+++ b/README.rst
@@ -869,6 +869,111 @@
You can read more about it here:
https://docs.openstack.org/security-guide/databases/database-access-control.html
+Enable security compliance policies.
+-----------------------------------
+By default security compliance policies disabled. You are able to define follow params independency each other.
+
+Notice: To ignore `change_password_upon_first_use` requirement for specific users, such as service users,
+set the `options` attribute `ignore_change_password_upon_first_use`
+to `True` for the desired user via the update user API
+
+Notice: Symbol "$" should have escape character and looks like "$$".
+
+.. code-block:: yaml
+
+keystone:
+ server:
+ security_compliance:
+ disable_user_account_days_inactive: 90
+ lockout_failure_attempts: 5
+ lockout_duration: 600
+ password_expires_days: 90
+ unique_last_password_count: 10
+ minimum_password_age: 0
+ password_regex: '^(?=.*\d)(?=.*[a-zA-Z]).{7,}$$'
+ password_regex_description: 'Your password must contains at least 1 letter, 1 digit, and have a minimum length of 7 characters'
+ change_password_upon_first_use: true
+
+
+Define extra user options.
+-------------------------
+
+ To ignore `change_password_upon_first_use` requirement for specific users,
+such as service users, set the `options` attribute `ignore_change_password_upon_first_use`
+to `True` for the desired user via the update user API.
+
+ To ignore `password_expires_days` requirement for specific users,
+such as service users, set the `options` attribute `ignore_password_expiry`
+to `True` for the desired user via the update user API.
+
+ To ignore `lockout_failure_attempts` requirement for specific users,
+such as service users, set the `options` attribute `ignore_lockout_failure_attempts`
+to `True` for the desired user via the update user API.
+
+ Also If there exists a user who should not be able to change her own password via
+the keystone password change API, keystone supports setting that user’s option `lock_password`
+to True via the user update API.
+
+#For release since Q
+.. code-block:: yaml
+
+keystone:
+ client:
+ resources:
+ v3:
+ users:
+ cinder:
+ options:
+ ignore_change_password_upon_first_use: True
+ ignore_password_expiry: False
+ ignore_lockout_failure_attempts: False
+ lock_password: True
+.. code-block::
+
+#For all early releases
+.. code-block:: yaml
+
+keystone:
+ client:
+ server:
+ identity:
+ project:
+ service:
+ user:
+ cinder:
+ options:
+ ignore_change_password_upon_first_use: True
+ ignore_password_expiry: False
+ ignore_lockout_failure_attempts: False
+ lock_password: True
+.. code-block::
+
+Enhanced max_active_keys setup
+------------------------------
+
+Rotating keys too frequently, or with ``[fernet_tokens] max_active_keys`` set too low,
+will cause tokens to become invalid prior to their expiration. As tokens may be fetched
+beyond their initial expiration period, keys should not be fully rotated within the
+period of ``[token] expiration`` + ``[token] allow_expired_window`` seconds to prevent the
+tokens becoming unavailable. As an example, the max_active_keys default value can be
+adjusted according to the following specified values:
+``[token] allow_expired_window`` = 86400 (24 hours)
+``[token] expiration`` = 3600 (1 hour)
+rotation_frequency = 1 (1 hour)
+``[fernet_token]max_active_keys`` = (24 + 1)/1 + 2 = 27
+
+.. code-block:: yaml
+
+ keystone:
+ server:
+ ...
+ tokens:
+ engine: fernet
+ expiration: 3600
+ allow_expired_window: 86400
+ max_active_keys: 27
+ ...
+
Upgrades
========
diff --git a/_modules/keystoneng.py b/_modules/keystoneng.py
index 76b6870..ddf0da5 100644
--- a/_modules/keystoneng.py
+++ b/_modules/keystoneng.py
@@ -1082,11 +1082,11 @@
return ret
-def user_update(user_id=None, name=None, email=None, enabled=None,
+def user_update(user_id=None, name=None, email=None, enabled=None, options=None,
tenant=None, profile=None, project=None, description=None, **connection_args):
'''
Update a user's information (keystone user-update)
- The following fields may be updated: name, email, enabled, tenant.
+ The following fields may be updated: name, email, enabled, tenant, options.
Because the name is one of the fields, a valid user id is required.
CLI Examples:
@@ -1112,6 +1112,8 @@
email = user.email
if enabled is None:
enabled = user.enabled
+ if not options:
+ options = {}
if _client_version(kstone) > 2:
if description is None:
@@ -1129,9 +1131,9 @@
project_id = getattr(user, 'project_id', None)
kstone.users.update(user=user_id, name=name, email=email, enabled=enabled, description=description,
- project_id=project_id)
+ options=options, project_id=project_id)
else:
- kstone.users.update(user=user_id, name=name, email=email, enabled=enabled)
+ kstone.users.update(user=user_id, name=name, email=email, options=options, enabled=enabled)
tenant_id = None
if tenant:
diff --git a/_modules/keystonev3/common.py b/_modules/keystonev3/common.py
index 93cfe6d..9f2c8b1 100644
--- a/_modules/keystonev3/common.py
+++ b/_modules/keystonev3/common.py
@@ -1,5 +1,9 @@
import logging
import os_client_config
+import functools
+import time
+
+from keystoneclient import exceptions as ks_exceptions
log = logging.getLogger(__name__)
@@ -64,33 +68,60 @@
def send(method, microversion_header=None):
def wrap(func):
+ @functools.wraps(func)
def wrapped_f(*args, **kwargs):
headers = kwargs.pop('headers', {})
if kwargs.get('microversion'):
headers.setdefault(microversion_header,
kwargs.get('microversion'))
cloud_name = kwargs.pop('cloud_name')
+ connect_retries = 30
+ connect_retry_delay = 1
if not cloud_name:
e = NoCredentials()
log.error('%s' % e)
raise e
- adapter = _get_raw_client(cloud_name)
+ adapter = None
+ for i in range(connect_retries):
+ try:
+ adapter = _get_raw_client(cloud_name)
+ except (ks_exceptions.DiscoveryFailure) as e:
+ msg = ("Got exception when determining a suitable "
+ "URL for Keystone plugin. Sleeping for %ss. Attepmpts "
+ "%s of %s")
+ log.error(msg % (connect_retry_delay, i, connect_retries))
+ time.sleep(connect_retry_delay)
+ continue
+ break
+ if not adapter:
+ raise ks_exceptions.DiscoveryFailure("Could not connect to Keystone API to determine a suitable URL for the plugin")
# Remove salt internal kwargs
kwarg_keys = list(kwargs.keys())
for k in kwarg_keys:
if k.startswith('__'):
kwargs.pop(k)
- url, json = func(*args, **kwargs)
- if json:
- response = getattr(adapter, method)(url, headers=headers,
- json=json)
- else:
- response = getattr(adapter, method)(url, headers=headers)
- if not response.content:
+ url, json = func(*args, **kwargs)
+ response = None
+ for i in range(connect_retries):
+ try:
+ response = getattr(adapter, method)(
+ url, connect_retries=connect_retries,
+ json=json)
+ except Exception as e:
+ if not hasattr(e, 'http_status') or (e.http_status >= 500
+ or e.http_status == 0):
+ msg = ("Got retriable exception when contacting "
+ "Keystone API. Sleeping for %ss. Attepmpts "
+ "%s of %s")
+ log.error(msg % (connect_retry_delay, i, connect_retries))
+ time.sleep(connect_retry_delay)
+ continue
+ break
+ if not response or not response.content:
return {}
try:
resp = response.json()
- except:
+ except ValueError:
resp = response.content
return resp
return wrapped_f
diff --git a/_states/keystoneng.py b/_states/keystoneng.py
index 82ce494..36a0d52 100644
--- a/_states/keystoneng.py
+++ b/_states/keystoneng.py
@@ -105,6 +105,7 @@
profile=None,
password_reset=True,
project=None,
+ options=None,
**connection_args):
'''
Ensure that the keystone user is present with the specified properties.
@@ -138,6 +139,9 @@
enabled
Availability state for this user
+ options
+ Dictionary of extra user options.
+
roles
The roles the user should have under given tenants.
Passed as a dictionary mapping tenant names to a list
@@ -182,6 +186,7 @@
change_enabled = False
change_tenant = False
change_password = False
+ change_options = False
if user[name].get('email', None) != email:
change_email = True
@@ -200,7 +205,15 @@
**connection_args)):
change_password = True
- if __opts__.get('test') and (change_email or change_enabled or change_tenant or change_password):
+ if options:
+ options_to_update = {option: options[option] for option in options
+ if (option not in user[name]['options'])
+ or (options[option] != user[name]['options'][option])}
+
+ if len(options_to_update):
+ change_options = True
+
+ if __opts__.get('test') and (change_email or change_enabled or change_tenant or change_password or change_options):
ret['result'] = None
ret['comment'] = 'User "{0}" will be updated'.format(name)
if change_email is True:
@@ -211,6 +224,8 @@
ret['changes']['Tenant'] = 'Will be added to "{0}" tenant'.format(tenant)
if change_password is True:
ret['changes']['Password'] = 'Will be updated'
+ if change_options is True:
+ ret['changes']['Options'] = 'Will be updated'
return ret
ret['comment'] = 'User "{0}" is already present'.format(name)
@@ -236,6 +251,11 @@
ret['comment'] = 'User "{0}" has been updated'.format(name)
ret['changes']['Password'] = 'Updated'
+ if change_options:
+ __salt__['keystoneng.user_update'](name=name, options=options_to_update, profile=profile, **connection_args)
+ ret['comment'] = 'Options has been updated'
+ ret['changes']['Options'] = options_to_update
+
if roles:
for tenant in roles:
args = dict({'user_name': name, 'tenant_name':
@@ -283,6 +303,7 @@
email=email,
tenant_id=tenant_id,
enabled=enabled,
+ options=options,
profile=profile,
**connection_args)
if roles:
diff --git a/_states/keystonev3.py b/_states/keystonev3.py
index 2dd651d..9f287d5 100644
--- a/_states/keystonev3.py
+++ b/_states/keystonev3.py
@@ -251,15 +251,23 @@
exact_user = users[0]
user_id = exact_user['id']
changable = (
- 'default_project_id', 'domain_id', 'enabled', 'email'
+ 'default_project_id', 'domain_id', 'enabled', 'email', 'options'
)
if password_reset:
changable += ('password',)
to_update = {}
for key in kwargs:
- if (key in changable and (key not in exact_user or
- kwargs[key] != exact_user[key])):
+ if key in changable:
+ if key == 'options':
+ to_update['options'] = {option: value for option, value in kwargs['options'].items()
+ if (option not in exact_user['options'])
+ or (value != exact_user['options'][option])}
+
+ if not len(to_update['options']):
+ del to_update['options']
+
+ elif key not in exact_user or kwargs[key] != exact_user[key]:
to_update[key] = kwargs[key]
if to_update:
diff --git a/keystone/client/project.sls b/keystone/client/project.sls
index 240c99f..c8179d9 100644
--- a/keystone/client/project.sls
+++ b/keystone/client/project.sls
@@ -33,6 +33,21 @@
- password: {{ user.password }}
- email: {{ user.get('email', 'root@localhost') }}
- tenant: {{ tenant_name }}
+ {%- if user.options is defined %}
+ - options:
+ {%- if user.options.lock_password is defined %}
+ lock_password: {{ user.options.lock_password }}
+ {%- endif %}
+ {%- if user.options.ignore_change_password_upon_first_use is defined %}
+ ignore_change_password_upon_first_use: {{ user.options.ignore_change_password_upon_first_use }}
+ {%- endif %}
+ {%- if user.options.ignore_password_expiry is defined %}
+ ignore_password_expiry: {{ user.options.ignore_password_expiry }}
+ {%- endif %}
+ {%- if user.options.ignore_lockout_failure_attempts is defined %}
+ ignore_lockout_failure_attempts: {{ user.options.ignore_lockout_failure_attempts }}
+ {%- endif %}
+ {%- endif %}
- roles:
"{{ tenant_name }}":
{%- if user.get('is_admin', False) %}
diff --git a/keystone/client/resources/v3.sls b/keystone/client/resources/v3.sls
index 7803233..8f58f0c 100644
--- a/keystone/client/resources/v3.sls
+++ b/keystone/client/resources/v3.sls
@@ -172,6 +172,21 @@
{%- if user.password_reset is defined %}
- password_reset: {{ user.password_reset }}
{%- endif %}
+ {%- if user.options is defined %}
+ - options:
+ {%- if user.options.lock_password is defined %}
+ lock_password: {{ user.options.lock_password }}
+ {%- endif %}
+ {%- if user.options.ignore_change_password_upon_first_use is defined %}
+ ignore_change_password_upon_first_use: {{ user.options.ignore_change_password_upon_first_use }}
+ {%- endif %}
+ {%- if user.options.ignore_password_expiry is defined %}
+ ignore_password_expiry: {{ user.options.ignore_password_expiry }}
+ {%- endif %}
+ {%- if user.options.ignore_lockout_failure_attempts is defined %}
+ ignore_lockout_failure_attempts: {{ user.options.ignore_lockout_failure_attempts }}
+ {%- endif %}
+ {%- endif %}
{%- elif user.get('status', 'present') == 'absent' %}
diff --git a/keystone/client/server.sls b/keystone/client/server.sls
index 9f97b74..bf83eeb 100644
--- a/keystone/client/server.sls
+++ b/keystone/client/server.sls
@@ -149,6 +149,21 @@
- email: {{ user.email }}
{%- endif %}
- tenant: {{ tenant_name }}
+ {%- if user.options is defined %}
+ - options:
+ {%- if user.options.lock_password is defined %}
+ lock_password: {{ user.options.lock_password }}
+ {%- endif %}
+ {%- if user.options.ignore_change_password_upon_first_use is defined %}
+ ignore_change_password_upon_first_use: {{ user.options.ignore_change_password_upon_first_use }}
+ {%- endif %}
+ {%- if user.options.ignore_password_expiry is defined %}
+ ignore_password_expiry: {{ user.options.ignore_password_expiry }}
+ {%- endif %}
+ {%- if user.options.ignore_lockout_failure_attempts is defined %}
+ ignore_lockout_failure_attempts: {{ user.options.ignore_lockout_failure_attempts }}
+ {%- endif %}
+ {%- endif %}
- roles:
"{{ tenant_name }}":
{%- if user.get('is_admin', False) %}
diff --git a/keystone/files/newton/keystone.conf.Debian b/keystone/files/newton/keystone.conf.Debian
index 8d22cf4..e176b99 100644
--- a/keystone/files/newton/keystone.conf.Debian
+++ b/keystone/files/newton/keystone.conf.Debian
@@ -2594,6 +2594,9 @@
# (integer value)
# Minimum value: 1
#disable_user_account_days_inactive = <None>
+{%- if server.security_compliance.disable_user_account_days_inactive is defined and server.get('backend', 'sql') == 'sql' %}
+disable_user_account_days_inactive = {{ server.security_compliance.disable_user_account_days_inactive }}
+{%- endif %}
# The maximum number of times that a user can fail to authenticate before the
# user account is locked for the number of seconds specified by
@@ -2604,6 +2607,9 @@
# backend for the `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_failure_attempts = <None>
+{%- if server.security_compliance.lockout_failure_attempts is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_failure_attempts = {{ server.security_compliance.lockout_failure_attempts }}
+{%- endif %}
# The number of seconds a user account will be locked when the maximum number
# of failed authentication attempts (as specified by `[security_compliance]
@@ -2613,6 +2619,9 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_duration = 1800
+{%- if server.security_compliance.lockout_duration is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_duration = {{ server.security_compliance.lockout_duration }}
+{%- endif %}
# The number of days for which a password will be considered valid before
# requiring it to be changed. This feature is disabled by default. If enabled,
@@ -2621,12 +2630,18 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#password_expires_days = <None>
+{%- if server.security_compliance.password_expires_days is defined and server.get('backend', 'sql') == 'sql' %}
+password_expires_days = {{ server.security_compliance.password_expires_days }}
+{%- endif %}
# Comma separated list of user IDs to be ignored when checking if a password is
# expired. Passwords for users in this list will not expire. This feature will
# only be enabled if `[security_compliance] password_expires_days` is set.
# (list value)
#password_expires_ignore_user_ids =
+{%- if server.password_expires_ignore_user_ids is defined and server.password_expires_days is defined and server.get('backend', 'sql') == 'sql' %}
+password_expires_ignore_user_ids = {{ server.password_expires_ignore_user_ids }}
+{%- endif %}
# This controls the number of previous user password iterations to keep in
# history, in order to enforce that newly created passwords are unique. Setting
@@ -2635,6 +2650,9 @@
# backend for the `[identity] driver`. (integer value)
# Minimum value: 1
#unique_last_password_count = 1
+{%- if server.security_compliance.unique_last_password_count is defined and server.get('backend', 'sql') == 'sql' %}
+unique_last_password_count = {{ server.security_compliance.unique_last_password_count }}
+{%- endif %}
# The number of days that a password must be used before the user can change
# it. This prevents users from changing their passwords immediately in order to
@@ -2646,6 +2664,9 @@
# option should be less than the `password_expires_days`. (integer value)
# Minimum value: 0
#minimum_password_age = 0
+{%- if server.security_compliance.minimum_password_age is defined and server.get('backend', 'sql') == 'sql' %}
+minimum_password_age = {{ server.security_compliance.minimum_password_age }}
+{%- endif %}
# The regular expression used to validate password strength requirements. By
# default, the regular expression will match any password. The following is an
@@ -2653,13 +2674,18 @@
# minimum length of 7 characters: ^(?=.*\d)(?=.*[a-zA-Z]).{7,}$ This feature
# depends on the `sql` backend for the `[identity] driver`. (string value)
#password_regex = <None>
+{%- if server.security_compliance.password_regex is defined and server.get('backend', 'sql') == 'sql' %}
+password_regex = {{ server.security_compliance.password_regex }}
+{%- endif %}
# Describe your password regular expression here in language for humans. If a
# password fails to match the regular expression, the contents of this
# configuration variable will be returned to users to explain why their
# requested password was insufficient. (string value)
#password_regex_description = <None>
-
+{%- if server.security_compliance.password_regex_description is defined %}
+password_regex_description = {{ server.security_compliance.password_regex_description }}
+{%- endif %}
[shadow_users]
diff --git a/keystone/files/ocata/keystone.conf.Debian b/keystone/files/ocata/keystone.conf.Debian
index a2c50eb..d7372f9 100644
--- a/keystone/files/ocata/keystone.conf.Debian
+++ b/keystone/files/ocata/keystone.conf.Debian
@@ -2805,6 +2805,9 @@
# (integer value)
# Minimum value: 1
#disable_user_account_days_inactive = <None>
+{%- if server.security_compliance.disable_user_account_days_inactive is defined and server.get('backend', 'sql') == 'sql' %}
+disable_user_account_days_inactive = {{ server.security_compliance.disable_user_account_days_inactive }}
+{%- endif %}
# The maximum number of times that a user can fail to authenticate before the
# user account is locked for the number of seconds specified by
@@ -2815,6 +2818,9 @@
# backend for the `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_failure_attempts = <None>
+{%- if server.security_compliance.lockout_failure_attempts is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_failure_attempts = {{ server.security_compliance.lockout_failure_attempts }}
+{%- endif %}
# The number of seconds a user account will be locked when the maximum number
# of failed authentication attempts (as specified by `[security_compliance]
@@ -2824,6 +2830,9 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_duration = 1800
+{%- if server.security_compliance.lockout_duration is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_duration = {{ server.security_compliance.lockout_duration }}
+{%- endif %}
# The number of days for which a password will be considered valid before
# requiring it to be changed. This feature is disabled by default. If enabled,
@@ -2832,20 +2841,9 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#password_expires_days = <None>
-
-# DEPRECATED: Comma separated list of user IDs to be ignored when checking if a
-# password is expired. Passwords for users in this list will not expire. This
-# feature will only be enabled if `[security_compliance] password_expires_days`
-# is set. (list value)
-# This option is deprecated for removal since O.
-# Its value may be silently ignored in the future.
-# Reason: Functionality added as a per-user option "ignore_password_expiry" in
-# Ocata. Each user that should ignore password expiry should have the value set
-# to "true" in the user's `options` attribute (e.g.
-# `user['options']['ignore_password_expiry'] = True`) with an "update_user"
-# call. This avoids the need to restart keystone to adjust the users that
-# ignore password expiry. This option will be removed in the Pike release.
-#password_expires_ignore_user_ids =
+{%- if server.security_compliance.password_expires_days is defined and server.get('backend', 'sql') == 'sql' %}
+password_expires_days = {{ server.security_compliance.password_expires_days }}
+{%- endif %}
# This controls the number of previous user password iterations to keep in
# history, in order to enforce that newly created passwords are unique. Setting
@@ -2854,6 +2852,9 @@
# backend for the `[identity] driver`. (integer value)
# Minimum value: 1
#unique_last_password_count = 1
+{%- if server.security_compliance.unique_last_password_count is defined and server.get('backend', 'sql') == 'sql' %}
+unique_last_password_count = {{ server.security_compliance.unique_last_password_count }}
+{%- endif %}
# The number of days that a password must be used before the user can change
# it. This prevents users from changing their passwords immediately in order to
@@ -2865,6 +2866,9 @@
# option should be less than the `password_expires_days`. (integer value)
# Minimum value: 0
#minimum_password_age = 0
+{%- if server.security_compliance.minimum_password_age is defined and server.get('backend', 'sql') == 'sql' %}
+minimum_password_age = {{ server.security_compliance.minimum_password_age }}
+{%- endif %}
# The regular expression used to validate password strength requirements. By
# default, the regular expression will match any password. The following is an
@@ -2872,12 +2876,18 @@
# minimum length of 7 characters: ^(?=.*\d)(?=.*[a-zA-Z]).{7,}$ This feature
# depends on the `sql` backend for the `[identity] driver`. (string value)
#password_regex = <None>
+{%- if server.security_compliance.password_regex is defined and server.get('backend', 'sql') == 'sql' %}
+password_regex = {{ server.security_compliance.password_regex }}
+{%- endif %}
# Describe your password regular expression here in language for humans. If a
# password fails to match the regular expression, the contents of this
# configuration variable will be returned to users to explain why their
# requested password was insufficient. (string value)
#password_regex_description = <None>
+{%- if server.security_compliance.password_regex_description is defined %}
+password_regex_description = {{ server.security_compliance.password_regex_description }}
+{%- endif %}
# Enabling this option requires users to change their password when the user is
# created, or upon administrative reset. Before accessing any services,
@@ -2888,7 +2898,9 @@
# only applicable with the `sql` backend for the `[identity] driver`. (boolean
# value)
#change_password_upon_first_use = false
-
+{%- if server.security_compliance.change_password_upon_first_use is defined and server.get('backend', 'sql') == 'sql' %}
+change_password_upon_first_use = {{ server.security_compliance.change_password_upon_first_use }}
+{%- endif %}
[shadow_users]
@@ -3060,6 +3072,9 @@
# the built-in expiry time. This allows long running operations to succeed.
# Defaults to two days. (integer value)
#allow_expired_window = 172800
+{%- if server.tokens.allow_expired_window is defined %}
+allow_expired_window = {{ server.tokens.allow_expired_window }}
+{%- endif %}
hash_algorithm = {{ server.hash_algorithm }}
diff --git a/keystone/files/pike/keystone.conf.Debian b/keystone/files/pike/keystone.conf.Debian
index 4f3ef6d..ba8c9f9 100644
--- a/keystone/files/pike/keystone.conf.Debian
+++ b/keystone/files/pike/keystone.conf.Debian
@@ -2822,6 +2822,9 @@
# (integer value)
# Minimum value: 1
#disable_user_account_days_inactive = <None>
+{%- if server.security_compliance.disable_user_account_days_inactive is defined and server.get('backend', 'sql') == 'sql' %}
+disable_user_account_days_inactive = {{ server.security_compliance.disable_user_account_days_inactive }}
+{%- endif %}
# The maximum number of times that a user can fail to authenticate before the
# user account is locked for the number of seconds specified by
@@ -2832,6 +2835,9 @@
# backend for the `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_failure_attempts = <None>
+{%- if server.security_compliance.lockout_failure_attempts is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_failure_attempts = {{ server.security_compliance.lockout_failure_attempts }}
+{%- endif %}
# The number of seconds a user account will be locked when the maximum number
# of failed authentication attempts (as specified by `[security_compliance]
@@ -2841,6 +2847,9 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_duration = 1800
+{%- if server.security_compliance.lockout_duration is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_duration = {{ server.security_compliance.lockout_duration }}
+{%- endif %}
# The number of days for which a password will be considered valid before
# requiring it to be changed. This feature is disabled by default. If enabled,
@@ -2849,20 +2858,9 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#password_expires_days = <None>
-
-# DEPRECATED: Comma separated list of user IDs to be ignored when checking if a
-# password is expired. Passwords for users in this list will not expire. This
-# feature will only be enabled if `[security_compliance] password_expires_days`
-# is set. (list value)
-# This option is deprecated for removal since O.
-# Its value may be silently ignored in the future.
-# Reason: Functionality added as a per-user option "ignore_password_expiry" in
-# Ocata. Each user that should ignore password expiry should have the value set
-# to "true" in the user's `options` attribute (e.g.
-# `user['options']['ignore_password_expiry'] = True`) with an "update_user"
-# call. This avoids the need to restart keystone to adjust the users that
-# ignore password expiry. This option will be removed in the Pike release.
-#password_expires_ignore_user_ids =
+{%- if server.security_compliance.password_expires_days is defined and server.get('backend', 'sql') == 'sql' %}
+password_expires_days = {{ server.security_compliance.password_expires_days }}
+{%- endif %}
# This controls the number of previous user password iterations to keep in
# history, in order to enforce that newly created passwords are unique. Setting
@@ -2871,6 +2869,9 @@
# backend for the `[identity] driver`. (integer value)
# Minimum value: 1
#unique_last_password_count = 1
+{%- if server.security_compliance.unique_last_password_count is defined and server.get('backend', 'sql') == 'sql' %}
+unique_last_password_count = {{ server.security_compliance.unique_last_password_count }}
+{%- endif %}
# The number of days that a password must be used before the user can change
# it. This prevents users from changing their passwords immediately in order to
@@ -2882,6 +2883,9 @@
# option should be less than the `password_expires_days`. (integer value)
# Minimum value: 0
#minimum_password_age = 0
+{%- if server.security_compliance.minimum_password_age is defined and server.get('backend', 'sql') == 'sql' %}
+minimum_password_age = {{ server.security_compliance.minimum_password_age }}
+{%- endif %}
# The regular expression used to validate password strength requirements. By
# default, the regular expression will match any password. The following is an
@@ -2889,12 +2893,18 @@
# minimum length of 7 characters: ^(?=.*\d)(?=.*[a-zA-Z]).{7,}$ This feature
# depends on the `sql` backend for the `[identity] driver`. (string value)
#password_regex = <None>
+{%- if server.security_compliance.password_regex is defined and server.get('backend', 'sql') == 'sql' %}
+password_regex = {{ server.security_compliance.password_regex }}
+{%- endif %}
# Describe your password regular expression here in language for humans. If a
# password fails to match the regular expression, the contents of this
# configuration variable will be returned to users to explain why their
# requested password was insufficient. (string value)
#password_regex_description = <None>
+{%- if server.security_compliance.password_regex_description is defined %}
+password_regex_description = {{ server.security_compliance.password_regex_description }}
+{%- endif %}
# Enabling this option requires users to change their password when the user is
# created, or upon administrative reset. Before accessing any services,
@@ -2905,7 +2915,9 @@
# only applicable with the `sql` backend for the `[identity] driver`. (boolean
# value)
#change_password_upon_first_use = false
-
+{%- if server.security_compliance.change_password_upon_first_use is defined and server.get('backend', 'sql') == 'sql' %}
+change_password_upon_first_use = {{ server.security_compliance.change_password_upon_first_use }}
+{%- endif %}
[shadow_users]
@@ -3078,6 +3090,9 @@
# the built-in expiry time. This allows long running operations to succeed.
# Defaults to two days. (integer value)
#allow_expired_window = 172800
+{%- if server.tokens.allow_expired_window is defined %}
+allow_expired_window = {{ server.tokens.allow_expired_window }}
+{%- endif %}
hash_algorithm = {{ server.hash_algorithm }}
diff --git a/keystone/files/queens/keystone.conf.Debian b/keystone/files/queens/keystone.conf.Debian
index c4d0c1b..f872c2f 100644
--- a/keystone/files/queens/keystone.conf.Debian
+++ b/keystone/files/queens/keystone.conf.Debian
@@ -1399,6 +1399,9 @@
# user table. (integer value)
# Minimum value: 1
#disable_user_account_days_inactive = <None>
+{%- if server.security_compliance.disable_user_account_days_inactive is defined and server.get('backend', 'sql') == 'sql' %}
+disable_user_account_days_inactive = {{ server.security_compliance.disable_user_account_days_inactive }}
+{%- endif %}
# The maximum number of times that a user can fail to authenticate
# before the user account is locked for the number of seconds
@@ -1410,6 +1413,9 @@
# `[identity] driver`. (integer value)
# Minimum value: 1
#lockout_failure_attempts = <None>
+{%- if server.security_compliance.lockout_failure_attempts is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_failure_attempts = {{ server.security_compliance.lockout_failure_attempts }}
+{%- endif %}
# The number of seconds a user account will be locked when the maximum
# number of failed authentication attempts (as specified by
@@ -1420,6 +1426,9 @@
# driver`. (integer value)
# Minimum value: 1
#lockout_duration = 1800
+{%- if server.security_compliance.lockout_duration is defined and server.get('backend', 'sql') == 'sql' %}
+lockout_duration = {{ server.security_compliance.lockout_duration }}
+{%- endif %}
# The number of days for which a password will be considered valid
# before requiring it to be changed. This feature is disabled by
@@ -1429,6 +1438,9 @@
# value)
# Minimum value: 1
#password_expires_days = <None>
+{%- if server.security_compliance.password_expires_days is defined and server.get('backend', 'sql') == 'sql' %}
+password_expires_days = {{ server.security_compliance.password_expires_days }}
+{%- endif %}
# This controls the number of previous user password iterations to
# keep in history, in order to enforce that newly created passwords
@@ -1439,6 +1451,9 @@
# for the `[identity] driver`. (integer value)
# Minimum value: 1
#unique_last_password_count = 1
+{%- if server.security_compliance.unique_last_password_count is defined and server.get('backend', 'sql') == 'sql' %}
+unique_last_password_count = {{ server.security_compliance.unique_last_password_count }}
+{%- endif %}
# The number of days that a password must be used before the user can
# change it. This prevents users from changing their passwords
@@ -1451,6 +1466,9 @@
# be less than the `password_expires_days`. (integer value)
# Minimum value: 0
#minimum_password_age = 0
+{%- if server.security_compliance.minimum_password_age is defined and server.get('backend', 'sql') == 'sql' %}
+minimum_password_age = {{ server.security_compliance.minimum_password_age }}
+{%- endif %}
# The regular expression used to validate password strength
# requirements. By default, the regular expression will match any
@@ -1459,6 +1477,9 @@
# ^(?=.*\d)(?=.*[a-zA-Z]).{7,}$ This feature depends on the `sql`
# backend for the `[identity] driver`. (string value)
#password_regex = <None>
+{%- if server.security_compliance.password_regex is defined and server.get('backend', 'sql') == 'sql' %}
+password_regex = {{ server.security_compliance.password_regex }}
+{%- endif %}
# Describe your password regular expression here in language for
# humans. If a password fails to match the regular expression, the
@@ -1466,6 +1487,9 @@
# explain why their requested password was insufficient. (string
# value)
#password_regex_description = <None>
+{%- if server.security_compliance.password_regex_description is defined %}
+password_regex_description = {{ server.security_compliance.password_regex_description }}
+{%- endif %}
# Enabling this option requires users to change their password when
# the user is created, or upon administrative reset. Before accessing
@@ -1476,7 +1500,9 @@
# is disabled by default. This feature is only applicable with the
# `sql` backend for the `[identity] driver`. (boolean value)
#change_password_upon_first_use = false
-
+{%- if server.security_compliance.change_password_upon_first_use is defined and server.get('backend', 'sql') == 'sql' %}
+change_password_upon_first_use = {{ server.security_compliance.change_password_upon_first_use }}
+{%- endif %}
[shadow_users]
@@ -1687,6 +1713,9 @@
# for beyond the built-in expiry time. This allows long running
# operations to succeed. Defaults to two days. (integer value)
#allow_expired_window = 172800
+{%- if server.tokens.allow_expired_window is defined %}
+allow_expired_window = {{ server.tokens.allow_expired_window }}
+{%- endif %}
hash_algorithm = {{ server.hash_algorithm }}
diff --git a/keystone/map.jinja b/keystone/map.jinja
index d97e1a5..cbf1f03 100644
--- a/keystone/map.jinja
+++ b/keystone/map.jinja
@@ -31,6 +31,7 @@
'notification': False,
'roles': ['admin', 'Member'],
'cacert': '/etc/ssl/certs/ca-certificates.crt',
+ 'security_compliance': {},
'logging': {
'app_name': 'keystone',
'log_appender': false,
@@ -56,6 +57,7 @@
'notification': False,
'roles': ['admin', 'Member'],
'cacert': '/etc/pki/tls/certs/ca-bundle.crt',
+ 'security_compliance': {},
'logging': {
'app_name': 'keystone',
'log_appender': false,
diff --git a/tests/pillar/cluster.sls b/tests/pillar/cluster.sls
index 9d76228..24b17f4 100644
--- a/tests/pillar/cluster.sls
+++ b/tests/pillar/cluster.sls
@@ -24,9 +24,20 @@
tokens:
engine: cache
expiration: 86400
+ allow_expired_window: 86400
location: /etc/keystone/fernet-keys/
notification: false
notification_format: cadf
+ security_compliance:
+ disable_user_account_days_inactive: 90
+ lockout_failure_attempts: 5
+ lockout_duration: 600
+ password_expires_days: 90
+ unique_last_password_count: 10
+ minimum_password_age: 0
+ password_regex: '^(?=.*\d)(?=.*[a-zA-Z]).{7,}$$'
+ password_regex_description: 'Your password must contains at least 1 letter, 1 digit, and have a minimum length of 7 characters'
+ change_password_upon_first_use: True
logging:
log_appender: false
log_handlers:
diff --git a/tests/pillar/single.sls b/tests/pillar/single.sls
index d52812d..3570ed1 100644
--- a/tests/pillar/single.sls
+++ b/tests/pillar/single.sls
@@ -26,8 +26,19 @@
engine: cache
expiration: 86400
location: /etc/keystone/fernet-keys/
+ allow_expired_window: 86400
notification: false
notification_format: cadf
+ security_compliance:
+ disable_user_account_days_inactive: 90
+ lockout_failure_attempts: 5
+ lockout_duration: 600
+ password_expires_days: 90
+ unique_last_password_count: 10
+ minimum_password_age: 0
+ password_regex: '^(?=.*\d)(?=.*[a-zA-Z]).{7,}$$'
+ password_regex_description: 'Your password must contains at least 1 letter, 1 digit, and have a minimum length of 7 characters'
+ change_password_upon_first_use: True
logging:
log_appender: false
log_handlers: