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',):
try:
retrow[date_key] = datetime.datetime.strptime(
row['date_key'], '%Y-%m-%d %H:%M:%S')
except (ValueError, KeyError):
row[date_key], '%Y-%m-%d %H:%M:%S')
except ValueError:
retrow[date_key] = None
retrow['defaults variables'] = row['defaults variables']
if return_password:
@ -1025,6 +1025,7 @@ def _role_cmd_args(name,
groups=None,
replication=None,
rolepassword=None,
valid_until=None,
db_role=None):
if createuser is not None and superuser is None:
superuser = createuser
@ -1041,6 +1042,7 @@ def _role_cmd_args(name,
encrypted = _DEFAULT_PASSWORDS_ENCRYPTION
skip_passwd = False
escaped_password = ''
escaped_valid_until = ''
if not (
rolepassword is not None
# first is passwd set
@ -1058,6 +1060,10 @@ def _role_cmd_args(name,
_maybe_encrypt_password(name,
rolepassword.replace('\'', '\'\''),
encrypted=encrypted))
if isinstance(valid_until, six.string_types) and bool(valid_until):
escaped_valid_until = '\'{0}\''.format(
valid_until.replace('\'', '\'\''),
)
skip_superuser = False
if bool(db_role) and bool(superuser) == bool(db_role['superuser']):
skip_superuser = True
@ -1081,6 +1087,10 @@ def _role_cmd_args(name,
{'flag': 'PASSWORD', 'test': bool(rolepassword),
'skip': skip_passwd,
'addtxt': escaped_password},
{'flag': 'VALID UNTIL',
'test': bool(valid_until),
'skip': valid_until is None,
'addtxt': escaped_valid_until},
)
for data in flags:
sub_cmd = _add_role_flag(sub_cmd, **data)
@ -1110,6 +1120,7 @@ def _role_create(name,
inherit=None,
replication=None,
rolepassword=None,
valid_until=None,
typ_='role',
groups=None,
runas=None):
@ -1138,7 +1149,8 @@ def _role_create(name,
superuser=superuser,
groups=groups,
replication=replication,
rolepassword=rolepassword
rolepassword=rolepassword,
valid_until=valid_until
))
ret = _psql_prepare_and_run(['-c', sub_cmd],
runas=runas, host=host, user=user, port=port,
@ -1164,6 +1176,7 @@ def user_create(username,
superuser=None,
replication=None,
rolepassword=None,
valid_until=None,
groups=None,
runas=None):
'''
@ -1175,7 +1188,7 @@ def user_create(username,
salt '*' postgres.user_create 'username' user='user' \\
host='hostname' port='port' password='password' \\
rolepassword='rolepassword'
rolepassword='rolepassword' valid_until='valid_until'
'''
return _role_create(username,
typ_='user',
@ -1194,6 +1207,7 @@ def user_create(username,
superuser=superuser,
replication=replication,
rolepassword=rolepassword,
valid_until=valid_until,
groups=groups,
runas=runas)
@ -1215,6 +1229,7 @@ def _role_update(name,
superuser=None,
replication=None,
rolepassword=None,
valid_until=None,
groups=None,
runas=None):
'''
@ -1250,6 +1265,7 @@ def _role_update(name,
groups=groups,
replication=replication,
rolepassword=rolepassword,
valid_until=valid_until,
db_role=role
))
ret = _psql_prepare_and_run(['-c', sub_cmd],
@ -1276,6 +1292,7 @@ def user_update(username,
connlimit=None,
replication=None,
rolepassword=None,
valid_until=None,
groups=None,
runas=None):
'''
@ -1287,7 +1304,7 @@ def user_update(username,
salt '*' postgres.user_update 'username' user='user' \\
host='hostname' port='port' password='password' \\
rolepassword='rolepassword'
rolepassword='rolepassword' valid_until='valid_until'
'''
return _role_update(username,
user=user,
@ -1306,6 +1323,7 @@ def user_update(username,
superuser=superuser,
replication=replication,
rolepassword=rolepassword,
valid_until=valid_until,
groups=groups,
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
# Import Python libs
import datetime
import logging
# Import salt libs
import logging
# Salt imports
from salt.modules import postgres
@ -45,6 +46,7 @@ def present(name,
password=None,
default_password=None,
refresh_password=None,
valid_until=None,
groups=None,
user=None,
maintenance_db=None,
@ -112,6 +114,9 @@ def present(name,
This behaviour makes it possible to execute in environments without
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
A string of comma separated groups the user should be in
@ -168,7 +173,6 @@ def present(name,
if user_attr is not None:
mode = 'update'
# The user is not present, make it!
cret = None
update = {}
if mode == 'update':
@ -199,6 +203,18 @@ def present(name,
update['superuser'] = superuser
if password is not None and (refresh_password or user_attr['password'] != password):
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:
lgroups = groups
if isinstance(groups, (six.string_types, six.text_type)):
@ -228,6 +244,7 @@ def present(name,
inherit=inherit,
replication=replication,
rolepassword=password,
valid_until=valid_until,
groups=groups,
**db_args)
else:

View File

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

View File

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