V-72865
Severity: Medium
Generated
2019-05-20 15:48:11.984914
Status
The role(s)/group(s) used to modify database structure (including but not necessarily limited to tables, indexes, storage, etc.) and logic modules (functions, trigger procedures, links to software external to PostgreSQL, etc.) must be restricted to authorized users.
NIST 800-53
STIG # | Description | Result |
---|---|---|
CM-5 | CM-5: Access Restrictions For Change | passed |
Guidance
If PostgreSQL were to allow any user to make changes to database structure or logic, those changes might be implemented without undergoing the appropriate testing and approvals that are part of a robust change management process.
Accordingly, only qualified and authorized individuals must be allowed
to obtain access to information system components for purposes of
initiating changes, including upgrades and modifications.
Unmanaged changes that occur to the database software libraries or
configuration can lead to unauthorized or compromised installations.
Check
Note: The following instructions use the PGDATA environment variable. See supplementary content APPENDIX-F for instructions on configuring PGDATA.
As the database administrator (shown here as "postgres"),
list all users and their permissions by running the following
SQL:
$ sudo su - postgres
$ psql -c "\dp *.*"
Verify that all objects have the correct privileges. If they do
not, this is a finding.
Next, as the database administrator (shown here as "postgres"),
verify the permissions of the database directory on the
filesystem:
$ ls -la ${PGDATA?}
If permissions of the database directory are not limited to an
authorized user account, this is a finding.
Fix
As the database administrator, revoke any permissions from a role that are deemed unnecessary by running the following SQL:
ALTER ROLE bob NOCREATEDB;
ALTER ROLE bob NOCREATEROLE;
ALTER ROLE bob NOSUPERUSER;
ALTER ROLE bob NOINHERIT;
REVOKE SELECT ON some_function FROM bob;
Test Results
Result | |
---|---|
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
PostgreSQL query with errors: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should match /FATAL:\s+database "template0" is not currently accepting connections/ | passed |
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
PostgreSQL query: SELECT n.nspname, c.relname, c.relkind, pg_catalog.array_to_string(c.relacl, E',') FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) AND pg_catalog.array_to_string(c.relacl, E',') !~ '^((((testuser|dashboard|replication|vcap|crunchy)=[arwdDxtU]+|=[r]+)\/\w+,?)+|)$' AND c.relname NOT IN ('pg_settings'); output should eq "" | passed |
Directory /var/vcap/store/postgresql/data should be directory | passed |
Directory /var/vcap/store/postgresql/data should be owned by "vcap" | passed |
Directory /var/vcap/store/postgresql/data mode should cmp == "0700" | passed |
Code
control "V-72865" do
# @todo update the title of this control to something sane
title "The role(s)/group(s) used to modify database structure (including but
not necessarily limited to tables, indexes, storage, etc.) and logic
modules (functions, trigger procedures, links to software external to
PostgreSQL, etc.) must be restricted to authorized users."
desc "If PostgreSQL were to allow any user to make changes to database
structure or logic, those changes might be implemented without
undergoing the appropriate testing and approvals that are part of a
robust change management process.
Accordingly, only qualified and authorized individuals must be allowed
to obtain access to information system components for purposes of
initiating changes, including upgrades and modifications.
Unmanaged changes that occur to the database software libraries or
configuration can lead to unauthorized or compromised installations."
impact 0.5
tag "severity": "medium"
tag "gtitle": "SRG-APP-000133-DB-000362"
tag "gid": "V-72865"
tag "rid": "SV-87517r1_rule"
tag "stig_id": "PGS9-00-001300"
tag "cci": "CCI-001499"
tag "nist": ["CM-5 (6)", "Rev_4"]
tag "check": "Note: The following instructions use the PGDATA environment
variable. See supplementary content APPENDIX-F for instructions
on configuring PGDATA.
As the database administrator (shown here as \"postgres\"),
list all users and their permissions by running the following
SQL:
$ sudo su - postgres
$ psql -c \"\\dp *.*\"
Verify that all objects have the correct privileges. If they do
not, this is a finding.
Next, as the database administrator (shown here as \"postgres\"),
verify the permissions of the database directory on the
filesystem:
$ ls -la ${PGDATA?}
If permissions of the database directory are not limited to an
authorized user account, this is a finding."
tag "fix": "As the database administrator, revoke any permissions from a role
that are deemed unnecessary by running the following SQL:
ALTER ROLE bob NOCREATEDB;
ALTER ROLE bob NOCREATEROLE;
ALTER ROLE bob NOSUPERUSER;
ALTER ROLE bob NOINHERIT;
REVOKE SELECT ON some_function FROM bob;"
exceptions = "#{PG_OBJECT_EXCEPTIONS.map { |e| "'#{e}'" }.join(',')}"
sql = postgres_session(PG_DBA, PG_DBA_PASSWORD, PG_HOST)
binding_users = []
if not PG_BINDINGS_DB.empty? and not PG_BINDING_USERS_SQL.empty?
binding_users_query = sql.query(PG_BINDING_USERS_SQL, [PG_BINDINGS_DB])
binding_users = binding_users_query.lines
end
authorized_owners = PG_OBJECT_OWNERS + binding_users
owners = authorized_owners.join('|')
superusers = PG_SUPERUSERS.join('|')
databases_sql = 'SELECT datname FROM pg_catalog.pg_database;'
databases_query = sql.query(databases_sql, [PG_DB])
databases = databases_query.lines
databases.each do |database|
role_list = database == 'postgres' ? superusers : owners
object_acl = "^((((#{role_list})=[#{PG_OBJECT_GRANTED_PRIVILEGES}]+|"\
"=[#{PG_OBJECT_PUBLIC_PRIVILEGES}]+)\\/\\w+,?)+|)$"
objects_sql = "SELECT n.nspname, c.relname, c.relkind, "\
"pg_catalog.array_to_string(c.relacl, E',') "\
"FROM pg_catalog.pg_class c "\
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "\
"WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f') "\
"AND n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid) "\
"AND pg_catalog.array_to_string(c.relacl, E',') !~ '#{object_acl}' "\
"AND c.relname NOT IN (#{exceptions});"
connection_error = "FATAL:\\s+database \"#{database}\" is not currently "\
"accepting connections"
connection_error_regex = Regexp.new(connection_error)
describe.one do
describe sql.query(objects_sql, [database]) do
its('output') { should eq '' }
end
describe sql.query(objects_sql, [database]) do
it { should match connection_error_regex }
end
end
# TODO: Add test for column acl
end
describe directory(PG_DATA) do
it { should be_directory }
it { should be_owned_by PG_OWNER }
its('mode') { should cmp '0700' }
end
end