Skip to content
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

Add config var to enable or disable event trigger function. #94

Merged
merged 3 commits into from
Nov 1, 2023
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
96 changes: 96 additions & 0 deletions sql/pgsodium--3.1.8--3.1.9.sql
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,99 @@ $$
LANGUAGE plpgsql
SET search_path=''
;

CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update()
RETURNS EVENT_TRIGGER AS
$$
DECLARE
r record;
BEGIN
IF (SELECT bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN
RAISE NOTICE 'skipping pgsodium mask regeneration in extension';
RETURN;
ELSIF current_setting('pgsodium.enable_event_trigger') <> 'on' THEN
RAISE NOTICE 'skipping pgsodium mask regeneration due to false pgsodium.enable_event_trigger';
RETURN;
END IF;

FOR r IN
SELECT e.*
FROM pg_event_trigger_ddl_commands() e
WHERE EXISTS (
SELECT FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_seclabel s ON s.classoid = c.tableoid
AND s.objoid = c.oid
WHERE c.tableoid = e.classid
AND e.objid = c.oid
AND s.provider = 'pgsodium'
)
LOOP
IF r.object_type in ('table', 'table column')
THEN
PERFORM pgsodium.update_mask(r.objid);
END IF;
END LOOP;
END
$$
LANGUAGE plpgsql
SET search_path=''
;

CREATE OR REPLACE FUNCTION pgsodium.encrypted_column(relid OID, m record)
RETURNS TEXT AS
$$
DECLARE
expression TEXT;
BEGIN
expression := '';
IF m.format_type = 'text' THEN
expression := expression || format($f$
IF %1$s = '' THEN RAISE EXCEPTION 'Cannot encrypt empty string.'; END IF;
%1$s = CASE WHEN %1$s IS NULL THEN NULL ELSE
CASE WHEN %2$s IS NULL THEN NULL ELSE pg_catalog.encode(
pgsodium.crypto_aead_det_encrypt(
pg_catalog.convert_to(%1$s, 'utf8'),
pg_catalog.convert_to((%3$s)::text, 'utf8'),
%2$s::uuid,
%4$s
),
'base64') END END$f$,
'new.' || quote_ident(m.attname),
COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)),
COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')),
COALESCE('new.' || quote_ident(m.nonce_column), 'NULL')
);
ELSIF m.format_type = 'bytea' THEN
expression := expression || format($f$
IF %1$s = ''::bytea THEN RAISE EXCEPTION 'Cannot encrypt empty bytes.'; END IF;
%1$s = CASE WHEN %1$s IS NULL THEN NULL ELSE
CASE WHEN %2$s IS NULL THEN NULL ELSE
pgsodium.crypto_aead_det_encrypt(%1$s::bytea, pg_catalog.convert_to((%3$s)::text, 'utf8'),
%2$s::uuid,
%4$s
) END END$f$,
'new.' || quote_ident(m.attname),
COALESCE('new.' || quote_ident(m.key_id_column), quote_literal(m.key_id)),
COALESCE(pgsodium.quote_assoc(m.associated_columns, true), quote_literal('')),
COALESCE('new.' || quote_ident(m.nonce_column), 'NULL')
);
END IF;
RETURN expression;
END
$$
LANGUAGE plpgsql
VOLATILE
SET search_path=''
;


CREATE VIEW pgsodium.seclabel AS
SELECT nspname, relname, attname, label
FROM pg_seclabel sl,
pg_class c,
pg_attribute a,
pg_namespace n
WHERE sl.objoid = c.oid
AND c.oid = a.attrelid
AND a.attnum = sl.objsubid
AND n.oid = c.relnamespace;
13 changes: 12 additions & 1 deletion src/pgsodium.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ PG_MODULE_MAGIC;

bytea *pgsodium_secret_key;
static char *getkey_script = NULL;
static bool enable_event_trigger = true;

/*
* Checking the syntax of the masking rules
Expand Down Expand Up @@ -120,10 +121,20 @@ _PG_init (void)
/* Security label provider hook */
register_label_provider ("pgsodium", pgsodium_object_relabel);

// we're done if not preloaded, otherwise try to get internal shared key
// we're done if not preloaded
if (!process_shared_preload_libraries_in_progress)
return;

// Variable to enable/disable event trigger
DefineCustomBoolVariable("pgsodium.enable_event_trigger",
"Variable to enable/disable event trigger that regenerates triggers and views.",
NULL,
&enable_event_trigger,
true,
PGC_USERSET, 0,
NULL, NULL, NULL);

// try to get internal shared key
path = (char *) palloc0 (MAXPGPATH);
get_share_path (my_exec_path, sharepath);
snprintf (path, MAXPGPATH, "%s/extension/%s", sharepath, PG_GETKEY_EXEC);
Expand Down
10 changes: 6 additions & 4 deletions test/pgsodium_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ SELECT bag_eq($$
('view pgsodium.decrypted_key' ::text),
('view pgsodium.mask_columns' ::text),
('view pgsodium.masking_rule' ::text),
('view pgsodium.valid_key' ::text)
('view pgsodium.valid_key' ::text),
('view pgsodium.seclabel' ::text)
$$,
'Check extension object list');

Expand Down Expand Up @@ -460,7 +461,8 @@ SELECT views_are('pgsodium', ARRAY[
'decrypted_key',
'mask_columns',
'masking_rule',
'valid_key'
'valid_key',
'seclabel'
]);

---- VIEW decrypted_key
Expand Down Expand Up @@ -4968,7 +4970,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex
AND oidvectortypes(proargtypes) = '';

