From 8423d78de19df8eae66acddba154ef67bcc80be2 Mon Sep 17 00:00:00 2001 From: George Hansper Date: Thu, 14 Sep 2017 14:57:22 +1000 Subject: [PATCH] postgresql::server::grant with ensure => absent uses REVOKE instead of GRANT --- README.md | 30 ++++ manifests/server/database_grant.pp | 2 + manifests/server/grant.pp | 219 +++++++++++++++-------- manifests/server/table_grant.pp | 4 +- spec/acceptance/server/grant_spec.rb | 231 ++++++++++++++++++++++++- spec/unit/defines/server/grant_spec.rb | 4 +- 6 files changed, 409 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 5dfe275468..412beec5af 100644 --- a/README.md +++ b/README.md @@ -1139,6 +1139,16 @@ Default value: 'template0'. Manages grant-based access privileges for users, wrapping the `postgresql::server::database_grant` for database specific permissions. Consult the [PostgreSQL documentation for `grant`](http://www.postgresql.org/docs/current/static/sql-grant.html) for more information. +##### `ensure` + +Specifies whether to grant or revoke the privilege. Default is to grant the privilege. + +Valid values: 'present', 'absent'. +* 'present' to grant the privilege +* 'absent' to revoke the privilege + +Default value: 'present'. + #### `connect_settings` Specifies a hash of environment variables used when connecting to a remote server. @@ -1205,6 +1215,16 @@ By default, the package specified with `package_name` is installed when the exte Manages grant-based access privileges for roles. See [PostgreSQL documentation for `grant`](http://www.postgresql.org/docs/current/static/sql-grant.html) for more information. +##### `ensure` + +Specifies whether to grant or revoke the privilege. Default is to grant the privilege. + +Valid values: 'present', 'absent'. +* 'present' to grant the privilege +* 'absent' to revoke the privilege + +Default value: 'present'. + ##### `db` Specifies the database to which you are granting access. @@ -1553,6 +1573,16 @@ Default value: the namevar. Manages grant-based access privileges for users. Consult the PostgreSQL documentation for `grant` for more information. +##### `ensure` + +Specifies whether to grant or revoke the privilege. Default is to grant the privilege. + +Valid values: 'present', 'absent'. +* 'present' to grant the privilege +* 'absent' to revoke the privilege + +Default value: 'present'. + ##### `connect_settings` Specifies a hash of environment variables used when connecting to a remote server. diff --git a/manifests/server/database_grant.pp b/manifests/server/database_grant.pp index 6c29b57176..34a69533e5 100644 --- a/manifests/server/database_grant.pp +++ b/manifests/server/database_grant.pp @@ -3,11 +3,13 @@ $privilege, $db, $role, + $ensure = undef, $psql_db = undef, $psql_user = undef, $connect_settings = undef, ) { postgresql::server::grant { "database:${name}": + ensure => $ensure, role => $role, db => $db, privilege => $privilege, diff --git a/manifests/server/grant.pp b/manifests/server/grant.pp index ecc7a76f7e..579acab4ef 100644 --- a/manifests/server/grant.pp +++ b/manifests/server/grant.pp @@ -27,8 +27,23 @@ Integer $port = $postgresql::server::port, Boolean $onlyif_exists = false, Hash $connect_settings = $postgresql::server::default_connect_settings, + Enum['present', + 'absent' + ] $ensure = 'present', ) { + case $ensure { + default: { + # default is 'present' + $sql_command = 'GRANT %s ON %s "%s" TO "%s"' + $unless_is = true + } + 'absent': { + $sql_command = 'REVOKE %s ON %s "%s" FROM "%s"' + $unless_is = false + } + } + $group = $postgresql::server::group $psql_path = $postgresql::server::psql_path @@ -81,7 +96,10 @@ } $unless_function = 'has_database_privilege' $on_db = $psql_db - $onlyif_function = undef + $onlyif_function = $ensure ? { + default => undef, + 'absent' => 'role_exists', + } } 'SCHEMA': { $unless_privilege = $_privilege ? { @@ -149,43 +167,78 @@ # If this number is not zero then there is at least one sequence for which # the role does not have the specified privilege, making it necessary to # execute the GRANT statement. - $custom_unless = "SELECT 1 FROM ( - SELECT sequence_name - FROM information_schema.sequences - WHERE sequence_schema='${schema}' - EXCEPT DISTINCT - SELECT object_name as sequence_name - FROM ( - SELECT object_schema, - object_name, - grantee, - CASE privs_split - WHEN 'r' THEN 'SELECT' - WHEN 'w' THEN 'UPDATE' - WHEN 'U' THEN 'USAGE' - END AS privilege_type - FROM ( - SELECT DISTINCT - object_schema, - object_name, - (regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[1] AS grantee, - regexp_split_to_table((regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[2],E'\\s*') AS privs_split - FROM ( - SELECT n.nspname as object_schema, - c.relname as object_name, - regexp_split_to_table(array_to_string(c.relacl,','),',') AS privs - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid - WHERE c.relkind = 'S' - AND n.nspname NOT IN ( 'pg_catalog', 'information_schema' ) - ) P1 - ) P2 - ) P3 - WHERE grantee='${role}' - AND object_schema='${schema}' - AND privilege_type='${custom_privilege}' - ) P - HAVING count(P.sequence_name) = 0" + if $ensure == 'present' { + $custom_unless = "SELECT 1 WHERE NOT EXISTS ( + SELECT sequence_name + FROM information_schema.sequences + WHERE sequence_schema='${schema}' + EXCEPT DISTINCT + SELECT object_name as sequence_name + FROM ( + SELECT object_schema, + object_name, + grantee, + CASE privs_split + WHEN 'r' THEN 'SELECT' + WHEN 'w' THEN 'UPDATE' + WHEN 'U' THEN 'USAGE' + END AS privilege_type + FROM ( + SELECT DISTINCT + object_schema, + object_name, + (regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[1] AS grantee, + regexp_split_to_table((regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[2],E'\\s*') AS privs_split + FROM ( + SELECT n.nspname as object_schema, + c.relname as object_name, + regexp_split_to_table(array_to_string(c.relacl,','),',') AS privs + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relkind = 'S' + AND n.nspname NOT IN ( 'pg_catalog', 'information_schema' ) + ) P1 + ) P2 + ) P3 + WHERE grantee='${role}' + AND object_schema='${schema}' + AND privilege_type='${custom_privilege}' + )" + } else { + # ensure == absent + $custom_unless = "SELECT 1 WHERE NOT EXISTS ( + SELECT object_name as sequence_name + FROM ( + SELECT object_schema, + object_name, + grantee, + CASE privs_split + WHEN 'r' THEN 'SELECT' + WHEN 'w' THEN 'UPDATE' + WHEN 'U' THEN 'USAGE' + END AS privilege_type + FROM ( + SELECT DISTINCT + object_schema, + object_name, + (regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[1] AS grantee, + regexp_split_to_table((regexp_split_to_array(regexp_replace(privs,E'/.*',''),'='))[2],E'\\s*') AS privs_split + FROM ( + SELECT n.nspname as object_schema, + c.relname as object_name, + regexp_split_to_table(array_to_string(c.relacl,','),',') AS privs + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid + WHERE c.relkind = 'S' + AND n.nspname NOT IN ( 'pg_catalog', 'information_schema' ) + ) P1 + ) P2 + ) P3 + WHERE grantee='${role}' + AND object_schema='${schema}' + AND privilege_type='${custom_privilege}' + )" + } } 'TABLE': { $unless_privilege = $_privilege ? { @@ -233,37 +286,62 @@ $schema = $object_name # Again there seems to be no easy way in plain SQL to check if ALL - # PRIVILEGES are granted on a table. By convention we use INSERT - # here to represent ALL PRIVILEGES (truly terrible). - $custom_privilege = $_privilege ? { - 'ALL' => 'INSERT', - 'ALL PRIVILEGES' => 'INSERT', - default => $_privilege, + # PRIVILEGES are granted on a table. + # There are currently 7 possible priviliges: + # ('SELECT','UPDATE','INSERT','DELETE','TRIGGER','REFERENCES','TRUNCATE') + # This list is consistant from Postgresql 8.0 + # + # There are 4 cases to cover, each with it's own distinct unless clause: + # grant ALL + # grant SELECT (or INSERT or DELETE ...) + # revoke ALL + # revoke SELECT (or INSERT or DELETE ...) + + if $ensure == 'present' { + if $_privilege == 'ALL' or $_privilege == 'ALL PRIVILEGES' { + # GRANT ALL + $custom_unless = "SELECT 1 WHERE NOT EXISTS + ( SELECT 1 FROM pg_catalog.pg_tables AS t, + (VALUES ('SELECT'), ('UPDATE'), ('INSERT'), ('DELETE'), ('TRIGGER'), ('REFERENCES'), ('TRUNCATE')) AS p(privilege_type) + WHERE t.schemaname = '${schema}' + AND NOT EXISTS ( + SELECT 1 FROM information_schema.role_table_grants AS g + WHERE g.grantee = '${role}' + AND g.table_schema = '${schema}' + AND g.privilege_type = p.privilege_type + ) + )" + + } else { + # GRANT $_privilege + $custom_unless = "SELECT 1 WHERE NOT EXISTS + ( SELECT 1 FROM pg_catalog.pg_tables AS t + WHERE t.schemaname = '${schema}' + AND NOT EXISTS ( + SELECT 1 FROM information_schema.role_table_grants AS g + WHERE g.grantee = '${role}' + AND g.table_schema = '${schema}' + AND g.privilege_type = '${_privilege}' + ) + )" + } + } else { + if $_privilege == 'ALL' or $_privilege == 'ALL PRIVILEGES' { + # REVOKE ALL + $custom_unless = "SELECT 1 WHERE NOT EXISTS + ( SELECT table_name FROM information_schema.role_table_grants + WHERE grantee = '${role}' AND table_schema ='${schema}' + )" + } else { + # REVOKE $_privilege + $custom_unless = "SELECT 1 WHERE NOT EXISTS + ( SELECT table_name FROM information_schema.role_table_grants + WHERE grantee = '${role}' AND table_schema ='${schema}' + AND privilege_type = '${_privilege}' + )" + } } - # This checks if there is a difference between the tables in the - # specified schema and the tables for which the role has the specified - # privilege. It uses the EXCEPT clause which computes the set of rows - # that are in the result of the first SELECT statement but not in the - # result of the second one. It then counts the number of rows from this - # operation. If this number is zero then the role has the specified - # privilege for all tables in the schema and the whole query returns a - # single row, which satisfies the `unless` parameter of Postgresql_psql. - # If this number is not zero then there is at least one table for which - # the role does not have the specified privilege, making it necessary to - # execute the GRANT statement. - $custom_unless = "SELECT 1 FROM ( - SELECT table_name - FROM information_schema.tables - WHERE table_schema='${schema}' - EXCEPT DISTINCT - SELECT table_name - FROM information_schema.role_table_grants - WHERE grantee='${role}' - AND table_schema='${schema}' - AND privilege_type='${custom_privilege}' - ) P - HAVING count(P.table_name) = 0" } 'LANGUAGE': { $unless_privilege = $_privilege ? { @@ -313,17 +391,18 @@ false => undef, 'custom' => $custom_unless, default => "SELECT 1 WHERE ${unless_function}('${role}', - '${_granted_object}', '${unless_privilege}')", + '${_granted_object}', '${unless_privilege}') = ${unless_is}", } $_onlyif = $onlyif_function ? { 'table_exists' => "SELECT true FROM pg_tables WHERE tablename = '${_togrant_object}'", 'language_exists' => "SELECT true from pg_language WHERE lanname = '${_togrant_object}'", + 'role_exists' => "SELECT 1 FROM pg_roles WHERE rolname = '${role}'", default => undef, } - $grant_cmd = "GRANT ${_privilege} ON ${_object_type} \"${_togrant_object}\" TO - \"${role}\"" + $grant_cmd = sprintf($sql_command, $_privilege, $_object_type, $_togrant_object, $role) + postgresql_psql { "grant:${name}": command => $grant_cmd, db => $on_db, diff --git a/manifests/server/table_grant.pp b/manifests/server/table_grant.pp index 452f13d9de..dd70aeb8f4 100644 --- a/manifests/server/table_grant.pp +++ b/manifests/server/table_grant.pp @@ -5,6 +5,7 @@ $table, $db, $role, + $ensure = undef, $port = undef, $psql_db = undef, $psql_user = undef, @@ -12,6 +13,7 @@ $onlyif_exists = false, ) { postgresql::server::grant { "table:${name}": + ensure => $ensure, role => $role, db => $db, port => $port, @@ -23,4 +25,4 @@ onlyif_exists => $onlyif_exists, connect_settings => $connect_settings, } -} \ No newline at end of file +} diff --git a/spec/acceptance/server/grant_spec.rb b/spec/acceptance/server/grant_spec.rb index 63f2d7e47d..5266a11bb6 100644 --- a/spec/acceptance/server/grant_spec.rb +++ b/spec/acceptance/server/grant_spec.rb @@ -132,6 +132,7 @@ class { 'postgresql::server': } end end + ### SEQUENCE grants context 'sequence' do it 'should grant usage on a sequence to a user' do begin @@ -167,8 +168,8 @@ class { 'postgresql::server': } apply_manifest(pp, :catch_changes => true) ## Check that the privilege was granted to the user - psql("-d #{db} --command=\"SELECT 1 WHERE has_sequence_privilege('#{user}', 'test_seq', 'USAGE')\"", user) do |r| - expect(r.stdout).to match(/\(1 row\)/) + psql("-d #{db} --tuples-only --command=\"SELECT * FROM has_sequence_privilege('#{user}', 'test_seq', 'USAGE')\"", user) do |r| + expect(r.stdout).to match(/t/) expect(r.stderr).to eq('') end end @@ -209,8 +210,8 @@ class { 'postgresql::server': } apply_manifest(pp, :catch_changes => true) ## Check that the privilege was granted to the user - psql("-d #{db} --command=\"SELECT 1 WHERE has_sequence_privilege('#{user}', 'test_seq', 'UPDATE')\"", user) do |r| - expect(r.stdout).to match(/\(1 row\)/) + psql("-d #{db} --tuples-only --command=\"SELECT * FROM has_sequence_privilege('#{user}', 'test_seq', 'UPDATE')\"", user) do |r| + expect(r.stdout).to match(/t/) expect(r.stderr).to eq('') end end @@ -253,8 +254,8 @@ class { 'postgresql::server': } apply_manifest(pp, :catch_changes => true) ## Check that the privileges were granted to the user, this check is not available on version < 9.0 - psql("-d #{db} --command=\"SELECT 1 WHERE has_sequence_privilege('#{user}', 'test_seq2', 'USAGE') AND has_sequence_privilege('#{user}', 'test_seq3', 'USAGE')\"", user) do |r| - expect(r.stdout).to match(/\(1 row\)/) + psql("-d #{db} --tuples-only --command=\"SELECT has_sequence_privilege('#{user}', 'test_seq2', 'USAGE') AND has_sequence_privilege('#{user}', 'test_seq3', 'USAGE')\"", user) do |r| + expect(r.stdout).to match(/t/) expect(r.stderr).to eq('') end end @@ -295,12 +296,226 @@ class { 'postgresql::server': } apply_manifest(pp, :catch_changes => true) ## Check that the privileges were granted to the user - psql("-d #{db} --command=\"SELECT 1 WHERE has_sequence_privilege('#{user}', 'test_seq2', 'UPDATE') AND has_sequence_privilege('#{user}', 'test_seq3', 'UPDATE')\"", user) do |r| - expect(r.stdout).to match(/\(1 row\)/) + psql("-d #{db} --tuples-only --command=\"SELECT has_sequence_privilege('#{user}', 'test_seq2', 'UPDATE') AND has_sequence_privilege('#{user}', 'test_seq3', 'UPDATE')\"", user) do |r| + expect(r.stdout).to match(/t/) expect(r.stderr).to eq('') end end end end end + ### TABLE grants + context 'table' do + describe 'GRANT ... ON TABLE' do + let(:pp_create_table) { pp_setup + <<-EOS.unindent + postgresql_psql { 'create test table': + command => 'CREATE TABLE test_tbl (col1 integer)', + db => $db, + psql_user => $owner, + unless => "SELECT table_name FROM information_schema.tables WHERE table_name = 'test_tbl'", + require => Postgresql::Server::Database[$db], + } + EOS + } + + it 'should grant select on a table to a user' do + begin + pp = pp_create_table + <<-EOS.unindent + + postgresql::server::grant { 'grant select on test_tbl': + privilege => 'SELECT', + object_type => 'TABLE', + object_name => 'test_tbl', + db => $db, + role => $user, + require => [ Postgresql_psql['create test table'], + Postgresql::Server::Role[$user], ] + } + EOS + + pp_revoke = pp_create_table + <<-EOS.unindent + + postgresql::server::grant { 'revoke select on test_tbl': + ensure => absent, + privilege => 'SELECT', + object_type => 'TABLE', + object_name => 'test_tbl', + db => $db, + role => $user, + require => [ Postgresql_psql['create test table'], + Postgresql::Server::Role[$user], ] + } + EOS + + apply_manifest(pp_install, :catch_failures => true) + + #postgres version + result = shell('psql --version') + version = result.stdout.match(%r{\s(\d\.\d)})[1] + + if version >= '9.0' + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + + ## Check that the privilege was granted to the user + psql("-d #{db} --tuples-only --command=\"SELECT * FROM has_table_privilege('#{user}', 'test_tbl', 'SELECT')\"", user) do |r| + expect(r.stdout).to match(/t/) + expect(r.stderr).to eq('') + end + + apply_manifest(pp_revoke, :catch_failures => true) + apply_manifest(pp_revoke, :catch_changes => true) + + ## Check that the privilege was revoked from the user + psql("-d #{db} --tuples-only --command=\"SELECT * FROM has_table_privilege('#{user}', 'test_tbl', 'SELECT')\"", user) do |r| + expect(r.stdout).to match(/f/) + expect(r.stderr).to eq('') + end + end + end + end + + it 'should grant update on all tables to a user' do + begin + pp = pp_create_table + <<-EOS.unindent + + postgresql::server::grant { 'grant update on all tables': + privilege => 'UPDATE', + object_type => 'ALL TABLES IN SCHEMA', + object_name => 'public', + db => $db, + role => $user, + require => [ Postgresql_psql['create test table'], + Postgresql::Server::Role[$user], ] + } + EOS + + pp_revoke = pp_create_table + <<-EOS.unindent + + postgresql::server::grant { 'revoke update on all tables': + ensure => absent, + privilege => 'UPDATE', + object_type => 'ALL TABLES IN SCHEMA', + object_name => 'public', + db => $db, + role => $user, + require => [ Postgresql_psql['create test table'], + Postgresql::Server::Role[$user], ] + } + EOS + + apply_manifest(pp_install, :catch_failures => true) + + #postgres version + result = shell('psql --version') + version = result.stdout.match(%r{\s(\d\.\d)})[1] + + if version >= '9.0' + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + + ## Check that all privileges were granted to the user + psql("-d #{db} --command=\"SELECT table_name,privilege_type FROM information_schema.role_table_grants + WHERE grantee = '#{user}' AND table_schema = 'public'\"", user) do |r| + expect(r.stdout).to match(/test_tbl[ |]*UPDATE\s*\(1 row\)/) + expect(r.stderr).to eq('') + end + + apply_manifest(pp_revoke, :catch_failures => true) + apply_manifest(pp_revoke, :catch_changes => true) + + ## Check that all privileges were revoked from the user + psql("-d #{db} --command=\"SELECT table_name,privilege_type FROM information_schema.role_table_grants + WHERE grantee = '#{user}' AND table_schema = 'public'\"", user) do |r| + expect(r.stdout).to match(/\(0 rows\)/) + expect(r.stderr).to eq('') + end + end + end + end + + it 'should grant all on all tables to a user' do + begin + pp = pp_create_table + <<-EOS.unindent + + postgresql::server::grant { 'grant all on all tables': + privilege => 'ALL', + object_type => 'ALL TABLES IN SCHEMA', + object_name => 'public', + db => $db, + role => $user, + require => [ Postgresql_psql['create test table'], + Postgresql::Server::Role[$user], ] + } + EOS + + pp_revoke = pp_create_table + <<-EOS.unindent + + postgresql::server::grant { 'revoke all on all tables': + ensure => absent, + privilege => 'ALL', + object_type => 'ALL TABLES IN SCHEMA', + object_name => 'public', + db => $db, + role => $user, + require => [ Postgresql_psql['create test table'], + Postgresql::Server::Role[$user], ] + } + EOS + + apply_manifest(pp_install, :catch_failures => true) + + #postgres version + result = shell('psql --version') + version = result.stdout.match(%r{\s(\d\.\d)})[1] + + if version >= '9.0' + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + + ## Check that all privileges were granted to the user + psql("-d #{db} --tuples-only --command=\"SELECT table_name,count(privilege_type) FROM information_schema.role_table_grants + WHERE grantee = '#{user}' AND table_schema = 'public' + AND privilege_type IN ('SELECT','UPDATE','INSERT','DELETE','TRIGGER','REFERENCES','TRUNCATE') + GROUP BY table_name\"", user) do |r| + expect(r.stdout).to match(/test_tbl[ |]*7$/) + expect(r.stderr).to eq('') + end + + apply_manifest(pp_revoke, :catch_failures => true) + apply_manifest(pp_revoke, :catch_changes => true) + + ## Check that all privileges were revoked from the user + psql("-d #{db} --command=\"SELECT table_name FROM information_schema.role_table_grants + WHERE grantee = '#{user}' AND table_schema = 'public'\"", user) do |r| + expect(r.stdout).to match(/\(0 rows\)/) + expect(r.stderr).to eq('') + end + end + end + end + end + end + context 'database' do + describe 'REVOKE ... ON DATABASE...' do + it 'should not fail on revoke connect from non-existant user' do + begin + apply_manifest(pp_setup, :catch_failures => true) + pp = pp_setup + <<-EOS.unindent + postgresql::server::grant { 'revoke connect on db from norole': + ensure => absent, + privilege => 'CONNECT', + object_type => 'DATABASE', + db => '#{db}', + role => '#{user}_does_not_exist', + } + EOS + apply_manifest(pp, :catch_changes => true) + apply_manifest(pp, :catch_failures => true) + + end + end + end + end + ##################### end diff --git a/spec/unit/defines/server/grant_spec.rb b/spec/unit/defines/server/grant_spec.rb index b3028e405a..87c020a870 100644 --- a/spec/unit/defines/server/grant_spec.rb +++ b/spec/unit/defines/server/grant_spec.rb @@ -50,7 +50,7 @@ it { is_expected.to contain_postgresql_psql('grant:test').with( { 'command' => /GRANT USAGE ON SEQUENCE "test" TO\s* "test"/m, - 'unless' => /SELECT 1 WHERE has_sequence_privilege\('test',\s* 'test', 'USAGE'\)/m, + 'unless' => /SELECT 1 WHERE has_sequence_privilege\('test',\s* 'test', 'USAGE'\) = true/m, } ) } end @@ -97,7 +97,7 @@ it { is_expected.to contain_postgresql_psql('grant:test').with( { 'command' => /GRANT USAGE ON ALL SEQUENCES IN SCHEMA "public" TO\s* "test"/m, - 'unless' => /SELECT 1 FROM \(\s*SELECT sequence_name\s* FROM information_schema\.sequences\s* WHERE sequence_schema='public'\s* EXCEPT DISTINCT\s* SELECT object_name as sequence_name\s* FROM .* WHERE .*grantee='test'\s* AND object_schema='public'\s* AND privilege_type='USAGE'\s*\) P\s* HAVING count\(P\.sequence_name\) = 0/m, + 'unless' => /SELECT 1 WHERE NOT EXISTS \(\s*SELECT sequence_name\s* FROM information_schema\.sequences\s* WHERE sequence_schema='public'\s* EXCEPT DISTINCT\s* SELECT object_name as sequence_name\s* FROM .* WHERE .*grantee='test'\s* AND object_schema='public'\s* AND privilege_type='USAGE'\s*\)/m, } ) } end