Skip to content

Commit

Permalink
Allow setting/unsetting BYPASSRLS Postgres role attribute (#24625)
Browse files Browse the repository at this point in the history
* Allow setting/unsetting BYPASSRLS role attr

* Build valid role attrs against version

* Add integration tests
  • Loading branch information
herrewig authored and bcoca committed May 26, 2017
1 parent 6033668 commit 723c8f0
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 93 deletions.
62 changes: 50 additions & 12 deletions lib/ansible/modules/database/postgresql/postgresql_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
required: false
default: ""
choices: [ "[NO]SUPERUSER","[NO]CREATEROLE", "[NO]CREATEUSER", "[NO]CREATEDB",
"[NO]INHERIT", "[NO]LOGIN", "[NO]REPLICATION" ]
"[NO]INHERIT", "[NO]LOGIN", "[NO]REPLICATION", "[NO]BYPASSRLS" ]
state:
description:
- The user (role) state
Expand Down Expand Up @@ -210,6 +210,8 @@
import re
import itertools

from distutils.version import StrictVersion

try:
import psycopg2
import psycopg2.extras
Expand All @@ -220,7 +222,7 @@
from ansible.module_utils.six import iteritems

_flags = ('SUPERUSER', 'CREATEROLE', 'CREATEUSER', 'CREATEDB', 'INHERIT', 'LOGIN', 'REPLICATION')
VALID_FLAGS = frozenset(itertools.chain(_flags, ('NO%s' % f for f in _flags)))
_flags_by_version = {'BYPASSRLS': '9.5.0'}

