Update postgresql users only when necessary
For read-only databases, users should not change when no changes are required. Don't issue ALTER ROLE when role attribute flags, users password or expiry time is not changing. In certain cases (hashed passwords in the DB, but the password argument is not hashed) passlib.hash is required to avoid running ALTER ROLE.
This commit is contained in:
parent
78daf52994
commit
43bad27948
1 changed files with 52 additions and 4 deletions
|
@ -44,7 +44,7 @@ options:
|
||||||
password:
|
password:
|
||||||
description:
|
description:
|
||||||
- set the user's password, before 1.4 this was required.
|
- set the user's password, before 1.4 this was required.
|
||||||
- "When passing an encrypted password, the encrypted parameter must also be true, and it must be generated with the format C('str[\\"md5\\"] + md5[ password + username ]'), resulting in a total of 35 characters. An easy way to do this is: C(echo \\"md5`echo -n \\"verysecretpasswordJOE\\" | md5`\\")."
|
- "When passing an encrypted password, the encrypted parameter must also be true, and it must be generated with the format C('str[\\"md5\\"] + md5[ password + username ]'), resulting in a total of 35 characters. An easy way to do this is: C(echo \\"md5`echo -n \\"verysecretpasswordJOE\\" | md5`\\"). Note that if encrypted is set, the stored password will be hashed whether or not it is pre-encrypted."
|
||||||
required: false
|
required: false
|
||||||
default: null
|
default: null
|
||||||
db:
|
db:
|
||||||
|
@ -103,7 +103,7 @@ options:
|
||||||
choices: [ "present", "absent" ]
|
choices: [ "present", "absent" ]
|
||||||
encrypted:
|
encrypted:
|
||||||
description:
|
description:
|
||||||
- denotes if the password is already encrypted. boolean.
|
- whether the password is stored hashed in the database. boolean. Passwords can be passed already hashed or unhashed, and postgresql ensures the stored password is hashed when encrypted is set.
|
||||||
required: false
|
required: false
|
||||||
default: false
|
default: false
|
||||||
version_added: '1.4'
|
version_added: '1.4'
|
||||||
|
@ -129,6 +129,10 @@ notes:
|
||||||
PostgreSQL must also be installed on the remote host. For Ubuntu-based
|
PostgreSQL must also be installed on the remote host. For Ubuntu-based
|
||||||
systems, install the postgresql, libpq-dev, and python-psycopg2 packages
|
systems, install the postgresql, libpq-dev, and python-psycopg2 packages
|
||||||
on the remote host before using this module.
|
on the remote host before using this module.
|
||||||
|
- If the passlib library is installed, then passwords that are encrypted
|
||||||
|
in the DB but not encrypted when passed as arguments can be checked for
|
||||||
|
changes. If the passlib library is not installed, unencrypted passwords
|
||||||
|
stored in the DB encrypted will be assumed to have changed.
|
||||||
- If you specify PUBLIC as the user, then the privilege changes will apply
|
- If you specify PUBLIC as the user, then the privilege changes will apply
|
||||||
to all users. You may not specify password or role_attr_flags when the
|
to all users. You may not specify password or role_attr_flags when the
|
||||||
PUBLIC user is specified.
|
PUBLIC user is specified.
|
||||||
|
@ -161,6 +165,7 @@ import itertools
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
except ImportError:
|
except ImportError:
|
||||||
postgresqldb_found = False
|
postgresqldb_found = False
|
||||||
else:
|
else:
|
||||||
|
@ -173,6 +178,12 @@ VALID_PRIVS = dict(table=frozenset(('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRU
|
||||||
database=frozenset(('CREATE', 'CONNECT', 'TEMPORARY', 'TEMP', 'ALL', 'USAGE')),
|
database=frozenset(('CREATE', 'CONNECT', 'TEMPORARY', 'TEMP', 'ALL', 'USAGE')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# map to cope with idiosyncracies of SUPERUSER and LOGIN
|
||||||
|
PRIV_TO_AUTHID_COLUMN = dict(SUPERUSER='rolsuper', CREATEROLE='rolcreaterole',
|
||||||
|
CREATEUSER='rolcreateuser', CREATEDB='rolcreatedb',
|
||||||
|
INHERIT='rolinherit', LOGIN='rolcanlogin',
|
||||||
|
REPLICATION='rolreplication')
|
||||||
|
|
||||||
class InvalidFlagsError(Exception):
|
class InvalidFlagsError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -230,8 +241,45 @@ def user_alter(cursor, module, user, password, role_attr_flags, encrypted, expir
|
||||||
# Grab current role attributes.
|
# Grab current role attributes.
|
||||||
current_role_attrs = cursor.fetchone()
|
current_role_attrs = cursor.fetchone()
|
||||||
|
|
||||||
alter = ['ALTER USER %(user)s' % {"user": pg_quote_identifier(user, 'role')}]
|
# Do we actually need to do anything?
|
||||||
|
pwchanging = False
|
||||||
if password is not None:
|
if password is not None:
|
||||||
|
if encrypted:
|
||||||
|
if password.startswith('md5'):
|
||||||
|
if password != current_role_attrs['rolpassword']:
|
||||||
|
pwchanging = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from passlib.hash import postgres_md5 as pm
|
||||||
|
if pm.encrypt(password, user) != current_role_attrs['rolpassword']:
|
||||||
|
pwchanging = True
|
||||||
|
except ImportError:
|
||||||
|
# Cannot check if passlib is not installed, so assume password is different
|
||||||
|
pwchanging = True
|
||||||
|
else:
|
||||||
|
if password != current_role_attrs['rolpassword']:
|
||||||
|
pwchanging = True
|
||||||
|
|
||||||
|
role_attr_flags_changing = False
|
||||||
|
if role_attr_flags:
|
||||||
|
role_attr_flags_dict = {}
|
||||||
|
for r in role_attr_flags.split(','):
|
||||||
|
if r.startswith('NO'):
|
||||||
|
role_attr_flags_dict[r.replace('NO', '', 1)] = False
|
||||||
|
else:
|
||||||
|
role_attr_flags_dict[r] = True
|
||||||
|
|
||||||
|
for role_attr_name, role_attr_value in role_attr_flags_dict.items():
|
||||||
|
if current_role_attrs[PRIV_TO_AUTHID_COLUMN[role_attr_name]] != role_attr_value:
|
||||||
|
role_attr_flags_changing = True
|
||||||
|
|
||||||
|
expires_changing = (expires is not None and expires == current_roles_attrs['rol_valid_until'])
|
||||||
|
|
||||||
|
if not pwchanging and not role_attr_flags_changing and not expires_changing:
|
||||||
|
return False
|
||||||
|
|
||||||
|
alter = ['ALTER USER %(user)s' % {"user": pg_quote_identifier(user, 'role')}]
|
||||||
|
if pwchanging:
|
||||||
alter.append("WITH %(crypt)s" % {"crypt": encrypted})
|
alter.append("WITH %(crypt)s" % {"crypt": encrypted})
|
||||||
alter.append("PASSWORD %(password)s")
|
alter.append("PASSWORD %(password)s")
|
||||||
alter.append(role_attr_flags)
|
alter.append(role_attr_flags)
|
||||||
|
@ -527,7 +575,7 @@ def main():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db_connection = psycopg2.connect(**kw)
|
db_connection = psycopg2.connect(**kw)
|
||||||
cursor = db_connection.cursor()
|
cursor = db_connection.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue