Skip to content

Commit

Permalink
PG-853: Access control of pg_tde SQL functions (#277)
Browse files Browse the repository at this point in the history
* PG-853: Access control of pg_tde SQL functions

Add SQL interfaces for granting and revoking access to key management and viewer
functions. This commit introduces four new SQL functions to manage access to
key-related functionalities in the `pg_tde` extension:

- `pg_tde_grant_key_management_to_role`: Grants execute permissions on key
    management functions to the specified user or role.
- `pg_tde_revoke_key_management_from_role`: Revokes execute permissions on
    key management functions from the specified user or role.
- `pg_tde_grant_key_viewer_to_role`: Grants execute permissions on key
    viewer functions to the specified user or role.
- `pg_tde_revoke_key_viewer_from_role`: Revokes execute permissions on
    key viewer functions from the specified user or role.

Additionally, upon creating the extension, all execute permissions are revoked
from the `PUBLIC` role. Therefore, a superuser must explicitly grant the
necessary permissions to non-superusers to access these functions after the
extension is created.

These additions provide a more controlled and secure way to manage permissions
for key management and viewer functionalities within the extension.
  • Loading branch information
codeforall authored Sep 9, 2024
1 parent 4b0a813 commit 19f722e
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 1 deletion.
3 changes: 2 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ tests += {
't/003_remote_config.pl',
't/004_file_config.pl',
't/005_multiple_extensions.pl',
't/006_remote_vault_config.pl'
't/006_remote_vault_config.pl',
't/007_access_control.pl'
],
},
}
180 changes: 180 additions & 0 deletions pg_tde--1.0.sql
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,183 @@ $$;
-- Per database extension initialization
SELECT pg_tde_extension_initialize();


CREATE OR REPLACE FUNCTION pg_tde_grant_execute_privilege_on_function(
target_user_or_role TEXT,
target_function_name TEXT,
target_function_args TEXT
)
RETURNS BOOLEAN AS $$
DECLARE
grant_query TEXT;
BEGIN
-- Construct the GRANT statement
grant_query := format('GRANT EXECUTE ON FUNCTION %I(%s) TO %I;',
target_function_name, target_function_args, target_user_or_role);

-- Execute the GRANT statement
EXECUTE grant_query;
-- If execution reaches here, it means the query was successful
RETURN TRUE;

END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION pg_tde_revoke_execute_privilege_on_function(
target_user_or_role TEXT,
target_function_name TEXT,
argument_types TEXT
)
RETURNS BOOLEAN AS $$
DECLARE
revoke_query TEXT;
BEGIN
-- Construct the REVOKE statement
revoke_query := format('REVOKE EXECUTE ON FUNCTION %I(%s) FROM %I;',
target_function_name, argument_types, target_user_or_role);

-- Execute the REVOKE statement
EXECUTE revoke_query;

-- If execution reaches here, it means the query was successful
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION pg_tde_grant_key_management_to_role(
target_user_or_role TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
BEGIN
-- Start the transaction block for performing grants
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'pg_tde_global, varchar, json');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'pg_tde_global, varchar, text');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'varchar, json');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'varchar, text');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_internal', 'varchar, varchar, JSON, BOOLEAN');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider', 'varchar, varchar, JSON');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'pg_tde_global, varchar, text, text,text,text');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'pg_tde_global, varchar, JSON, JSON,JSON,JSON');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'varchar, text, text,text,text');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'varchar, JSON, JSON,JSON,JSON');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_set_principal_key', 'varchar, varchar, BOOLEAN');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'pg_tde_global, varchar, varchar');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'varchar, varchar');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key_internal', 'varchar, varchar, BOOLEAN, BOOLEAN');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_grant_key_management_to_role', 'TEXT');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_revoke_key_management_from_role', 'TEXT');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_grant_key_viewer_to_role', 'TEXT');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_revoke_key_viewer_from_role', 'TEXT');

PERFORM pg_tde_grant_key_viewer_to_role(target_user_or_role);

RETURN TRUE;

EXCEPTION
-- If any error occurs, re-raise the error to roll back the transaction
WHEN OTHERS THEN
RAISE;
END;
$$;

CREATE OR REPLACE FUNCTION pg_tde_grant_key_viewer_to_role(
target_user_or_role TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
BEGIN
-- Start the transaction block for performing grants
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_list_all_key_providers', 'OUT INT, OUT varchar, OUT varchar, OUT JSON');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_is_encrypted', 'VARCHAR');

PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_principal_key_info_internal', 'BOOLEAN');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_principal_key_info', '');
PERFORM pg_tde_grant_execute_privilege_on_function(target_user_or_role, 'pg_tde_principal_key_info', 'pg_tde_global');
-- If all statements succeed, return TRUE
RETURN TRUE;