VALID_PRIVS = dict(table=frozenset(('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'TRUNCATE', 'REFERENCES', 'TRIGGER', 'ALL')),
database=frozenset(('CREATE', 'CONNECT', 'TEMPORARY', 'TEMP', 'ALL')),
Expand All @@ -230,7 +232,7 @@
PRIV_TO_AUTHID_COLUMN = dict(SUPERUSER='rolsuper', CREATEROLE='rolcreaterole',
CREATEUSER='rolcreateuser', CREATEDB='rolcreatedb',
INHERIT='rolinherit', LOGIN='rolcanlogin',
REPLICATION='rolreplication')
REPLICATION='rolreplication', BYPASSRLS='rolbypassrls')

class InvalidFlagsError(Exception):
pass
Expand Down Expand Up @@ -558,7 +560,7 @@ def grant_privileges(cursor, user, privs):
changed = True
return changed

def parse_role_attrs(role_attr_flags):
def parse_role_attrs(cursor, role_attr_flags):
"""
Parse role attributes string for user creation.
Format:
Expand All @@ -569,18 +571,24 @@ def parse_role_attrs(role_attr_flags):
attributes := CREATEDB,CREATEROLE,NOSUPERUSER,...
[ "[NO]SUPERUSER","[NO]CREATEROLE", "[NO]CREATEUSER", "[NO]CREATEDB",
"[NO]INHERIT", "[NO]LOGIN", "[NO]REPLICATION" ]
"[NO]INHERIT", "[NO]LOGIN", "[NO]REPLICATION",
"[NO]BYPASSRLS" ]
Note: "[NO]BYPASSRLS" role attribute introduced in 9.5
"""
flags = frozenset(itertools.chain(_flags, get_valid_flags_by_version(cursor)))
valid_flags = frozenset(itertools.chain(flags, ('NO%s' % f for f in flags)))

if ',' in role_attr_flags:
flag_set = frozenset(r.upper() for r in role_attr_flags.split(","))
elif role_attr_flags:
flag_set = frozenset((role_attr_flags.upper(),))
else:
flag_set = frozenset()
if not flag_set.issubset(VALID_FLAGS):
if not flag_set.issubset(valid_flags):
raise InvalidFlagsError('Invalid role_attr_flags specified: %s' %
' '.join(flag_set.difference(VALID_FLAGS)))
' '.join(flag_set.difference(valid_flags)))
o_flags = ' '.join(flag_set)
return o_flags

Expand Down Expand Up @@ -633,6 +641,35 @@ def parse_privs(privs, db):

return o_privs

def get_pg_server_version(cursor):
"""
Queries Postgres for its server version.
server_version should be just the server version itself:
postgres=# SHOW SERVER_VERSION;
server_version
----------------
9.6.2
(1 row)
"""
cursor.execute("SHOW SERVER_VERSION")
return cursor.fetchone()['server_version']

def get_valid_flags_by_version(cursor):
"""
Some role attributes were introduced after certain versions. We want to
compile a list of valid flags against the current Postgres version.
"""
current_version = StrictVersion(get_pg_server_version(cursor))

return [
flag
for flag, version_introduced in _flags_by_version.items()
if current_version >= StrictVersion(version_introduced)
]


# ===========================================
# Module execution.
#
Expand Down Expand Up @@ -671,11 +708,6 @@ def main():
privs = parse_privs(module.params["priv"], db)
port = module.params["port"]
no_password_changes = module.params["no_password_changes"]
try:
role_attr_flags = parse_role_attrs(module.params["role_attr_flags"])
except InvalidFlagsError:
e = get_exception()
module.fail_json(msg=str(e))
if module.params["encrypted"]:
encrypted = "ENCRYPTED"
else:
Expand Down Expand Up @@ -723,6 +755,12 @@ def main():
e = get_exception()
module.fail_json(msg="unable to connect to database: %s" % e)

try:
role_attr_flags = parse_role_attrs(cursor, module.params["role_attr_flags"])
except InvalidFlagsError:
e = get_exception()
module.fail_json(msg=str(e))

kw = dict(user=user)
changed = False
user_removed = False
Expand Down
97 changes: 16 additions & 81 deletions test/integration/targets/postgresql/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,92 +268,27 @@
that:
- "result.changed == False"

- name: Create a user with all role attributes
# BYPASSRLS role attribute was introduced in Postgres 9.5, so
# we want to test atrribute management differently depending
# on the version. See https://github.com/ansible/ansible/pull/24625
# for more details.
- name: Get Postgres version
become_user: "{{ pg_user }}"
become: True
postgresql_user:
name: "{{ db_user1 }}"
state: "present"
role_attr_flags: "SUPERUSER,CREATEROLE,CREATEDB,INHERIT,login"
login_user: "{{ pg_user }}"
db: postgres

- name: Check that the user has the requested role attributes
become_user: "{{ pg_user }}"
become: True
shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres
register: result

- assert:
that:
- "result.stdout_lines[-1] == '(1 row)'"
- "'super:t' in result.stdout_lines[-2]"
- "'createrole:t' in result.stdout_lines[-2]"
- "'create:t' in result.stdout_lines[-2]"
- "'inherit:t' in result.stdout_lines[-2]"
- "'login:t' in result.stdout_lines[-2]"

- name: Modify a user to have no role attributes
become_user: "{{ pg_user }}"
become: True
postgresql_user:
name: "{{ db_user1 }}"
state: "present"
role_attr_flags: "NOSUPERUSER,NOCREATEROLE,NOCREATEDB,noinherit,NOLOGIN"
login_user: "{{ pg_user }}"
db: postgres
register: result

- name: Check that ansible reports it modified the role
assert:
that:
- "result.changed == True"

- name: Check that the user has the requested role attributes
become_user: "{{ pg_user }}"
become: True
shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres
register: result

- assert:
that:
- "result.stdout_lines[-1] == '(1 row)'"
- "'super:f' in result.stdout_lines[-2]"
- "'createrole:f' in result.stdout_lines[-2]"
- "'create:f' in result.stdout_lines[-2]"
- "'inherit:f' in result.stdout_lines[-2]"
- "'login:f' in result.stdout_lines[-2]"
shell: echo 'SHOW SERVER_VERSION' | psql -d postgres
register: postgres_version_resp

- name: Modify a single role attribute on a user
become_user: "{{ pg_user }}"
become: True
postgresql_user:
name: "{{ db_user1 }}"
state: "present"
role_attr_flags: "LOGIN"
login_user: "{{ pg_user }}"
db: postgres
register: result

- name: Check that ansible reports it modified the role
assert:
that:
- "result.changed == True"
- name: Print Postgres server version
debug:
msg: "{{ postgres_version_resp.stdout_lines[-2] | trim }}"

- name: Check that the user has the requested role attributes
become_user: "{{ pg_user }}"
become: True
shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres
register: result
- name: Role attribute testing for Postgres 9.5+
include: postgresql_user_9.5_or_greater.yml
when: (postgres_version_resp.stdout_lines[-2] | trim) | version_compare('9.5.0', '>=')

- assert:
that:
- "result.stdout_lines[-1] == '(1 row)'"
- "'super:f' in result.stdout_lines[-2]"
- "'createrole:f' in result.stdout_lines[-2]"
- "'create:f' in result.stdout_lines[-2]"
- "'inherit:f' in result.stdout_lines[-2]"
- "'login:t' in result.stdout_lines[-2]"
- name: Role attribute testing for Postgres versions below 9.5
include: postgresql_user_less_than_9.5.yml
when: (postgres_version_resp.stdout_lines[-2] | trim) | version_compare('9.5.0', '<')

- name: Cleanup the user
become_user: "{{ pg_user }}"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
- name: Create a user with all role attributes
become_user: "{{ pg_user }}"
become: True
postgresql_user:
name: "{{ db_user1 }}"
state: "present"
role_attr_flags: "SUPERUSER,CREATEROLE,CREATEDB,INHERIT,login,BYPASSRLS"
login_user: "{{ pg_user }}"
db: postgres

- name: Check that the user has the requested role attributes
become_user: "{{ pg_user }}"
become: True
shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin, 'bypassrls:'||rolbypassrls from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres
register: result

- assert:
that:
- "result.stdout_lines[-1] == '(1 row)'"
- "'super:t' in result.stdout_lines[-2]"
- "'createrole:t' in result.stdout_lines[-2]"
- "'create:t' in result.stdout_lines[-2]"
- "'inherit:t' in result.stdout_lines[-2]"
- "'login:t' in result.stdout_lines[-2]"
- "'bypassrls:t' in result.stdout_lines[-2]"

- name: Modify a user to have no role attributes
become_user: "{{ pg_user }}"
become: True
postgresql_user:
name: "{{ db_user1 }}"
state: "present"
role_attr_flags: "NOSUPERUSER,NOCREATEROLE,NOCREATEDB,noinherit,NOLOGIN,NOBYPASSRLS"
login_user: "{{ pg_user }}"
db: postgres
register: result

- name: Check that ansible reports it modified the role
assert:
that:
- "result.changed == True"

- name: Check that the user has the requested role attributes
become_user: "{{ pg_user }}"
become: True
shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin, 'bypassrls:'||rolbypassrls from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres
register: result

- assert:
that:
- "result.stdout_lines[-1] == '(1 row)'"
- "'super:f' in result.stdout_lines[-2]"
- "'createrole:f' in result.stdout_lines[-2]"
- "'create:f' in result.stdout_lines[-2]"
- "'inherit:f' in result.stdout_lines[-2]"
- "'login:f' in result.stdout_lines[-2]"
- "'bypassrls:f' in result.stdout_lines[-2]"

- name: Modify a single role attribute on a user
become_user: "{{ pg_user }}"
become: True
postgresql_user:
name: "{{ db_user1 }}"
state: "present"
role_attr_flags: "LOGIN"
login_user: "{{ pg_user }}"
db: postgres
register: result

- name: Check that ansible reports it modified the role
assert:
that:
- "result.changed == True"

- name: Check that the user has the requested role attributes
become_user: "{{ pg_user }}"
become: True
shell: echo "select 'super:'||rolsuper, 'createrole:'||rolcreaterole, 'create:'||rolcreatedb, 'inherit:'||rolinherit, 'login:'||rolcanlogin, 'bypassrls:'||rolbypassrls from pg_roles where rolname='{{ db_user1 }}';" | psql -d postgres
register: result

- assert:
that:
- "result.stdout_lines[-1] == '(1 row)'"
- "'super:f' in result.stdout_lines[-2]"
- "'createrole:f' in result.stdout_lines[-2]"
- "'create:f' in result.stdout_lines[-2]"
- "'inherit:f' in result.stdout_lines[-2]"
- "'login:t' in result.stdout_lines[-2]"
- "'bypassrls:f' in result.stdout_lines[-2]"

0 comments on commit 723c8f0

Please sign in to comment.