From 64a7753d5ed949c492e65b59de9452fa43f06625 Mon Sep 17 00:00:00 2001 From: Chris Le Sueur Date: Wed, 15 Sep 2021 14:48:00 +0100 Subject: [PATCH] Support target_role in default_privileges ALTER DEFAULT PRIVILEGES supports the FOR ROLE argument, without which the statement applies only to objects created by the *current* role, which may not be most useful. Support specifying the target role. --- manifests/server/default_privileges.pp | 22 +++- .../server/default_privileges_spec.rb | 122 ++++++++++++++++++ .../defines/server/default_privileges_spec.rb | 30 +++++ 3 files changed, 168 insertions(+), 6 deletions(-) diff --git a/manifests/server/default_privileges.pp b/manifests/server/default_privileges.pp index 7311d5eb1a..0410d803a9 100644 --- a/manifests/server/default_privileges.pp +++ b/manifests/server/default_privileges.pp @@ -1,5 +1,6 @@ # @summary Manage a database defaults privileges. Only works with PostgreSQL version 9.6 and above. # +# @param target_role Target role whose created objects will receive the default privileges. Defaults to the current user. # @param ensure Specifies whether to grant or revoke the privilege. # @param role Specifies the role or user whom you are granting access to. # @param db Specifies the database to which you are granting access. @@ -13,6 +14,7 @@ # @param connect_settings Specifies a hash of environment variables used when connecting to a remote server. # @param psql_path Specifies the path to the psql command. define postgresql::server::default_privileges ( + Optional[String] $target_role = undef, String $role, String $db, String $privilege, @@ -50,11 +52,11 @@ case $ensure { default: { # default is 'present' - $sql_command = 'ALTER DEFAULT PRIVILEGES IN SCHEMA %s GRANT %s ON %s TO "%s"' + $sql_command = 'ALTER DEFAULT PRIVILEGES%s IN SCHEMA %s GRANT %s ON %s TO "%s"' $unless_is = true } 'absent': { - $sql_command = 'ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE %s ON %s FROM "%s"' + $sql_command = 'ALTER DEFAULT PRIVILEGES%s IN SCHEMA %s REVOKE %s ON %s FROM "%s"' $unless_is = false } } @@ -70,6 +72,14 @@ $port_override = $postgresql::server::port } + if $target_role != undef { + $_target_role = " FOR ROLE $target_role" + $_check_target_role = "/$target_role" + } else { + $_target_role = '' + $_check_target_role = '' + } + ## Munge the input values $_object_type = upcase($object_type) $_privilege = upcase($privilege) @@ -128,12 +138,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' = 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' = ANY (defaclacl) AND nspname = '%s' and defaclobjtype = '%s')" + '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')" } - $unless_cmd = sprintf($_unless, $role, $_check_privilege, $schema, $_check_type) - $grant_cmd = sprintf($sql_command, $schema, $_privilege, $_object_type, $role) + $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) 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 d5d9f259c5..51d5e564a2 100644 --- a/spec/acceptance/server/default_privileges_spec.rb +++ b/spec/acceptance/server/default_privileges_spec.rb @@ -68,6 +68,105 @@ class { 'postgresql::server': } MANIFEST end + let(:target_user) { 'target_role_user' } + let(:target_password) { 'target_role_password' } + + let(:target_check_command) do + "SELECT 1 FROM pg_default_acl a LEFT JOIN pg_namespace AS b ON a.defaclnamespace = b.oid WHERE '#{user}=arwdDxt/#{target_user}' = ANY (defaclacl) AND nspname = 'public' AND defaclobjtype = 'r';" + end + + let(:pp_target_role) do + <<-MANIFEST.unindent + $db = #{db} + $user = #{user} + $group = #{group} + $password = #{password} + $target_user = #{target_user} + $target_password = #{target_password} + + user {$user: + ensure => present, + } + postgresql::server::database_grant { "allow connect for ${user}": + privilege => 'CONNECT', + db => $db, + role => $user, + } + + class { 'postgresql::server': } + + postgresql::server::role { $user: + password_hash => postgresql::postgresql_password($user, $password), + } + + postgresql::server::role { $target_user: + password_hash => postgresql::postgresql_password($target_user, $target_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, + target_role => $target_user, + psql_user => 'postgres', + privilege => 'ALL', + object_type => 'TABLES', + require => Postgresql::Server::Database[$db], + } + MANIFEST + end + + let(:pp_target_role_revoke) do + <<-MANIFEST.unindent + $db = #{db} + $user = #{user} + $group = #{group} + $password = #{password} + $target_user = #{target_user} + $target_password = #{target_password} + + user {$user: + ensure => present, + } + postgresql::server::database_grant { "allow connect for ${user}": + privilege => 'CONNECT', + db => $db, + role => $user, + } + + + class { 'postgresql::server': } + + postgresql::server::role { $user: + password_hash => postgresql::postgresql_password($user, $password), + } + + postgresql::server::role { $target_user: + password_hash => postgresql::postgresql_password($target_user, $target_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, + target_role => $target_user, + psql_user => 'postgres', + privilege => 'ALL', + object_type => 'TABLES', + 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) @@ -90,4 +189,27 @@ class { 'postgresql::server': } end end end + + it 'grants default privileges to a user on a specific target role' do + if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6') + idempotent_apply(pp_target_role) + + psql("--command=\"SET client_min_messages = 'error'; #{target_check_command}\" --db=#{db}", user) do |r| + expect(r.stdout).to match(%r{^\(1 row\)$}) + expect(r.stderr).to eq('') + end + end + end + + it 'revokes default privileges from a user on a specific target role' do + if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6') + idempotent_apply(pp_target_role) + idempotent_apply(pp_target_role_revoke) + + psql("--command=\"SET client_min_messages = 'error'; #{target_check_command}\" --db=#{db}", user) 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 9b47538159..c21c7ba81e 100644 --- a/spec/unit/defines/server/default_privileges_spec.rb +++ b/spec/unit/defines/server/default_privileges_spec.rb @@ -253,6 +253,36 @@ class {'postgresql::server':} end end + context 'with a target role' do + let :params do + { + target_role: 'target', + db: 'test', + role: 'test', + privilege: 'all', + object_type: 'tables', + } + end + + let :pre_condition do + <<-EOS + class {'postgresql::server':} + postgresql::server::role { 'target': } + EOS + end + + it { is_expected.to compile.with_all_deps } + it { is_expected.to contain_postgresql__server__default_privileges('test') } + it { is_expected.to contain_postgresql__server__role('target') } + it do + # 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')") + # rubocop:enable Layout/LineLength + end + end + context 'standalone not managing server' do let :params do {