SELECT unnest(ARRAY[
is(md5(prosrc), 'b58694d2602515d557e8637d43b6df1a',
is(md5(prosrc), 'faacedb8c19aba1c5f9c7556d18c2286',
format('Function pgsodium.%s(%s) body should match checksum',
proname, pg_get_function_identity_arguments(oid))
),
Expand Down Expand Up @@ -5604,7 +5606,7 @@ SELECT function_privs_are('pgsodium'::name, proname, proargtypes::regtype[]::tex
AND oidvectortypes(proargtypes) = 'bytea';

SELECT unnest(ARRAY[
is(md5(prosrc), 'b8b02682e0138dc894512f55587db8d4',
is(md5(prosrc), '7e6641f8c9f661514f123598b1ca2448',
format('Function pgsodium.%s(%s) body should match checksum',
proname, pg_get_function_identity_arguments(oid))
),
Expand Down
94 changes: 84 additions & 10 deletions test/tce.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ SELECT throws_ok(
'quoted schema cannot be labled');

CREATE TABLE private.foo(
id integer,
secret text,
secretbytes bytea,
associated text default ''
);

Expand Down Expand Up @@ -86,7 +88,14 @@ SELECT lives_ok(
SECURITY LABEL FOR pgsodium ON COLUMN private.foo.secret
IS 'ENCRYPT WITH KEY ID %s'
$test$, :'secret_key_id'),
'can label column for encryption');
'can label string column for encryption');

SELECT lives_ok(
format($test$
SECURITY LABEL FOR pgsodium ON COLUMN private.foo.secretbytes
IS 'ENCRYPT WITH KEY ID %s'
$test$, :'secret_key_id'),
'can label bytea column for encryption');

SELECT lives_ok(
format($test$
Expand Down Expand Up @@ -139,16 +148,45 @@ select ok(has_table_privilege('bobo', 'private.bar', 'SELECT'),

select ok(has_table_privilege('bobo', 'private.other_bar', 'SELECT'),
'user keeps view select privs after regeneration');

select ok(has_table_privilege('bobo', 'private.other_bar', 'INSERT'),
'user keeps view insert privs after regeneration');

select ok(has_table_privilege('bobo', 'private.other_bar', 'UPDATE'),
'user keeps view update privs after regeneration');

select ok(has_table_privilege('bobo', 'private.other_bar', 'DELETE'),
'user keeps view delete privs after regeneration');

SET pgsodium.enable_event_trigger = 'off';

CREATE TABLE private.fooz(
secret text
);

SELECT lives_ok(
format($test$
SECURITY LABEL FOR pgsodium ON COLUMN private.fooz.secret
IS 'ENCRYPT WITH KEY ID %s'
$test$, :'secret_key_id'),
'can label column for encryption with event trigger disabled');

SELECT hasnt_view('private', 'decrypted_fooz', 'Dynamic view was not created due to disabled event trigger.');

SELECT hasnt_trigger('private', 'fooz', 'fooz_encrypt_secret_trigger_secret',
'Dynamic trigger was not created due to disabled event trigger.');

SELECT lives_ok(
$test$SELECT pgsodium.update_mask('private.fooz'::regclass);$test$,
'can manually create trigger and view with event trigger disabled.');

SELECT has_view('private', 'decrypted_fooz', 'Dynamic view was created manually.');

SELECT has_trigger('private', 'fooz', 'fooz_encrypt_secret_trigger_secret',
'Dynamic trigger was created manually.');

RESET pgsodium.enable_event_trigger;

SET SESSION AUTHORIZATION bobo;
SET ROLE bobo;

Expand All @@ -158,20 +196,56 @@ SELECT pgsodium.crypto_aead_det_noncegen() nonce2 \gset
SELECT lives_ok(
format(
$test$
INSERT INTO private.decrypted_foo (secret) VALUES ('s3kr3t');
INSERT INTO private.decrypted_foo (id, secret) VALUES (1, 's3kr3t');
$test$),
'can insert into decrypted view');
'can insert string into decrypted view');

SELECT lives_ok(
format(
$test$
INSERT INTO private.decrypted_foo (id, secretbytes) VALUES (2, 's3kr3t'::bytea);
$test$),
'can insert bytes into decrypted view');

SELECT throws_ok(
format(
$test$
INSERT INTO private.decrypted_foo (id, secret) VALUES (3, '');
$test$),
'P0001',
'Cannot encrypt empty string.',
'cannot insert empty string into decrypted view');

SELECT throws_ok(
format(
$test$
INSERT INTO private.decrypted_foo (id, secretbytes) VALUES (4, ''::bytea);
$test$),
'P0001',
'Cannot encrypt empty bytes.',
'cannot insert empty bytes into decrypted view');

SELECT lives_ok(
format(
$test$
UPDATE private.decrypted_foo SET secret = 'sp00n' WHERE id = 1;
$test$),
'can update string into decrypted view');

SELECT results_eq($$SELECT decrypted_secret = 'sp00n' from private.decrypted_foo WHERE id = 1$$,
$$VALUES (true)$$,
'can see updated string in decrypted view');

SELECT lives_ok(
format(
$test$
UPDATE private.decrypted_foo SET secret = 'sp00n';
UPDATE private.decrypted_foo SET secretbytes = 'sp00nb' WHERE id = 2;
$test$),
'can update into decrypted view');
'can update bytes into decrypted view');

SELECT results_eq($$SELECT decrypted_secret = 'sp00n' from private.decrypted_foo$$,
SELECT results_eq($$SELECT decrypted_secretbytes = 'sp00nb' from private.decrypted_foo WHERE id = 2$$,
$$VALUES (true)$$,
'can see updated decrypted view');
'can see updated bytes in decrypted view');

SELECT lives_ok(
$test$
Expand Down