mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
postgresql_user: set encrypted as default and fix empty password reporting changed (#36931)
* Set encrypted as default and fix empty password reporting changed * Starting with Postgres 10 `UNENCRYPTED` passwords are removed and because of that this module fails with the default `encrypted=no`. Also encrypted passwords are suported since version 7.2 (https://www.postgresql.org/docs/7.2/static/sql-createuser.html) which went EOL in 2007 and since 7.3 it is the default. Because of this it makes a lot more sense to make `encrypted=yes` the default. This won't break backward compatibility, the module would just update the user's password in the DB in the hashed format and everything else will work like before. It's also a security bad practice to store passwords in plain text. fixes #25823 * There was also a bug with `encrypted=yes` and an empty password always reported as changed. * Improved documentation for `encrypted`/`password` parameters, and removed some obsolete notes about passlib. * Fix clearing user's password to work with all versions of Postgres * Add tests for clearing the user password * Fix documentation atfer rebase * Add changelog fragment
This commit is contained in:
parent
062f0444a1
commit
32d6a354d7
5 changed files with 169 additions and 44 deletions
|
@ -19,6 +19,11 @@ See [Porting Guide](https://docs.ansible.com/ansible/devel/porting_guides/portin
|
||||||
* Removed restriction from protocol in cloudflare_dns module to allow other protocols than tcp and udp to be specified.
|
* Removed restriction from protocol in cloudflare_dns module to allow other protocols than tcp and udp to be specified.
|
||||||
|
|
||||||
* Ansible 2.6 and onwards, `target_id` parameter in `vmware_target_canonical_facts` module is an optional parameter.
|
* Ansible 2.6 and onwards, `target_id` parameter in `vmware_target_canonical_facts` module is an optional parameter.
|
||||||
|
* `postgresql_user` module changed `encrypted=yes` to be the default. This
|
||||||
|
shouldn't break any current playbooks, the module will just store passwords
|
||||||
|
hashed by default. This change was done because Postgres 10 dropped support for
|
||||||
|
`UNENCRYPTED` passwords and because all versions since Postgres 7.2 support
|
||||||
|
storing encrypted passwords.
|
||||||
|
|
||||||
#### Removed modules (previously deprecated)
|
#### Removed modules (previously deprecated)
|
||||||
|
|
||||||
|
|
7
changelogs/fragments/postgresql_user-encrypted-fixes.yml
Normal file
7
changelogs/fragments/postgresql_user-encrypted-fixes.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
minor_changes:
|
||||||
|
- "`postgresql_user` module changed `encrypted=yes` to be the default. This
|
||||||
|
shouldn't break any current playbooks, the module will just store passwords
|
||||||
|
hashed by default. This change was done because Postgres 10 dropped support for
|
||||||
|
`UNENCRYPTED` passwords and because all versions since Postgres 7.2 support
|
||||||
|
storing encrypted passwords."
|
|
@ -7,9 +7,11 @@ from __future__ import absolute_import, division, print_function
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
ANSIBLE_METADATA = {
|
||||||
'status': ['stableinterface'],
|
'metadata_version': '1.1',
|
||||||
'supported_by': 'community'}
|
'status': ['stableinterface'],
|
||||||
|
'supported_by': 'community'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
DOCUMENTATION = '''
|
||||||
|
@ -34,60 +36,70 @@ version_added: "0.6"
|
||||||
options:
|
options:
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- name of the user (role) to add or remove
|
- Name of the user (role) to add or remove.
|
||||||
required: true
|
required: true
|
||||||
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.
|
||||||
- >
|
- Password can be passed unhashed or hashed (MD5-hashed).
|
||||||
When passing an encrypted password, the encrypted parameter must also be true, and it must be generated with the format
|
- Unhashed password will automatically be hashed when saved into the
|
||||||
C('str[\\"md5\\"] + md5[ password + username ]'), resulting in a total of 35 characters. An easy way to do this is:
|
database if C(encrypted) parameter is set, otherwise it will be save in
|
||||||
C(echo \\"md5`echo -n \\"verysecretpasswordJOE\\" | md5`\\"). Note that if the provided password string is already in
|
plain text format.
|
||||||
MD5-hashed format, then it is used as-is, regardless of encrypted parameter.
|
- When passing a hashed password 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' | md5sum)").
|
||||||
|
- Note that if the provided password string is already in MD5-hashed
|
||||||
|
format, then it is used as-is, regardless of C(encrypted) parameter.
|
||||||
db:
|
db:
|
||||||
description:
|
description:
|
||||||
- name of database where permissions will be granted
|
- Name of database where permissions will be granted.
|
||||||
fail_on_user:
|
fail_on_user:
|
||||||
description:
|
description:
|
||||||
- if C(yes), fail when user can't be removed. Otherwise just log and continue
|
- If C(yes), fail when user can't be removed. Otherwise just log and
|
||||||
type: bool
|
continue.
|
||||||
default: 'yes'
|
default: 'yes'
|
||||||
|
type: bool
|
||||||
port:
|
port:
|
||||||
description:
|
description:
|
||||||
- Database port to connect to.
|
- Database port to connect to.
|
||||||
default: 5432
|
default: 5432
|
||||||
login_user:
|
login_user:
|
||||||
description:
|
description:
|
||||||
- User (role) used to authenticate with PostgreSQL
|
- User (role) used to authenticate with PostgreSQL.
|
||||||
default: postgres
|
default: postgres
|
||||||
login_password:
|
login_password:
|
||||||
description:
|
description:
|
||||||
- Password used to authenticate with PostgreSQL
|
- Password used to authenticate with PostgreSQL.
|
||||||
login_host:
|
login_host:
|
||||||
description:
|
description:
|
||||||
- Host running PostgreSQL.
|
- Host running PostgreSQL.
|
||||||
default: localhost
|
default: localhost
|
||||||
login_unix_socket:
|
login_unix_socket:
|
||||||
description:
|
description:
|
||||||
- Path to a Unix domain socket for local connections
|
- Path to a Unix domain socket for local connections.
|
||||||
priv:
|
priv:
|
||||||
description:
|
description:
|
||||||
- "PostgreSQL privileges string in the format: C(table:priv1,priv2)"
|
- "PostgreSQL privileges string in the format: C(table:priv1,priv2)."
|
||||||
role_attr_flags:
|
role_attr_flags:
|
||||||
description:
|
description:
|
||||||
- "PostgreSQL role attributes string in the format: CREATEDB,CREATEROLE,SUPERUSER"
|
- "PostgreSQL role attributes string in the format: CREATEDB,CREATEROLE,SUPERUSER."
|
||||||
- Note that '[NO]CREATEUSER' is deprecated.
|
- Note that '[NO]CREATEUSER' is deprecated.
|
||||||
default: ""
|
choices: ["[NO]SUPERUSER", "[NO]CREATEROLE", "[NO]CREATEDB", "[NO]INHERIT", "[NO]LOGIN", "[NO]REPLICATION", "[NO]BYPASSRLS"]
|
||||||
choices: [ "[NO]SUPERUSER", "[NO]CREATEROLE", "[NO]CREATEDB", "[NO]INHERIT", "[NO]LOGIN", "[NO]REPLICATION", "[NO]BYPASSRLS" ]
|
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- The user (role) state
|
- The user (role) state.
|
||||||
default: present
|
default: present
|
||||||
choices: [ present, absent ]
|
choices: ["present", "absent"]
|
||||||
encrypted:
|
encrypted:
|
||||||
description:
|
description:
|
||||||
- whether the password is stored hashed in the database. boolean. Passwords can be passed already hashed or unhashed, and postgresql ensures the
|
- Whether the password is stored hashed in the database. Passwords can be
|
||||||
stored password is hashed when encrypted is set.
|
passed already hashed or unhashed, and postgresql ensures the stored
|
||||||
|
password is hashed when C(encrypted) is set.
|
||||||
|
- "Note: Postgresql 10 and newer doesn't support unhashed passwords."
|
||||||
|
- Previous to Ansible 2.6, this was C(no) by default.
|
||||||
|
default: 'yes'
|
||||||
|
type: bool
|
||||||
version_added: '1.4'
|
version_added: '1.4'
|
||||||
expires:
|
expires:
|
||||||
description:
|
description:
|
||||||
|
@ -97,22 +109,26 @@ options:
|
||||||
version_added: '1.4'
|
version_added: '1.4'
|
||||||
no_password_changes:
|
no_password_changes:
|
||||||
description:
|
description:
|
||||||
- if C(yes), don't inspect database for password changes. Effective when C(pg_authid) is not accessible (such as AWS RDS). Otherwise, make
|
- If C(yes), don't inspect database for password changes. Effective when
|
||||||
|
C(pg_authid) is not accessible (such as AWS RDS). Otherwise, make
|
||||||
password changes as necessary.
|
password changes as necessary.
|
||||||
type: bool
|
|
||||||
default: 'no'
|
default: 'no'
|
||||||
|
type: bool
|
||||||
version_added: '2.0'
|
version_added: '2.0'
|
||||||
ssl_mode:
|
ssl_mode:
|
||||||
description:
|
description:
|
||||||
- Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server.
|
- Determines whether or with what priority a secure SSL TCP/IP connection
|
||||||
- See https://www.postgresql.org/docs/current/static/libpq-ssl.html for more information on the modes.
|
will be negotiated with the server.
|
||||||
|
- See U(https://www.postgresql.org/docs/current/static/libpq-ssl.html) for
|
||||||
|
more information on the modes.
|
||||||
- Default of C(prefer) matches libpq default.
|
- Default of C(prefer) matches libpq default.
|
||||||
default: prefer
|
default: prefer
|
||||||
choices: [disable, allow, prefer, require, verify-ca, verify-full]
|
choices: ["disable", "allow", "prefer", "require", "verify-ca", "verify-full"]
|
||||||
version_added: '2.3'
|
version_added: '2.3'
|
||||||
ssl_rootcert:
|
ssl_rootcert:
|
||||||
description:
|
description:
|
||||||
- Specifies the name of a file containing SSL certificate authority (CA) certificate(s). If the file exists, the server's certificate will be
|
- Specifies the name of a file containing SSL certificate authority (CA)
|
||||||
|
certificate(s). If the file exists, the server's certificate will be
|
||||||
verified to be signed by one of these authorities.
|
verified to be signed by one of these authorities.
|
||||||
version_added: '2.3'
|
version_added: '2.3'
|
||||||
conn_limit:
|
conn_limit:
|
||||||
|
@ -128,14 +144,11 @@ 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.
|
||||||
- The ssl_rootcert parameter requires at least Postgres version 8.4 and I(psycopg2) version 2.4.3.
|
- The ssl_rootcert parameter requires at least Postgres version 8.4 and
|
||||||
|
I(psycopg2) version 2.4.3.
|
||||||
requirements: [ psycopg2 ]
|
requirements: [ psycopg2 ]
|
||||||
author: "Ansible Core Team"
|
author: "Ansible Core Team"
|
||||||
'''
|
'''
|
||||||
|
@ -148,10 +161,11 @@ EXAMPLES = '''
|
||||||
password: ceec4eif7ya
|
password: ceec4eif7ya
|
||||||
priv: "CONNECT/products:ALL"
|
priv: "CONNECT/products:ALL"
|
||||||
|
|
||||||
# Create rails user, grant privilege to create other databases and demote rails from super user status
|
# Create rails user, set its password (MD5-hashed) and grant privilege to create other
|
||||||
|
# databases and demote rails from super user status
|
||||||
- postgresql_user:
|
- postgresql_user:
|
||||||
name: rails
|
name: rails
|
||||||
password: secret
|
password: md59543f1d82624df2b31672ec0f7050460
|
||||||
role_attr_flags: CREATEDB,NOSUPERUSER
|
role_attr_flags: CREATEDB,NOSUPERUSER
|
||||||
|
|
||||||
# Remove test user privileges from acme
|
# Remove test user privileges from acme
|
||||||
|
@ -184,7 +198,7 @@ EXAMPLES = '''
|
||||||
- postgresql_user:
|
- postgresql_user:
|
||||||
db: test
|
db: test
|
||||||
user: test
|
user: test
|
||||||
password: NULL
|
password: ""
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -248,7 +262,7 @@ def user_add(cursor, user, password, role_attr_flags, encrypted, expires, conn_l
|
||||||
query_password_data = dict(password=password, expires=expires)
|
query_password_data = dict(password=password, expires=expires)
|
||||||
query = ['CREATE USER %(user)s' %
|
query = ['CREATE USER %(user)s' %
|
||||||
{"user": pg_quote_identifier(user, 'role')}]
|
{"user": pg_quote_identifier(user, 'role')}]
|
||||||
if password is not None:
|
if password is not None and password != '':
|
||||||
query.append("WITH %(crypt)s" % {"crypt": encrypted})
|
query.append("WITH %(crypt)s" % {"crypt": encrypted})
|
||||||
query.append("PASSWORD %(password)s")
|
query.append("PASSWORD %(password)s")
|
||||||
if expires is not None:
|
if expires is not None:
|
||||||
|
@ -277,11 +291,16 @@ def user_should_we_change_password(current_role_attrs, user, password, encrypted
|
||||||
# Do we actually need to do anything?
|
# Do we actually need to do anything?
|
||||||
pwchanging = False
|
pwchanging = False
|
||||||
if password is not None:
|
if password is not None:
|
||||||
|
# Empty password means that the role shouldn't have a password, which
|
||||||
|
# means we need to check if the current password is None.
|
||||||
|
if password == '':
|
||||||
|
if current_role_attrs['rolpassword'] is not None:
|
||||||
|
pwchanging = True
|
||||||
# 32: MD5 hashes are represented as a sequence of 32 hexadecimal digits
|
# 32: MD5 hashes are represented as a sequence of 32 hexadecimal digits
|
||||||
# 3: The size of the 'md5' prefix
|
# 3: The size of the 'md5' prefix
|
||||||
# When the provided password looks like a MD5-hash, value of
|
# When the provided password looks like a MD5-hash, value of
|
||||||
# 'encrypted' is ignored.
|
# 'encrypted' is ignored.
|
||||||
if ((password.startswith('md5') and len(password) == 32 + 3) or encrypted == 'UNENCRYPTED'):
|
elif (password.startswith('md5') and len(password) == 32 + 3) or encrypted == 'UNENCRYPTED':
|
||||||
if password != current_role_attrs['rolpassword']:
|
if password != current_role_attrs['rolpassword']:
|
||||||
pwchanging = True
|
pwchanging = True
|
||||||
elif encrypted == 'ENCRYPTED':
|
elif encrypted == 'ENCRYPTED':
|
||||||
|
@ -360,8 +379,11 @@ def user_alter(db_connection, module, user, password, role_attr_flags, encrypted
|
||||||
|
|
||||||
alter = ['ALTER USER %(user)s' % {"user": pg_quote_identifier(user, 'role')}]
|
alter = ['ALTER USER %(user)s' % {"user": pg_quote_identifier(user, 'role')}]
|
||||||
if pwchanging:
|
if pwchanging:
|
||||||
alter.append("WITH %(crypt)s" % {"crypt": encrypted})
|
if password != '':
|
||||||
alter.append("PASSWORD %(password)s")
|
alter.append("WITH %(crypt)s" % {"crypt": encrypted})
|
||||||
|
alter.append("PASSWORD %(password)s")
|
||||||
|
else:
|
||||||
|
alter.append("WITH PASSWORD NULL")
|
||||||
alter.append(role_attr_flags)
|
alter.append(role_attr_flags)
|
||||||
elif role_attr_flags:
|
elif role_attr_flags:
|
||||||
alter.append('WITH %s' % role_attr_flags)
|
alter.append('WITH %s' % role_attr_flags)
|
||||||
|
@ -715,7 +737,7 @@ def main():
|
||||||
port=dict(default='5432'),
|
port=dict(default='5432'),
|
||||||
fail_on_user=dict(type='bool', default='yes'),
|
fail_on_user=dict(type='bool', default='yes'),
|
||||||
role_attr_flags=dict(default=''),
|
role_attr_flags=dict(default=''),
|
||||||
encrypted=dict(type='bool', default='no'),
|
encrypted=dict(type='bool', default='yes'),
|
||||||
no_password_changes=dict(type='bool', default='no'),
|
no_password_changes=dict(type='bool', default='no'),
|
||||||
expires=dict(default=None),
|
expires=dict(default=None),
|
||||||
ssl_mode=dict(default='prefer', choices=[
|
ssl_mode=dict(default='prefer', choices=[
|
||||||
|
|
|
@ -161,6 +161,52 @@
|
||||||
|
|
||||||
- <<: *changed
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using MD5-hashed password: check that password changed when clearing the password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: 'yes'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using MD5-hashed password: check that password not changed when clearing the password again'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: 'yes'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed
|
||||||
|
|
||||||
|
- <<: *not_changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password: check that password not changed when clearing the password again'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: 'no'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed
|
||||||
|
|
||||||
|
- <<: *not_changed
|
||||||
|
|
||||||
|
- name: 'Using MD5-hashed password: check that password changed when using a cleartext password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: '{{ db_password1 }}'
|
||||||
|
encrypted: 'yes'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
when: encrypted == 'yes'
|
when: encrypted == 'yes'
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
|
@ -201,6 +247,52 @@
|
||||||
|
|
||||||
- <<: *changed
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password: check that password changed when clearing the password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: 'no'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password: check that password not changed when clearing the password again'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: 'no'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed
|
||||||
|
|
||||||
|
- <<: *not_changed
|
||||||
|
|
||||||
|
- name: 'Using MD5-hashed password: check that password not changed when clearing the password again'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: ''
|
||||||
|
encrypted: 'yes'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
PGOPTIONS: '-c default_transaction_read_only=on' # ensure 'alter user' query isn't executed
|
||||||
|
|
||||||
|
- <<: *not_changed
|
||||||
|
|
||||||
|
- name: 'Using cleartext password: check that password changed when using cleartext password'
|
||||||
|
<<: *task_parameters
|
||||||
|
postgresql_user:
|
||||||
|
<<: *parameters
|
||||||
|
password: "{{ db_password1 }}"
|
||||||
|
encrypted: 'no'
|
||||||
|
environment:
|
||||||
|
PGCLIENTENCODING: 'UTF8'
|
||||||
|
|
||||||
|
- <<: *changed
|
||||||
|
|
||||||
when: encrypted == 'no'
|
when: encrypted == 'no'
|
||||||
|
|
||||||
- name: Remove user
|
- name: Remove user
|
||||||
|
|
|
@ -643,7 +643,6 @@ lib/ansible/modules/database/postgresql/postgresql_schema.py E322
|
||||||
lib/ansible/modules/database/postgresql/postgresql_schema.py E324
|
lib/ansible/modules/database/postgresql/postgresql_schema.py E324
|
||||||
lib/ansible/modules/database/postgresql/postgresql_user.py E322
|
lib/ansible/modules/database/postgresql/postgresql_user.py E322
|
||||||
lib/ansible/modules/database/postgresql/postgresql_user.py E324
|
lib/ansible/modules/database/postgresql/postgresql_user.py E324
|
||||||
lib/ansible/modules/database/postgresql/postgresql_user.py E325
|
|
||||||
lib/ansible/modules/database/postgresql/postgresql_user.py E326
|
lib/ansible/modules/database/postgresql/postgresql_user.py E326
|
||||||
lib/ansible/modules/database/proxysql/proxysql_backend_servers.py E322
|
lib/ansible/modules/database/proxysql/proxysql_backend_servers.py E322
|
||||||
lib/ansible/modules/database/proxysql/proxysql_backend_servers.py E325
|
lib/ansible/modules/database/proxysql/proxysql_backend_servers.py E325
|
||||||
|
|
Loading…
Reference in a new issue