EXCEPTION
-- If any error occurs, re-raise the error to roll back the transaction
WHEN OTHERS THEN
RAISE;
END;
$$;



CREATE OR REPLACE FUNCTION pg_tde_revoke_key_management_from_role(
target_user_or_role TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
BEGIN
-- Start the transaction block for performing grants
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'pg_tde_global, varchar, json');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'pg_tde_global, varchar, text');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'varchar, json');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_file', 'varchar, text');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_internal', 'varchar, varchar, JSON, BOOLEAN');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider', 'varchar, varchar, JSON');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'pg_tde_global, varchar, text, text,text,text');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'pg_tde_global, varchar, JSON, JSON,JSON,JSON');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'varchar, text, text,text,text');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_add_key_provider_vault_v2', 'varchar, JSON, JSON,JSON,JSON');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_set_principal_key', 'varchar, varchar, BOOLEAN');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'pg_tde_global, varchar, varchar');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key', 'varchar, varchar');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_rotate_principal_key_internal', 'varchar, varchar, BOOLEAN, BOOLEAN');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_grant_key_management_to_role', 'TEXT');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_revoke_key_management_from_role', 'TEXT');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_grant_key_viewer_to_role', 'TEXT');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_revoke_key_viewer_from_role', 'TEXT');

-- If all statements succeed, return TRUE
RETURN TRUE;

EXCEPTION
-- If any error occurs, re-raise the error to roll back the transaction
WHEN OTHERS THEN
RAISE;
END;
$$;

CREATE OR REPLACE FUNCTION pg_tde_revoke_key_viewer_from_role(
target_user_or_role TEXT)
RETURNS BOOLEAN
LANGUAGE plpgsql
AS $$
BEGIN
-- Start the transaction block for performing grants
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_list_all_key_providers', 'OUT INT, OUT varchar, OUT varchar, OUT JSON');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_is_encrypted', 'VARCHAR');

PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_principal_key_info_internal', 'BOOLEAN');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_principal_key_info', '');
PERFORM pg_tde_revoke_execute_privilege_on_function(target_user_or_role, 'pg_tde_principal_key_info', 'pg_tde_global');
-- If all statements succeed, return TRUE
RETURN TRUE;

EXCEPTION
-- If any error occurs, re-raise the error to roll back the transaction
WHEN OTHERS THEN
RAISE;
END;
$$;

-- Revoking all the privileges from the public role
SELECT pg_tde_revoke_key_management_from_role('public');
SELECT pg_tde_revoke_key_viewer_from_role('public');
129 changes: 129 additions & 0 deletions t/007_access_control.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/perl

use strict;
use warnings;
use File::Basename;
use File::Compare;
use File::Copy;
use Test::More;
use lib 't';
use pgtde;

# Get file name and CREATE out file name and dirs WHERE requried
PGTDE::setup_files_dir(basename($0));

# CREATE new PostgreSQL node and do initdb
my $node = PGTDE->pgtde_init_pg();
my $pgdata = $node->data_dir;

# UPDATE postgresql.conf to include/load pg_tde library
open my $conf, '>>', "$pgdata/postgresql.conf";
print $conf "shared_preload_libraries = 'pg_tde'\n";
close $conf;

# Start server
my $rt_value = $node->start;
ok($rt_value == 1, "Start Server");

