Skip to content

Commit 1175ea2

Browse files
committed
Add postgresql::db convenience type, improve security
This commit adds a postgresql::db type for convenience; it mirrors the 'db' type from the mysql module, which allows you to create a database instance and user plus grant privileges to that user all in one succint resource. This commit also improves security in the following ways: * Revoke "CONNECT" privilege from the 'public' role for newly created databases; without this, any database created via this module will allow connections from any database user, and will allow them to do things like create tables. * Change to a 'reject'-based policy for dealing with remote connections by the postgres user in pg_hba.conf. Prior to this commit, if you tried to restrict access to the postgres user by IP, the rule would simply not match for disallowed IPs; then it would fall through to the rule for "all" users, which could still match and thus allow the postgres user to connect remotely.
1 parent 45055d3 commit 1175ea2

13 files changed

+193
-56
lines changed

Diff for: manifests/config.pp

+28-27
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
#
33
# Parameters:
44
#
5-
# [*postgres_password*] - postgres db user password.
6-
# [*ip_mask_postgres_user*] - ip mask for allowing remote access for postgres user; defaults to '127.0.0.1/32'
7-
# [*ip_mask_all_users*] - ip mask for allowing remote access for other users (besides postgres);
8-
# defaults to '127.0.0.1/32'
9-
# [*listen_addresses*] - what IP address(es) to listen on; comma-separated list of addresses; defaults to
10-
# 'localhost', '*' = all
11-
# [*pg_hba_conf_path*] - path to pg_hba.conf file
12-
# [*postgresql_conf_path*] - path to postgresql.conf file
13-
# [*manage_redhat_firewall*] - boolean indicating whether or not the module should open a port in the firewall on
14-
# redhat-based systems; this parameter is likely to change in future versions. Possible
15-
# changes include support for non-RedHat systems and finer-grained control over the
16-
# firewall rule (currently, it simply opens up the postgres port to all TCP connections).
5+
# [*postgres_password*] - postgres db user password.
6+
# [*ip_mask_deny_postgres_user*] - ip mask for denying remote access for postgres user; defaults to '0.0.0.0/0',
7+
# meaning that all TCP access for postgres user is denied.
8+
# [*ip_mask_allow_all_users*] - ip mask for allowing remote access for other users (besides postgres);
9+
# defaults to '127.0.0.1/32', meaning only allow connections from localhost
10+
# [*listen_addresses*] - what IP address(es) to listen on; comma-separated list of addresses; defaults to
11+
# 'localhost', '*' = all
12+
# [*pg_hba_conf_path*] - path to pg_hba.conf file
13+
# [*postgresql_conf_path*] - path to postgresql.conf file
14+
# [*manage_redhat_firewall*] - boolean indicating whether or not the module should open a port in the firewall on
15+
# redhat-based systems; this parameter is likely to change in future versions. Possible
16+
# changes include support for non-RedHat systems and finer-grained control over the
17+
# firewall rule (currently, it simply opens up the postgres port to all TCP connections).
1718
#
1819
#
1920
# Actions:
@@ -23,31 +24,31 @@
2324
# Usage:
2425
#
2526
# class { 'postgresql::config':
26-
# postgres_password => 'postgres',
27-
# ip_mask_other_user => '127.0.0.1/32',
27+
# postgres_password => 'postgres',
28+
# ip_mask_allow_all_users => '0.0.0.0/0',
2829
# }
2930
#
3031
class postgresql::config(
31-
$postgres_password = undef,
32-
$ip_mask_postgres_user = $postgresql::params::ip_mask_postgres_user,
33-
$ip_mask_all_users = $postgresql::params::ip_mask_all_users,
34-
$listen_addresses = $postgresql::params::listen_addresses,
35-
$pg_hba_conf_path = $postgresql::params::pg_hba_conf_path,
36-
$postgresql_conf_path = $postgresql::params::postgresql_conf_path,
37-
$manage_redhat_firewall = $postgresql::params::manage_redhat_firewall,
32+
$postgres_password = undef,
33+
$ip_mask_deny_postgres_user = $postgresql::params::ip_mask_postgres_user,
34+
$ip_mask_allow_all_users = $postgresql::params::ip_mask_all_users,
35+
$listen_addresses = $postgresql::params::listen_addresses,
36+
$pg_hba_conf_path = $postgresql::params::pg_hba_conf_path,
37+
$postgresql_conf_path = $postgresql::params::postgresql_conf_path,
38+
$manage_redhat_firewall = $postgresql::params::manage_redhat_firewall,
3839
) inherits postgresql::params {
3940

4041
# Basically, all this class needs to handle is passing parameters on
4142
# to the "beforeservice" and "afterservice" classes, and ensure
4243
# the proper ordering.
4344

4445
class { "postgresql::config::beforeservice":
45-
ip_mask_postgres_user => $ip_mask_postgres_user,
46-
ip_mask_all_users => $ip_mask_all_users,
47-
listen_addresses => $listen_addresses,
48-
pg_hba_conf_path => $pg_hba_conf_path,
49-
postgresql_conf_path => $postgresql_conf_path,
50-
manage_redhat_firewall => $manage_redhat_firewall,
46+
ip_mask_deny_postgres_user => $ip_mask_deny_postgres_user,
47+
ip_mask_allow_all_users => $ip_mask_allow_all_users,
48+
listen_addresses => $listen_addresses,
49+
pg_hba_conf_path => $pg_hba_conf_path,
50+
postgresql_conf_path => $postgresql_conf_path,
51+
manage_redhat_firewall => $manage_redhat_firewall,
5152
}
5253

5354
class { "postgresql::config::afterservice":

Diff for: manifests/config/beforeservice.pp

+11-10
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
#
33
# Parameters:
44
#
5-
# [*ip_mask_postgres_user*] - ip mask for allowing remote access for postgres user; defaults to '127.0.0.1/32'
6-
# [*ip_mask_all_users*] - ip mask for allowing remote access for other users (besides postgres);
7-
# defaults to '127.0.0.1/32'
5+
# [*ip_mask_deny_postgres_user*] - ip mask for denying remote access for postgres user; defaults to '0.0.0.0/0',
6+
# meaning that all TCP access for postgres user is denied.
7+
# [*ip_mask_allow_all_users*] - ip mask for allowing remote access for other users (besides postgres);
8+
# defaults to '127.0.0.1/32', meaning only allow connections from localhost
89
# [*listen_addresses*] - what IP address(es) to listen on; comma-separated list of addresses; defaults to
910
# 'localhost', '*' = all
1011
# [*pg_hba_conf_path*] - path to pg_hba.conf file
@@ -25,16 +26,16 @@
2526
# has been started up.
2627
#
2728
# class { 'postgresql::config::before_service':
28-
# ip_mask_other_user => '127.0.0.1/32',
29+
# ip_mask_allow_all_users => '0.0.0.0/0',
2930
# }
3031
#
3132
class postgresql::config::beforeservice(
32-
$ip_mask_postgres_user = $postgresql::params::ip_mask_postgres_user,
33-
$ip_mask_all_users = $postgresql::params::ip_mask_all_users,
34-
$listen_addresses = $postgresql::params::listen_addresses,
35-
$pg_hba_conf_path = $postgresql::params::pg_hba_conf_path,
36-
$postgresql_conf_path = $postgresql::params::postgresql_conf_path,
37-
$manage_redhat_firewall = $postgresql::params::manage_redhat_firewall,
33+
$ip_mask_deny_postgres_user = $postgresql::params::ip_mask_deny_postgres_user,
34+
$ip_mask_allow_all_users = $postgresql::params::ip_mask_allow_all_users,
35+
$listen_addresses = $postgresql::params::listen_addresses,
36+
$pg_hba_conf_path = $postgresql::params::pg_hba_conf_path,
37+
$postgresql_conf_path = $postgresql::params::postgresql_conf_path,
38+
$manage_redhat_firewall = $postgresql::params::manage_redhat_firewall,
3839
) inherits postgresql::params {
3940

4041
File {

Diff for: manifests/database.pp

+13-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,21 @@
2525
{
2626
require postgresql::params
2727

28+
$createdb_command = "${postgresql::params::createdb_path} --template=template0 --encoding '$charset' --locale=C '$dbname'"
2829

29-
exec {"${postgresql::params::createdb_path} --template=template0 --encoding '$charset' --locale=C '$dbname'":
30+
exec { $createdb_command :
3031
unless => "${postgresql::params::psql_path} --command=\"SELECT datname FROM pg_database WHERE datname=\'$dbname\' \" --pset=tuples_only | grep -q $dbname",
3132
user => 'postgres',
3233
}
34+
35+
# This will prevent users from connecting to the database unless they've been
36+
# granted privileges.
37+
postgresql::psql {"REVOKE CONNECT ON DATABASE $dbname FROM public":
38+
db => 'postgres',
39+
user => 'postgres',
40+
unless => 'SELECT 1 where 1 = 0',
41+
refreshonly => true,
42+
subscribe => Exec[$createdb_command],
43+
}
44+
3345
}

Diff for: manifests/database_grant.pp

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
# TODO: this is a terrible hack; if they pass "ALL" as the desired privilege,
4040
# we need a way to test for it--and has_database_privilege does not recognize
41-
# 'ALL' as a valid privelege name. So we probably need to hard-code a mapping
41+
# 'ALL' as a valid privilege name. So we probably need to hard-code a mapping
4242
# between 'ALL' and the list of actual privileges that it entails, and loop
4343
# over them to check them. That sort of thing will probably need to wait until
4444
# we port this over to ruby, so, for now, we're just going to assume that if

Diff for: manifests/database_user.pp

+23-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,36 @@
1616
# See the License for the specific language governing permissions and
1717
# limitations under the License.
1818

19+
# Define: mysql::db
20+
#
21+
# This type creates a postgres database user.
22+
#
23+
# Parameters:
24+
# [*user*] - username to create.
25+
# [*password_hash*] - user's password; this may be clear text, or an md5 hash as returned by the
26+
# "postgresql_password" function in this module.
27+
#
28+
# Actions:
29+
#
30+
# Requires:
31+
#
32+
#
33+
# Sample Usage:
34+
#
35+
# postgresql::database_user { 'frank':
36+
# password_hash => postgresql_passowrd('password'),
37+
# }
38+
#
39+
1940
define postgresql::database_user(
20-
$username=$title,
41+
$user=$title,
2142
$password_hash,
2243
$db = 'postgres',
2344
$createdb=false,
2445
$superuser=false,
2546
$createrole=false
2647
) {
27-
postgresql::role {$username:
48+
postgresql::role {$user:
2849
db => $db,
2950
password_hash => $password_hash,
3051
login => true,

Diff for: manifests/db.pp

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Define: postgresql::db
2+
#
3+
# This module creates database instances, a user, and grants that user
4+
# privileges to the database.
5+
#
6+
# Since it requires class postgresql::server, we assume to run all commands as the
7+
# postgresql user against the local postgresql server.
8+
#
9+
# TODO: support an array of privileges for "grant"; currently only supports a single
10+
# privilege, which is pretty useless unless that privilege is "ALL"
11+
#
12+
# Parameters:
13+
# [*title*] - postgresql database name.
14+
# [*user*] - username to create and grant access.
15+
# [*password*] - user's password. may be md5-encoded, in the format returned by the "postgresql_password"
16+
# function in this module
17+
# [*charset*] - database charset.
18+
# [*grant*] - privilege to grant user.
19+
#
20+
# Actions:
21+
#
22+
# Requires:
23+
#
24+
# class postgresql::server
25+
#
26+
# Sample Usage:
27+
#
28+
# postgresql::db { 'mydb':
29+
# user => 'my_user',
30+
# password => 'password',
31+
# grant => 'all'
32+
# }
33+
#
34+
define postgresql::db (
35+
$user,
36+
$password,
37+
$charset = 'utf8',
38+
$grant = 'ALL'
39+
) {
40+
41+
postgresql::database { $name:
42+
# TODO: ensure is not yet supported
43+
#ensure => present,
44+
charset => $charset,
45+
#provider => 'postgresql',
46+
require => Class['postgresql::server'],
47+
}
48+
49+
postgresql::database_user { "${user}":
50+
# TODO: ensure is not yet supported
51+
#ensure => present,
52+
password_hash => $password,
53+
#provider => 'postgresql',
54+
require => Postgresql::Database[$name],
55+
}
56+
57+
postgresql::database_grant { "GRANT ${user} - ${grant} - ${name}":
58+
privilege => $grant,
59+
db => $name,
60+
role => $user,
61+
#provider => 'postgresql',
62+
require => Postgresql::Database_user["${user}"],
63+
}
64+
65+
}

Diff for: manifests/params.pp

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
# Sample Usage:
1212
#
1313
class postgresql::params {
14-
$user = 'postgres'
15-
$group = 'postgres'
16-
$ip_mask_postgres_user = '127.0.0.1/32'
17-
$ip_mask_all_users = '127.0.0.1/32'
18-
$listen_addresses = 'localhost'
14+
$user = 'postgres'
15+
$group = 'postgres'
16+
$ip_mask_deny_postgres_user = '0.0.0.0/0'
17+
$ip_mask_allow_all_users = '127.0.0.1/32'
18+
$listen_addresses = 'localhost'
1919
# TODO: figure out a way to make this not platform-specific
20-
$manage_redhat_firewall = false
20+
$manage_redhat_firewall = false
2121

2222
case $::operatingsystem {
2323
"Ubuntu": {

Diff for: manifests/psql.pp

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616
# See the License for the specific language governing permissions and
1717
# limitations under the License.
1818

19-
define postgresql::psql($command = $title, $unless, $db, $user = 'postgres') {
19+
define postgresql::psql(
20+
$command = $title,
21+
$unless,
22+
$db,
23+
$user = 'postgres',
24+
$refreshonly = false
25+
) {
2026

2127
require postgresql::params
2228

@@ -33,6 +39,7 @@
3339
user => $user,
3440
returns => 1,
3541
unless => "/bin/echo \"$quoted_$unless\" | $psql | egrep -v -q '^$'",
42+
refreshonly => $refreshonly,
3643
}
3744
}
3845

Diff for: templates/pg_hba.conf.erb

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ local all postgres ident
8181
# "local" is for Unix domain socket connections only
8282
local all all ident
8383
# IPv4 local connections:
84-
host all postgres <%= @ip_mask_postgres_user + "\t" %> md5
85-
host all all <%= @ip_mask_all_users + "\t" %> md5
84+
host all postgres <%= @ip_mask_deny_postgres_user + "\t" %> reject
85+
host all all <%= @ip_mask_allow_all_users + "\t" %> md5
8686
# IPv6 local connections:
8787
host all all ::1/128 md5
8888

Diff for: tests/postgresql_database.pp

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class { 'postgresql::server':
22
config_hash => {
3-
'ip_mask_postgres_user' => '0.0.0.0/0',
4-
'ip_mask_all_users' => '0.0.0.0/0',
3+
'ip_mask_deny_postgres_user' => '0.0.0.0/32',
4+
'ip_mask_allow_all_users' => '0.0.0.0/0',
55
'listen_addresses' => '*',
66
'manage_redhat_firewall' => true,
77
'postgres_password' => 'postgres',

Diff for: tests/postgresql_db.pp

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
class { 'postgresql::server':
2+
config_hash => {
3+
'ip_mask_allow_all_users' => '0.0.0.0/0',
4+
'listen_addresses' => '*',
5+
'manage_redhat_firewall' => true,
6+
7+
#'ip_mask_deny_postgres_user' => '0.0.0.0/32',
8+
#'postgres_password' => 'puppet',
9+
},
10+
}
11+
12+
postgresql::db{ 'test1':
13+
user => 'test1',
14+
password => 'test1',
15+
grant => 'all',
16+
}
17+
18+
postgresql::db{ 'test2':
19+
user => 'test2',
20+
password => postgresql_password('test2', 'test2'),
21+
grant => 'all',
22+
}
23+
24+
postgresql::db{ 'test3':
25+
user => 'test3',
26+
# The password here is a copy/paste of the output of the 'postgresql_password'
27+
# function from this module, with a u/p of 'test3', 'test3'.
28+
password => 'md5e12234d4575a12bfd61d61294f32b086',
29+
grant => 'all',
30+
}

Diff for: tests/postgresql_user.pp

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class { 'postgresql::server':
22
config_hash => {
3-
'ip_mask_postgres_user' => '0.0.0.0/0',
4-
'ip_mask_all_users' => '0.0.0.0/0',
3+
'ip_mask_deny_postgres_user' => '0.0.0.0/32',
4+
'ip_mask_allow_all_users' => '0.0.0.0/0',
55
'listen_addresses' => '*',
66
'manage_redhat_firewall' => true,
77
'postgres_password' => 'postgres',

Diff for: tests/server.pp

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class { 'postgresql::server':
22
config_hash => {
3-
'ip_mask_postgres_user' => '0.0.0.0/0',
4-
'ip_mask_all_users' => '0.0.0.0/0',
3+
'ip_mask_deny_postgres_user' => '0.0.0.0/32',
4+
'ip_mask_allow_all_users' => '0.0.0.0/0',
55
'listen_addresses' => '*',
66
'manage_redhat_firewall' => true,
77
'postgres_password' => 'postgres',

0 commit comments

Comments
 (0)