Skip to content

Commit c96b073

Browse files
committed
Implement default privileges changes
1 parent 37ced4c commit c96b073

File tree

4 files changed

+551
-0
lines changed

4 files changed

+551
-0
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,19 @@ This applies to objects within the nominated database, 'test_db' only.
131131

132132
For Postgresql >= 9.3, the ownership of the database is also updated.
133133

134+
### Manage default permissions (PostgreSQL >= 9.6)
135+
136+
To change default permissions for newly created objects using ALTER DEFAULT PRIVILEGES:
137+
138+
```puppet
139+
postgresql::server::default_privileges { 'marmot access to new tables on test_db':
140+
db => 'test_db',
141+
role => 'marmot',
142+
privilege => 'ALL',
143+
object_type => 'TABLES',
144+
}
145+
```
146+
134147
### Override defaults
135148

136149
The `postgresql::globals` class allows you to configure the main settings for this module globally, so that other classes and defined resources can use them. By itself, it does nothing.
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# @summary Manage a database defaults privileges. Only works with PostgreSQL version 9.6 and above.
2+
#
3+
# @param ensure Specifies whether to grant or revoke the privilege.
4+
# @param role Specifies the role or user whom you are granting access to.
5+
# @param db Specifies the database to which you are granting access.
6+
# @param object_type Specify target object type: 'FUNCTIONS', 'ROUTINES', 'SEQUENCES', 'TABLES', 'TYPES'.
7+
# @param privilege Specifies comma-separated list of privileges to grant. Valid options: depends on object type.
8+
# @param schema Target schema. Defaults to 'public'.
9+
# @param psql_db Defines the database to execute the grant against. This should not ordinarily be changed from the default.
10+
# @param psql_user Specifies the OS user for running psql. Default value: The default user for the module, usually 'postgres'.
11+
# @param psql_path Specifies the OS user for running psql. Default value: The default user for the module, usually 'postgres'.
12+
# @param port Specifies the port to access the server. Default value: The default user for the module, usually '5432'.
13+
# @param connect_settings Specifies a hash of environment variables used when connecting to a remote server.
14+
# @param psql_path Specifies the path to the psql command.
15+
define postgresql::server::default_privileges (
16+
String $role,
17+
String $db,
18+
String $privilege,
19+
Pattern[
20+
/(?i:^FUNCTIONS$)/,
21+
/(?i:^ROUTINES$)/,
22+
/(?i:^SEQUENCES$)/,
23+
/(?i:^TABLES$)/,
24+
/(?i:^TYPES$)/
25+
] $object_type,
26+
String $schema = 'public',
27+
String $psql_db = $postgresql::server::default_database,
28+
String $psql_user = $postgresql::server::user,
29+
Integer $port = $postgresql::server::port,
30+
Hash $connect_settings = $postgresql::server::default_connect_settings,
31+
Enum['present',
32+
'absent'
33+
] $ensure = 'present',
34+
String $group = $postgresql::server::group,
35+
String $psql_path = $postgresql::server::psql_path,
36+
) {
37+
38+
# If possible use the version of the remote database, otherwise
39+
# fallback to our local DB version
40+
if $connect_settings != undef and has_key( $connect_settings, 'DBVERSION') {
41+
$version = $connect_settings['DBVERSION']
42+
} else {
43+
$version = $postgresql::server::_version
44+
}
45+
46+
if (versioncmp($version, '9.6') == -1) {
47+
fail 'Default_privileges is only useable with PostgreSQL >= 9.6'
48+
}
49+
50+
case $ensure {
51+
default: {
52+
# default is 'present'
53+
$sql_command = 'ALTER DEFAULT PRIVILEGES IN SCHEMA %s GRANT %s ON %s TO "%s"'
54+
$unless_is = true
55+
}
56+
'absent': {
57+
$sql_command = 'ALTER DEFAULT PRIVILEGES IN SCHEMA %s REVOKE %s ON %s FROM "%s"'
58+
$unless_is = false
59+
}
60+
}
61+
62+
#
63+
# Port, order of precedence: $port parameter, $connect_settings[PGPORT], $postgresql::server::port
64+
#
65+
if $port != undef {
66+
$port_override = $port
67+
} elsif $connect_settings != undef and has_key( $connect_settings, 'PGPORT') {
68+
$port_override = undef
69+
} else {
70+
$port_override = $postgresql::server::port
71+
}
72+
73+
## Munge the input values
74+
$_object_type = upcase($object_type)
75+
$_privilege = upcase($privilege)
76+
77+
case $_object_type {
78+
# Routines and functions ends up with the same definition
79+
Pattern[
80+
/^ROUTINES$/,
81+
/^FUNCTIONS$/,
82+
]: {
83+
case $_privilege {
84+
Pattern[
85+
/^ALL$/,
86+
/^EXECUTE$/,
87+
]: {
88+
$_check_privilege = 'X'
89+
}
90+
default: { fail('Illegal value for $privilege parameter') }
91+
}
92+
$_check_type = 'f'
93+
}
94+
'SEQUENCES': {
95+
case $_privilege {
96+
/^(ALL)$/: { $_check_privilege = 'rwU' }
97+
/^SELECT$/: { $_check_privilege = 'r'}
98+
/^UPDATE$/: { $_check_privilege = 'w'}
99+
/^USAGE$/: { $_check_privilege = 'U'}
100+
default: { fail('Illegal value for $privilege parameter') }
101+
}
102+
$_check_type = 'S'
103+
}
104+
'TABLES': {
105+
case $_privilege {
106+
/^ALL$/: { $_check_privilege = 'arwdDxt' }
107+
/^DELETE$/: { $_check_privilege = 'd' }
108+
/^INSERT$/: { $_check_privilege = 'a' }
109+
/^REFERENCES$/: { $_check_privilege = 'x' }
110+
/^SELECT$/: { $_check_privilege = 'r' }
111+
/^TRIGGER$/: { $_check_privilege = 'd' }
112+
/^TRUNCATE$/: { $_check_privilege = 'D' }
113+
/^UPDATE$/: { $_check_privilege = 'w' }
114+
default: { fail('Illegal value for $privilege parameter') }
115+
}
116+
$_check_type = 'r'
117+
}
118+
'TYPES': {
119+
case $_privilege {
120+
/^(ALL|USAGE)$/: { $_check_privilege = 'U'}
121+
default: { fail('Illegal value for $privilege parameter') }
122+
}
123+
$_check_type = 'T'
124+
}
125+
default: {
126+
fail("Missing privilege validation for object type ${_object_type}")
127+
}
128+
}
129+
130+
$_unless = $ensure ? {
131+
'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')",
132+
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')"
133+
}
134+
135+
$unless_cmd = sprintf($_unless, $role, $_check_privilege, $schema, $_check_type)
136+
$grant_cmd = sprintf($sql_command, $schema, $_privilege, $_object_type, $role)
137+
138+
postgresql_psql { "default_privileges:${name}":
139+
command => $grant_cmd,
140+
db => $db,
141+
port => $port_override,
142+
connect_settings => $connect_settings,
143+
psql_user => $psql_user,
144+
psql_group => $group,
145+
psql_path => $psql_path,
146+
unless => $unless_cmd,
147+
environment => "PGOPTIONS=--client-min-messages=error"
148+
}
149+
150+
if($role != undef and defined(Postgresql::Server::Role[$role])) {
151+
Postgresql::Server::Role[$role]->Postgresql_psql["default_privileges:${name}"]
152+
}
153+
154+
if($db != undef and defined(Postgresql::Server::Database[$db])) {
155+
Postgresql::Server::Database[$db]->Postgresql_psql["default_privileges:${name}"]
156+
}
157+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper_acceptance'
4+
5+
describe 'postgresql::server::default_privileges:' do
6+
let(:db) { 'grant_role_test' }
7+
let(:user) { 'psql_grant_role_tester' }
8+
let(:group) { 'test_group' }
9+
let(:password) { 'psql_grant_role_pw' }
10+
11+
# Check that the default privileges were revoked
12+
let(:check_command) do
13+
"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';"
14+
end
15+
16+
let(:pp_one) do
17+
<<-MANIFEST.unindent
18+
$db = #{db}
19+
$user = #{user}
20+
$group = #{group}
21+
$password = #{password}
22+
23+
class { 'postgresql::server': }
24+
25+
postgresql::server::role { $user:
26+
password_hash => postgresql::postgresql_password($user, $password),
27+
}
28+
29+
postgresql::server::database { $db:
30+
require => Postgresql::Server::Role[$user],
31+
}
32+
33+
# Set default privileges on tables
34+
postgresql::server::default_privileges { "alter default privileges grant all on tables to ${user}":
35+
db => $db,
36+
role => $user,
37+
privilege => 'ALL',
38+
object_type => 'TABLES',
39+
require => Postgresql::Server::Database[$db],
40+
}
41+
MANIFEST
42+
end
43+
let(:pp_two) do
44+
<<-MANIFEST
45+
$db = #{db}
46+
$user = #{user}
47+
$group = #{group}
48+
$password = #{password}
49+
50+
class { 'postgresql::server': }
51+
52+
postgresql::server::role { $user:
53+
password_hash => postgresql::postgresql_password($user, $password),
54+
}
55+
postgresql::server::database { $db:
56+
require => Postgresql::Server::Role[$user],
57+
}
58+
59+
# Removes default privileges on tables
60+
postgresql::server::default_privileges { "alter default privileges revoke all on tables for ${user}":
61+
db => $db,
62+
role => $user,
63+
privilege => 'ALL',
64+
object_type => 'TABLES',
65+
ensure => 'absent',
66+
require => Postgresql::Server::Database[$db],
67+
}
68+
MANIFEST
69+
end
70+
71+
it 'grants default privileges to an user' do
72+
if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6')
73+
idempotent_apply(pp_one)
74+
75+
psql("--command=\"SET client_min_messages = 'error';#{check_command}\" --db=#{db}") do |r|
76+
expect(r.stdout).to match(%r{\(1 row\)})
77+
expect(r.stderr).to eq('')
78+
end
79+
end
80+
end
81+
82+
it 'revokes default privileges for an user' do
83+
if Gem::Version.new(postgresql_version) >= Gem::Version.new('9.6')
84+
apply_manifest(pp_one, catch_failures: true)
85+
apply_manifest(pp_two, expect_changes: true)
86+
87+
psql("--command=\"SET client_min_messages = 'error';#{check_command}\" --db=#{db}") do |r|
88+
expect(r.stdout).to match(%r{\(0 rows\)})
89+
expect(r.stderr).to eq('')
90+
end
91+
end
92+
end
93+
end

0 commit comments

Comments
 (0)