Skip to content

(maint) Add read-only user. #330

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -606,25 +606,26 @@ Which database backend to use for the read database. Only supports
`postgres` (default). This option is supported in PuppetDB >= 1.6.

#### `read_database_host`
*This parameter must be set to enable the PuppetDB read-database.*
*This parameter must be set to use another PuppetDB instance for queries.*

The hostname or IP address of the read database server. Defaults to `undef`.
The default is to use the regular database for reads and writes. This option is
supported in PuppetDB >= 1.6.
The hostname or IP address of the read database server. If set to `undef`, and
`manage_database` is set to `true`, it will use the value of the `database_host`
parameter. This option is supported in PuppetDB >= 1.6.

#### `read_database_port`

The port that the read database server listens on. Defaults to `5432`. This
option is supported in PuppetDB >= 1.6.
The port that the read database server listens on. If `read_database_host`
is set to `undef`, and `manage_database` is set to `true`, it will use the value of
the `database_port` parameter. This option is supported in PuppetDB >= 1.6.

#### `read_database_username`

The name of the read database user to connect as. Defaults to `puppetdb`. This
The name of the read database user to connect as. Defaults to `puppetdb-read`. This
Copy link

@BogdanIrimie BogdanIrimie Jun 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this always default to puppetdb-read? If managed is set to false will it still default to puppetdb-read or will it default to database_username?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always defaults to puppetdb-read

option is supported in PuppetDB >= 1.6.

#### `read_database_password`

The password for the read database user. Defaults to `puppetdb`. This option is
The password for the read database user. Defaults to `puppetdb-read`. This option is

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as for username

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will also default to puppetdb-read as well.

supported in PuppetDB >= 1.6.

#### `manage_read_db_password`
Expand All @@ -635,8 +636,9 @@ Defaults to `true`

#### `read_database_name`

The name of the read database instance to connect to. Defaults to `puppetdb`.
This option is supported in PuppetDB >= 1.6.
The name of the read database instance to connect to. If `read_database_host`
is set to `undef`, and `manage_database` is set to `true`, it will use the value of
the `database_name` parameter. This option is supported in PuppetDB >= 1.6.

#### `read_log_slow_statements`