# CREATE EXTENSION and change out file permissions
my ($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE EXTENSION pg_tde;', extra_params => ['-a']);
ok($cmdret == 0, "CREATE PGTDE EXTENSION");
PGTDE::append_to_file($stdout);


($cmdret, $stdout, $stderr) = $node->psql('postgres', 'CREATE USER test_access;', extra_params => ['-a']);
ok($cmdret == 0, "CREATE test_access USER");
PGTDE::append_to_file($stdout);

($cmdret, $stdout, $stderr) = $node->psql('postgres', 'grant all ON database postgres TO test_access;', extra_params => ['-a']);
ok($cmdret == 0, "CREATE test_access USER");
PGTDE::append_to_file($stdout);

# Restart the server
PGTDE::append_to_file("-- server restart");
$node->stop();

$rt_value = $node->start();
ok($rt_value == 1, "Restart Server");

# TRY performing operations without permission
PGTDE::append_to_file("-- pg_tde_add_key_provider_file should throw access denied");
($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stderr);

PGTDE::append_to_file("-- pg_tde_set_principal_key should also fail");
($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stderr);

PGTDE::append_to_file("-- pg_tde_rotate_principal_key should give access denied error");
($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_rotate_principal_key('rotated-principal-key','file-2');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stderr);


# now give key management access to test_access user
PGTDE::append_to_file("-- grant key management access to test_access");
$stdout = $node->safe_psql('postgres', "select pg_tde_grant_key_management_to_role('test_access');", extra_params => ['-a']);
PGTDE::append_to_file($stdout);

# TRY performing key operation with permission
$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($cmdret);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', "SELECT pg_tde_add_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', "SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', "SELECT pg_tde_rotate_principal_key('rotated-principal-key','file-2');", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', "SELECT principal_key_name,key_provider_name,key_provider_id,principal_key_internal_name, principal_key_version from pg_tde_principal_key_info();", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($cmdret);


$stdout = $node->safe_psql('postgres', "SELECT pg_tde_list_all_key_providers();", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

# Now revoke the view access from test_access user
$stdout = $node->safe_psql('postgres', "select pg_tde_revoke_key_viewer_from_role('test_access');", extra_params => ['-a']);

# verify the view access is revoked

PGTDE::append_to_file("-- pg_tde_list_all_key_providers should also fail");
($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT pg_tde_list_all_key_providers();", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stderr);

PGTDE::append_to_file("-- pg_tde_principal_key_info should also fail");
($cmdret, $stdout, $stderr) = $node->psql('postgres', "SELECT principal_key_name,key_provider_name,key_provider_id,principal_key_internal_name, principal_key_version from pg_tde_principal_key_info();", extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stderr);


$stdout = $node->safe_psql('postgres', 'CREATE SCHEMA test_access;', extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', 'CREATE TABLE test_access.test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap_basic;', extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', 'INSERT INTO test_access.test_enc1 (k) VALUES (5),(6);', extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

$stdout = $node->safe_psql('postgres', 'SELECT * FROM test_access.test_enc1 ORDER BY id ASC;', extra_params => ['-a', '-U', 'test_access']);
PGTDE::append_to_file($stdout);

# DROP EXTENSION
$stdout = $node->safe_psql('postgres', 'DROP EXTENSION pg_tde CASCADE;', extra_params => ['-a']);
PGTDE::append_to_file($stdout);

# Stop the server
$node->stop();

# compare the expected and out file
my $compare = PGTDE->compare_results();

# Test/check if expected and result/out file match. If Yes, test passes.
is($compare,0,"Compare Files: $PGTDE::expected_filename_with_path and $PGTDE::out_filename_with_path files.");

# Done testing for this testcase file.
done_testing();
37 changes: 37 additions & 0 deletions t/expected/007_access_control.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
CREATE EXTENSION pg_tde;
CREATE USER test_access;
grant all ON database postgres TO test_access;
-- server restart
-- pg_tde_add_key_provider_file should throw access denied
psql:<stdin>:1: ERROR: permission denied for function pg_tde_add_key_provider_file
-- pg_tde_set_principal_key should also fail
psql:<stdin>:1: ERROR: permission denied for function pg_tde_set_principal_key
-- pg_tde_rotate_principal_key should give access denied error
psql:<stdin>:1: ERROR: permission denied for function pg_tde_rotate_principal_key
-- grant key management access to test_access
select pg_tde_grant_key_management_to_role('test_access');
t
3
SELECT pg_tde_add_key_provider_file('file-vault','/tmp/pg_tde_test_keyring.per');
1
SELECT pg_tde_add_key_provider_file('file-2','/tmp/pg_tde_test_keyring_2.per');
2
SELECT pg_tde_set_principal_key('test-db-principal-key','file-vault');
t
SELECT pg_tde_rotate_principal_key('rotated-principal-key','file-2');
t
3
SELECT pg_tde_list_all_key_providers();
(1,file-vault,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring.per""}")
(2,file-2,file,"{""type"" : ""file"", ""path"" : ""/tmp/pg_tde_test_keyring_2.per""}")
-- pg_tde_list_all_key_providers should also fail
psql:<stdin>:1: ERROR: permission denied for function pg_tde_list_all_key_providers
-- pg_tde_principal_key_info should also fail
psql:<stdin>:1: ERROR: permission denied for function pg_tde_principal_key_info
CREATE SCHEMA test_access;
CREATE TABLE test_access.test_enc1(id SERIAL,k INTEGER,PRIMARY KEY (id)) USING tde_heap_basic;
INSERT INTO test_access.test_enc1 (k) VALUES (5),(6);
SELECT * FROM test_access.test_enc1 ORDER BY id ASC;
1|5
2|6
DROP EXTENSION pg_tde CASCADE;

0 comments on commit 19f722e

Please sign in to comment.