From 6aaa1cf90b08e513aa16c094afc187819b8f7e9e Mon Sep 17 00:00:00 2001 From: Chris Le Sueur Date: Wed, 15 Sep 2021 15:16:58 +0100 Subject: [PATCH] Support setting default_privileges on all schemas The Postgres default is for the absent specification of a schema name when altering default privileges to apply to all schemas. Support that behaviour, but keep the current default behaviour for an unset schema parameter. --- manifests/server/default_privileges.pp | 22 +++-- .../server/default_privileges_spec.rb | 86 ++++++++++++++++++- .../defines/server/default_privileges_spec.rb | 32 ++++++- 3 files changed, 129 insertions(+), 11 deletions(-) diff --git a/manifests/server/default_privileges.pp b/manifests/server/default_privileges.pp index 80d2348d9d..9cc90935bf 100644 --- a/manifests/server/default_privileges.pp +++ b/manifests/server/default_privileges.pp @@ -6,7 +6,7 @@ # @param db Specifies the database to which you are granting access. # @param object_type Specify target object type: 'FUNCTIONS', 'ROUTINES', 'SEQUENCES', 'TABLES', 'TYPES'. # @param privilege Specifies comma-separated list of privileges to grant. Valid options: depends on object type. -# @param schema Target schema. Defaults to 'public'. +# @param schema Target schema. Defaults to 'public'. Can be set to '' to apply to all schemas. # @param psql_db Defines the database to execute the grant against. This should not ordinarily be changed from the default. # @param psql_user Specifies the OS user for running psql. Default value: The default user for the module, usually 'postgres'. # @param psql_path Specifies the OS user for running psql. Default value: The default user for the module, usually 'postgres'. @@ -52,11 +52,11 @@ case $ensure { default: { # default is 'present' - $sql_command = 'ALTER DEFAULT PRIVILEGES%s IN SCHEMA %s GRANT %s ON %s TO "%s"' + $sql_command = 'ALTER DEFAULT PRIVILEGES%s%s GRANT %s ON %s TO "%s"' $unless_is = true } 'absent': { - $sql_command = 'ALTER DEFAULT PRIVILEGES%s IN SCHEMA %s REVOKE %s ON %s FROM "%s"' + $sql_command = 'ALTER DEFAULT PRIVILEGES%s%s REVOKE %s ON %s FROM "%s"' $unless_is = false } } @@ -80,6 +80,14 @@ $_check_target_role = '' } + if $schema != '' { + $_schema = " IN SCHEMA $schema" + $_check_schema = " AND nspname = '$schema'" + } else { + $_schema = '' + $_check_schema = ' AND nspname IS NULL' + } + ## Munge the input values $_object_type = upcase($object_type) $_privilege = upcase($privilege) @@ -138,12 +146,12 @@ } $_unless = $ensure ? { - 'absent' => "SELECT 1 WHERE NOT EXISTS (SELECT * FROM pg_default_acl AS da JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE '%s=%s%s' = ANY (defaclacl) AND nspname = '%s' and defaclobjtype = '%s')", - default => "SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE '%s=%s%s' = ANY (defaclacl) AND nspname = '%s' and defaclobjtype = '%s')" + 'absent' => "SELECT 1 WHERE NOT EXISTS (SELECT * FROM pg_default_acl AS da LEFT JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE '%s=%s%s' = ANY (defaclacl)%s and defaclobjtype = '%s')", + default => "SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da LEFT JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE '%s=%s%s' = ANY (defaclacl)%s and defaclobjtype = '%s')" } - $unless_cmd = sprintf($_unless, $role, $_check_privilege, $_check_target_role, $schema, $_check_type) - $grant_cmd = sprintf($sql_command, $_target_role, $schema, $_privilege, $_object_type, $role) + $unless_cmd = sprintf($_unless, $role, $_check_privilege, $_check_target_role, $_check_schema, $_check_type) + $grant_cmd = sprintf($sql_command, $_target_role, $_schema, $_privilege, $_object_type, $role) postgresql_psql { "default_privileges:${name}": command => $grant_cmd, diff --git a/spec/acceptance/server/default_privileges_spec.rb b/spec/acceptance/server/default_privileges_spec.rb index 51d5e564a2..db474da87b 100644 --- a/spec/acceptance/server/default_privileges_spec.rb +++ b/spec/acceptance/server/default_privileges_spec.rb @@ -10,7 +10,7 @@ # Check that the default privileges were revoked let(:check_command) do - "SELECT * FROM pg_default_acl a JOIN pg_namespace b ON a.defaclnamespace = b.oid WHERE '#{user}=arwdDxt' = ANY (defaclacl) AND nspname = 'public' and defaclobjtype = 'r';" + "SELECT * FROM pg_default_acl a LEFT JOIN pg_namespace b ON a.defaclnamespace = b.oid WHERE '#{user}=arwdDxt' = ANY (defaclacl) AND nspname = 'public' and defaclobjtype = 'r';" end let(:pp_one) do @@ -167,6 +167,67 @@ class { 'postgresql::server': } MANIFEST end + let(:all_schemas_check_command) do + "SELECT * FROM pg_default_acl a WHERE '#{user}=arwdDxt' = ANY (defaclacl) AND defaclnamespace = 0 and defaclobjtype = 'r';" + end + + let(:pp_unset_schema) do + <<-MANIFEST.unindent + $db = #{db} + $user = #{user} + $group = #{group} + $password = #{password} + + class { 'postgresql::server': } + + postgresql::server::role { $user: + password_hash => postgresql::postgresql_password($user, $password), + } + + postgresql::server::database { $db: + require => Postgresql::Server::Role[$user], + } + + # Set default privileges on tables + postgresql::server::default_privileges { "alter default privileges grant all on tables to ${user}": + db => $db, + role => $user, + privilege => 'ALL', + object_type => 'TABLES', + schema => '', + require => Postgresql::Server::Database[$db], + } + MANIFEST + end + let(:pp_unset_schema_revoke) do + <<-MANIFEST + $db = #{db} + $user = #{user} + $group = #{group} + $password = #{password} + + class { 'postgresql::server': } + + postgresql::server::role { $user: + password_hash => postgresql::postgresql_password($user, $password), + } + postgresql::server::database { $db: + require => Postgresql::Server::Role[$user], + } + + # Removes default privileges on tables + postgresql::server::default_privileges { "alter default privileges revoke all on tables for ${user}": + db => $db, + role => $user, + privilege => 'ALL', + object_type => 'TABLES', + schema => '', + ensure => 'absent', + require => Postgresql::Server::Database[$db], + } + MANIFEST + end + it 'grants default privileges to an user' do if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6') idempotent_apply(pp_one) @@ -212,4 +273,27 @@ class { 'postgresql::server': } end end end + + it 'grants default privileges on all schemas to a user' do + if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6') + idempotent_apply(pp_unset_schema) + + psql("--command=\"SET client_min_messages = 'error';#{all_schemas_check_command}\" --db=#{db}") do |r| + expect(r.stdout).to match(%r{\(1 row\)}) + expect(r.stderr).to eq('') + end + end + end + + it 'revokes default privileges on all schemas for a user' do + if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6') + apply_manifest(pp_unset_schema, catch_failures: true) + apply_manifest(pp_unset_schema_revoke, expect_changes: true) + + psql("--command=\"SET client_min_messages = 'error';#{all_schemas_check_command}\" --db=#{db}") do |r| + expect(r.stdout).to match(%r{\(0 rows\)}) + expect(r.stderr).to eq('') + end + end + end end diff --git a/spec/unit/defines/server/default_privileges_spec.rb b/spec/unit/defines/server/default_privileges_spec.rb index c21c7ba81e..d6612ff14b 100644 --- a/spec/unit/defines/server/default_privileges_spec.rb +++ b/spec/unit/defines/server/default_privileges_spec.rb @@ -112,7 +112,7 @@ # rubocop:disable Layout/LineLength is_expected.to contain_postgresql_psql('default_privileges:test') .with_command('ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "test"') - .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt' = ANY (defaclacl) AND nspname = 'public' and defaclobjtype = 'r')") + .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da LEFT JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt' = ANY (defaclacl) AND nspname = 'public' and defaclobjtype = 'r')") # rubocop:enable Layout/LineLength end end @@ -222,7 +222,33 @@ # rubocop:disable Layout/LineLength is_expected.to contain_postgresql_psql('default_privileges:test') .with_command('ALTER DEFAULT PRIVILEGES IN SCHEMA my_schema GRANT ALL ON TABLES TO "test"') - .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt' = ANY (defaclacl) AND nspname = 'my_schema' and defaclobjtype = 'r')") + .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da LEFT JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt' = ANY (defaclacl) AND nspname = 'my_schema' and defaclobjtype = 'r')") + # rubocop:enable Layout/LineLength + end + end + + context 'with unset schema name' do + let :params do + { + db: 'test', + role: 'test', + privilege: 'all', + object_type: 'tables', + schema: '' + } + end + + let :pre_condition do + "class {'postgresql::server':}" + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_postgresql__server__default_privileges('test') } + it do + # rubocop:disable Layout/LineLength + is_expected.to contain_postgresql_psql('default_privileges:test') + .with_command('ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO "test"') + .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da LEFT JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt' = ANY (defaclacl) AND nspname IS NULL and defaclobjtype = 'r')") # rubocop:enable Layout/LineLength end end @@ -278,7 +304,7 @@ class {'postgresql::server':} # rubocop:disable Layout/LineLength is_expected.to contain_postgresql_psql('default_privileges:test') .with_command('ALTER DEFAULT PRIVILEGES FOR ROLE target IN SCHEMA public GRANT ALL ON TABLES TO "test"') - .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt/target' = ANY (defaclacl) AND nspname = 'public' and defaclobjtype = 'r')") + .with_unless("SELECT 1 WHERE EXISTS (SELECT * FROM pg_default_acl AS da LEFT JOIN pg_namespace AS n ON da.defaclnamespace = n.oid WHERE 'test=arwdDxt/target' = ANY (defaclacl) AND nspname = 'public' and defaclobjtype = 'r')") # rubocop:enable Layout/LineLength end end