Expand Down
59 changes: 59 additions & 0 deletions manifests/database/default_read_grant.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Private class. Grant read permissions to $database_read_only_username by default, for new tables created by
# $database_username.
define puppetdb::database::default_read_grant(
String $database_name,
String $schema,
String $database_username,
String $database_read_only_username,
) {
postgresql_psql {"grant default select permission for ${database_read_only_username}":
db => $database_name,
command => "ALTER DEFAULT PRIVILEGES
FOR USER \"${database_username}\"
IN SCHEMA \"${schema}\"
GRANT SELECT ON TABLES
TO \"${database_read_only_username}\"",
unless => "SELECT
ns.nspname,
acl.defaclobjtype,
acl.defaclacl
FROM pg_default_acl acl
JOIN pg_namespace ns ON acl.defaclnamespace=ns.oid
WHERE acl.defaclacl::text ~ '.*\\\\\"${database_read_only_username}\\\\\"=r/${database_username}\\\".*'
AND nspname = '${schema}'",
}

postgresql_psql {"grant default usage permission for ${database_read_only_username}":
db => $database_name,
command => "ALTER DEFAULT PRIVILEGES
FOR USER \"${database_username}\"
IN SCHEMA \"${schema}\"
GRANT USAGE ON SEQUENCES
TO \"${database_read_only_username}\"",
unless => "SELECT
ns.nspname,
acl.defaclobjtype,
acl.defaclacl
FROM pg_default_acl acl
JOIN pg_namespace ns ON acl.defaclnamespace=ns.oid
WHERE acl.defaclacl::text ~ '.*\\\\\"${database_read_only_username}\\\\\"=U/${database_username}\\\".*'
AND nspname = '${schema}'",
}

postgresql_psql {"grant default execute permission for ${database_read_only_username}":
db => $database_name,
command => "ALTER DEFAULT PRIVILEGES
FOR USER \"${database_username}\"
IN SCHEMA \"${schema}\"
GRANT EXECUTE ON FUNCTIONS
TO \"${database_read_only_username}\"",
unless => "SELECT
ns.nspname,
acl.defaclobjtype,
acl.defaclacl
FROM pg_default_acl acl
JOIN pg_namespace ns ON acl.defaclnamespace=ns.oid
WHERE acl.defaclacl::text ~ '.*\\\\\"${database_read_only_username}\\\\\"=X/${database_username}\\\".*'
AND nspname = '${schema}'",
}
}
45 changes: 41 additions & 4 deletions manifests/database/postgresql.pp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Class for creating the PuppetDB postgresql database. See README.md for more
# information.
class puppetdb::database::postgresql(
class puppetdb::database::postgresql (
$listen_addresses = $puppetdb::params::database_host,
$puppetdb_server = $puppetdb::params::puppetdb_server,
$database_name = $puppetdb::params::database_name,
Expand All @@ -14,7 +14,10 @@
$postgresql_ssl_on = $puppetdb::params::postgresql_ssl_on,
$postgresql_ssl_key_path = $puppetdb::params::postgresql_ssl_key_path,
$postgresql_ssl_cert_path = $puppetdb::params::postgresql_ssl_cert_path,
$postgresql_ssl_ca_cert_path = $puppetdb::params::postgresql_ssl_ca_cert_path
$postgresql_ssl_ca_cert_path = $puppetdb::params::postgresql_ssl_ca_cert_path,
$read_database_username = $puppetdb::params::read_database_username,
$read_database_password = $puppetdb::params::read_database_password,
$read_database_host = $puppetdb::params::read_database_host
) inherits puppetdb::params {

if $manage_server {
Expand All @@ -29,16 +32,27 @@
port => scanf($database_port, '%i')[0],
}

# We need to create the ssl connection for the read user, when
# manage_database is set to true, or when read_database_host is defined.
# Otherwise we don't create it.
if $manage_database or $read_database_host != undef{
$create_read_user_rule = true
} else {
$create_read_user_rule = false
}

# configure PostgreSQL communication with Puppet Agent SSL certificates if
# postgresql_ssl_on is set to true
if $postgresql_ssl_on {
class { 'puppetdb::database::ssl_configuration':
database_name => $database_name,
database_username => $database_username,
read_database_username => $read_database_username,
puppetdb_server => $puppetdb_server,
postgresql_ssl_key_path => $postgresql_ssl_key_path,
postgresql_ssl_cert_path => $postgresql_ssl_cert_path,
postgresql_ssl_ca_cert_path => $postgresql_ssl_ca_cert_path
postgresql_ssl_ca_cert_path => $postgresql_ssl_ca_cert_path,
create_read_user_rule => $create_read_user_rule
}
}

Expand All @@ -62,5 +76,28 @@
password => $database_password,
grant => 'all',
}

-> postgresql_psql { 'revoke all access on public schema':
db => $database_name,
command => 'REVOKE CREATE ON SCHEMA public FROM public',
unless => "SELECT * FROM
(SELECT has_schema_privilege('public', 'public', 'create') can_create) privs
WHERE privs.can_create=false",
}

-> postgresql_psql { "grant all permissions to ${database_username}":
db => $database_name,
command => "GRANT CREATE ON SCHEMA public TO \"${database_username}\"",
unless => "SELECT * FROM
(SELECT has_schema_privilege('${database_username}', 'public', 'create') can_create) privs
WHERE privs.can_create=true",
}

-> puppetdb::database::read_only_user { $read_database_username:
read_database_username => $read_database_username,
database_name => $database_name,
password_hash => postgresql::postgresql_password($read_database_username, $read_database_password),
database_owner => $database_username
}
}
}
}
34 changes: 34 additions & 0 deletions manifests/database/postgresql_ssl_rules.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Private class for configuring the pg_ident.conf and pg_hba.conf files
define puppetdb::database::postgresql_ssl_rules (
String $database_name,
String $database_username,
String $puppetdb_server,
) {
$identity_map_key = "${database_name}-${database_username}-map"

postgresql::server::pg_hba_rule { "Allow certificate mapped connections to ${database_name} as ${database_username} (ipv4)":
type => 'hostssl',
database => $database_name,
user => $database_username,
address => '0.0.0.0/0',
auth_method => 'cert',
order => 0,
auth_option => "map=${identity_map_key} clientcert=1"
}

postgresql::server::pg_hba_rule { "Allow certificate mapped connections to ${database_name} as ${database_username} (ipv6)":
type => 'hostssl',
database => $database_name,
user => $database_username,
address => '::0/0',
auth_method => 'cert',
order => 0,
auth_option => "map=${identity_map_key} clientcert=1"
}

postgresql::server::pg_ident_rule { "Map the SSL certificate of the server as a ${database_username} user":
map_name => $identity_map_key,
system_username => $puppetdb_server,
database_username => $database_username,
}
}
50 changes: 50 additions & 0 deletions manifests/database/read_grant.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Private class. Grant read-only permissions to $database_read_only_username for all objects in $schema of
# $database_name
define puppetdb::database::read_grant (
String $database_name,
String $schema,
String $database_read_only_username,
) {
postgresql_psql { "grant select permission for ${database_read_only_username}":
db => $database_name,
command => "GRANT SELECT
ON ALL TABLES IN SCHEMA \"${schema}\"
TO \"${database_read_only_username}\"",
unless => "SELECT * FROM (
SELECT COUNT(*)
FROM pg_tables
WHERE schemaname='public'
AND has_table_privilege('${database_read_only_username}', schemaname || '.' || tablename, 'SELECT')=false
) x
WHERE x.count=0",
}

