Merge pull request #42894 from gilbsgilbs/postgres-valid-until

Add support for PostgreSQL password expiration.
This commit is contained in:
Mike Place 2017-08-22 12:14:53 -06:00 committed by GitHub
commit 7d52bcee46
4 changed files with 57 additions and 12 deletions

View File

@ -886,8 +886,8 @@ def user_list(user=None, host=None, port=None, maintenance_db=None,
for date_key in ('expiry time',): for date_key in ('expiry time',):
try: try:
retrow[date_key] = datetime.datetime.strptime( retrow[date_key] = datetime.datetime.strptime(
row['date_key'], '%Y-%m-%d %H:%M:%S') row[date_key], '%Y-%m-%d %H:%M:%S')
except (ValueError, KeyError): except ValueError:
retrow[date_key] = None retrow[date_key] = None
retrow['defaults variables'] = row['defaults variables'] retrow['defaults variables'] = row['defaults variables']
if return_password: if return_password:
@ -1025,6 +1025,7 @@ def _role_cmd_args(name,
groups=None, groups=None,
replication=None, replication=None,
rolepassword=None, rolepassword=None,
valid_until=None,
db_role=None): db_role=None):
if createuser is not None and superuser is None: if createuser is not None and superuser is None:
superuser = createuser superuser = createuser
@ -1041,6 +1042,7 @@ def _role_cmd_args(name,
encrypted = _DEFAULT_PASSWORDS_ENCRYPTION encrypted = _DEFAULT_PASSWORDS_ENCRYPTION
skip_passwd = False skip_passwd = False
escaped_password = '' escaped_password = ''
escaped_valid_until = ''
if not ( if not (
rolepassword is not None rolepassword is not None
# first is passwd set # first is passwd set
@ -1058,6 +1060,10 @@ def _role_cmd_args(name,
_maybe_encrypt_password(name, _maybe_encrypt_password(name,
rolepassword.replace('\'', '\'\''), rolepassword.replace('\'', '\'\''),
encrypted=encrypted)) encrypted=encrypted))
if isinstance(valid_until, six.string_types) and bool(valid_until):
escaped_valid_until = '\'{0}\''.format(
valid_until.replace('\'', '\'\''),
)
skip_superuser = False skip_superuser = False
if bool(db_role) and bool(superuser) == bool(db_role['superuser']): if bool(db_role) and bool(superuser) == bool(db_role['superuser']):
skip_superuser = True skip_superuser = True
@ -1081,6 +1087,10 @@ def _role_cmd_args(name,
{'flag': 'PASSWORD', 'test': bool(rolepassword), {'flag': 'PASSWORD', 'test': bool(rolepassword),
'skip': skip_passwd, 'skip': skip_passwd,
'addtxt': escaped_password}, 'addtxt': escaped_password},
{'flag': 'VALID UNTIL',
'test': bool(valid_until),
'skip': valid_until is None,
'addtxt': escaped_valid_until},
) )
for data in flags: for data in flags:
sub_cmd = _add_role_flag(sub_cmd, **data) sub_cmd = _add_role_flag(sub_cmd, **data)
@ -1110,6 +1120,7 @@ def _role_create(name,
inherit=None, inherit=None,
replication=None, replication=None,
rolepassword=None, rolepassword=None,
valid_until=None,
typ_='role', typ_='role',
groups=None, groups=None,
runas=None): runas=None):
@ -1138,7 +1149,8 @@ def _role_create(name,
superuser=superuser, superuser=superuser,
groups=groups, groups=groups,
replication=replication, replication=replication,
rolepassword=rolepassword rolepassword=rolepassword,
valid_until=valid_until
)) ))
ret = _psql_prepare_and_run(['-c', sub_cmd], ret = _psql_prepare_and_run(['-c', sub_cmd],
runas=runas, host=host, user=user, port=port, runas=runas, host=host, user=user, port=port,
@ -1164,6 +1176,7 @@ def user_create(username,
superuser=None, superuser=None,
replication=None, replication=None,
rolepassword=None, rolepassword=None,
valid_until=None,
groups=None, groups=None,
runas=None): runas=None):
''' '''
@ -1175,7 +1188,7 @@ def user_create(username,
salt '*' postgres.user_create 'username' user='user' \\ salt '*' postgres.user_create 'username' user='user' \\
host='hostname' port='port' password='password' \\ host='hostname' port='port' password='password' \\
rolepassword='rolepassword' rolepassword='rolepassword' valid_until='valid_until'
''' '''
return _role_create(username, return _role_create(username,
typ_='user', typ_='user',
@ -1194,6 +1207,7 @@ def user_create(username,
superuser=superuser, superuser=superuser,
replication=replication, replication=replication,
rolepassword=rolepassword, rolepassword=rolepassword,
valid_until=valid_until,
groups=groups, groups=groups,
runas=runas) runas=runas)
@ -1215,6 +1229,7 @@ def _role_update(name,
superuser=None, superuser=None,
replication=None, replication=None,
rolepassword=None, rolepassword=None,
valid_until=None,
groups=None, groups=None,
runas=None): runas=None):
''' '''
@ -1250,6 +1265,7 @@ def _role_update(name,
groups=groups, groups=groups,
replication=replication, replication=replication,
rolepassword=rolepassword, rolepassword=rolepassword,
valid_until=valid_until,
db_role=role db_role=role
)) ))
ret = _psql_prepare_and_run(['-c', sub_cmd], ret = _psql_prepare_and_run(['-c', sub_cmd],
@ -1276,6 +1292,7 @@ def user_update(username,
connlimit=None, connlimit=None,
replication=None, replication=None,
rolepassword=None, rolepassword=None,
valid_until=None,
groups=None, groups=None,
runas=None): runas=None):
''' '''
@ -1287,7 +1304,7 @@ def user_update(username,
salt '*' postgres.user_update 'username' user='user' \\ salt '*' postgres.user_update 'username' user='user' \\
host='hostname' port='port' password='password' \\ host='hostname' port='port' password='password' \\
rolepassword='rolepassword' rolepassword='rolepassword' valid_until='valid_until'
''' '''
return _role_update(username, return _role_update(username,
user=user, user=user,
@ -1306,6 +1323,7 @@ def user_update(username,
superuser=superuser, superuser=superuser,
replication=replication, replication=replication,
rolepassword=rolepassword, rolepassword=rolepassword,
valid_until=valid_until,
groups=groups, groups=groups,
runas=runas) runas=runas)

View File

@ -13,9 +13,10 @@ The postgres_users module is used to create and manage Postgres users.
from __future__ import absolute_import from __future__ import absolute_import
# Import Python libs # Import Python libs
import datetime
import logging
# Import salt libs # Import salt libs
import logging
# Salt imports # Salt imports
from salt.modules import postgres from salt.modules import postgres
@ -45,6 +46,7 @@ def present(name,
password=None, password=None,
default_password=None, default_password=None,
refresh_password=None, refresh_password=None,
valid_until=None,
groups=None, groups=None,
user=None, user=None,
maintenance_db=None, maintenance_db=None,
@ -112,6 +114,9 @@ def present(name,
This behaviour makes it possible to execute in environments without This behaviour makes it possible to execute in environments without
superuser access available, e.g. Amazon RDS for PostgreSQL superuser access available, e.g. Amazon RDS for PostgreSQL
valid_until
A date and time after which the role's password is no longer valid.
groups groups
A string of comma separated groups the user should be in A string of comma separated groups the user should be in
@ -168,7 +173,6 @@ def present(name,
if user_attr is not None: if user_attr is not None:
mode = 'update' mode = 'update'
# The user is not present, make it!
cret = None cret = None
update = {} update = {}
if mode == 'update': if mode == 'update':
@ -199,6 +203,18 @@ def present(name,
update['superuser'] = superuser update['superuser'] = superuser
if password is not None and (refresh_password or user_attr['password'] != password): if password is not None and (refresh_password or user_attr['password'] != password):
update['password'] = True update['password'] = True
if valid_until is not None:
valid_until_dt = __salt__['postgres.psql_query'](
'SELECT \'{0}\'::timestamp(0) as dt;'.format(
valid_until.replace('\'', '\'\'')),
**db_args)[0]['dt']
try:
valid_until_dt = datetime.datetime.strptime(
valid_until_dt, '%Y-%m-%d %H:%M:%S')
except ValueError:
valid_until_dt = None
if valid_until_dt != user_attr['expiry time']:
update['valid_until'] = valid_until
if groups is not None: if groups is not None:
lgroups = groups lgroups = groups
if isinstance(groups, (six.string_types, six.text_type)): if isinstance(groups, (six.string_types, six.text_type)):
@ -228,6 +244,7 @@ def present(name,
inherit=inherit, inherit=inherit,
replication=replication, replication=replication,
rolepassword=password, rolepassword=password,
valid_until=valid_until,
groups=groups, groups=groups,
**db_args) **db_args)
else: else:

View File

@ -2,6 +2,7 @@
# Import python libs # Import python libs
from __future__ import absolute_import, print_function from __future__ import absolute_import, print_function
import datetime
import re import re
# Import Salt Testing libs # Import Salt Testing libs
@ -335,6 +336,7 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
superuser=False, superuser=False,
replication=False, replication=False,
rolepassword='test_role_pass', rolepassword='test_role_pass',
valid_until='2042-07-01',
groups='test_groups', groups='test_groups',
runas='foo' runas='foo'
) )
@ -345,9 +347,10 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
call = postgres._run_psql.call_args[0][0][14] call = postgres._run_psql.call_args[0][0][14]
self.assertTrue(re.match('CREATE ROLE "testuser"', call)) self.assertTrue(re.match('CREATE ROLE "testuser"', call))
for i in ( for i in (
'INHERIT NOCREATEDB NOCREATEROLE ' 'INHERIT', 'NOCREATEDB', 'NOCREATEROLE', 'NOSUPERUSER',
'NOSUPERUSER NOREPLICATION LOGIN UNENCRYPTED PASSWORD' 'NOREPLICATION', 'LOGIN', 'UNENCRYPTED', 'PASSWORD',
).split(): 'VALID UNTIL',
):
self.assertTrue(i in call, '{0} not in {1}'.format(i, call)) self.assertTrue(i in call, '{0} not in {1}'.format(i, call))
def test_user_exists(self): def test_user_exists(self):
@ -368,6 +371,7 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
'replication': None, 'replication': None,
'password': 'test_password', 'password': 'test_password',
'connections': '-1', 'connections': '-1',
'expiry time': '',
'defaults variables': None 'defaults variables': None
}])): }])):
ret = postgres.user_exists( ret = postgres.user_exists(
@ -398,6 +402,7 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
'can login': 't', 'can login': 't',
'replication': None, 'replication': None,
'connections': '-1', 'connections': '-1',
'expiry time': '2017-08-16 08:57:46',
'defaults variables': None 'defaults variables': None
}])): }])):
ret = postgres.user_list( ret = postgres.user_list(
@ -416,7 +421,8 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
'can create roles': True, 'can create roles': True,
'connections': None, 'connections': None,
'replication': None, 'replication': None,
'expiry time': None, 'expiry time': datetime.datetime(
2017, 8, 16, 8, 57, 46),
'can login': True, 'can login': True,
'can update system catalogs': True, 'can update system catalogs': True,
'groups': [], 'groups': [],
@ -464,6 +470,7 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
login=True, login=True,
replication=False, replication=False,
rolepassword='test_role_pass', rolepassword='test_role_pass',
valid_until='2017-07-01',
groups='test_groups', groups='test_groups',
runas='foo' runas='foo'
) )
@ -475,7 +482,8 @@ class PostgresTestCase(TestCase, LoaderModuleMockMixin):
re.match( re.match(
'ALTER ROLE "test_username" WITH INHERIT NOCREATEDB ' 'ALTER ROLE "test_username" WITH INHERIT NOCREATEDB '
'NOCREATEROLE NOREPLICATION LOGIN ' 'NOCREATEROLE NOREPLICATION LOGIN '
'UNENCRYPTED PASSWORD [\'"]{0,5}test_role_pass[\'"]{0,5};' 'UNENCRYPTED PASSWORD [\'"]{0,5}test_role_pass[\'"]{0,5} '
'VALID UNTIL \'2017-07-01\';'
' GRANT "test_groups" TO "test_username"', ' GRANT "test_groups" TO "test_username"',
postgres._run_psql.call_args[0][0][14] postgres._run_psql.call_args[0][0][14]
) )

View File

@ -78,6 +78,7 @@ class PostgresUserTestCase(TestCase, LoaderModuleMockMixin):
maintenance_db=None, maintenance_db=None,
login=None, login=None,
password=None, password=None,
valid_until=None,
createdb=None) createdb=None)
def test_present__update(self): def test_present__update(self):
@ -126,6 +127,7 @@ class PostgresUserTestCase(TestCase, LoaderModuleMockMixin):
maintenance_db=None, maintenance_db=None,
login=True, login=True,
password=None, password=None,
valid_until=None,
createdb=None) createdb=None)
def test_present__no_update(self): def test_present__no_update(self):