postgresql_psql { "grant usage permission for ${database_read_only_username}":
db => $database_name,
command => "GRANT USAGE
ON ALL SEQUENCES IN SCHEMA \"${schema}\"
TO \"${database_read_only_username}\"",
unless => "SELECT * FROM (
SELECT COUNT(*)
FROM information_schema.sequences
WHERE sequence_schema='public'
AND has_sequence_privilege('${database_read_only_username}', sequence_schema || '.' || sequence_name, 'USAGE')=false
) x
WHERE x.count=0",
}

postgresql_psql { "grant execution permission for ${database_read_only_username}":
db => $database_name,
command => "GRANT EXECUTE
ON ALL FUNCTIONS IN SCHEMA \"${schema}\"
TO \"${database_read_only_username}\"",
unless => "SELECT * FROM (
SELECT COUNT(*)
FROM pg_catalog.pg_proc p
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname='public'
AND has_function_privilege('${database_read_only_username}', p.oid, 'EXECUTE')=false
) x
WHERE x.count=0",
}
}
44 changes: 44 additions & 0 deletions manifests/database/read_only_user.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Private class
# A define type to manage the creation of a read-only postgres users.
# In particular, it manages the necessary grants to enable such a user
# to have read-only access to any existing objects as well as changes
# the default access privileges so read-only access is maintained when
# new objects are created by the $database_owner
#
# @param database_read_only_username [String] The name of the postgres read only user.
# @param database [String] The name of the database to grant access to.
# @param database_owner [String] The user which owns the database (i.e. the migration user
# for the database).
# @param password_hash [String] The value of $_database_password in app_database.

define puppetdb::database::read_only_user (
String $read_database_username,
String $database_name,
String $database_owner,
Variant[String, Boolean] $password_hash = false,
) {
postgresql::server::role { $read_database_username:
password_hash => $password_hash,
}

-> postgresql::server::database_grant { "${database_name} grant connection permission to ${read_database_username}":
privilege => 'CONNECT',
db => $database_name,
role => $read_database_username,
}

-> puppetdb::database::default_read_grant {
"${database_name} grant read permission on new objects from ${database_owner} to ${read_database_username}":
database_username => $database_owner,
database_read_only_username => $read_database_username,
database_name => $database_name,
schema => 'public',
}

-> puppetdb::database::read_grant {
"${database_name} grant read-only permission on existing objects to ${read_database_username}":
database_read_only_username => $read_database_username,
database_name => $database_name,
schema => 'public',
}
}
Loading