Skip to content

Commit 62611ad

Browse files
authored
Merge branch 'main' into feature/windows_port
2 parents 3a34bbd + 54ee412 commit 62611ad

32 files changed

+482
-203
lines changed

Dockerfile

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ RUN chown postgres:postgres /home/postgres
2727

2828
RUN curl -s -L https://github.com/theory/pgtap/archive/v1.2.0.tar.gz | tar zxvf - && cd pgtap-1.2.0 && make && make install
2929
RUN curl -s -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz | tar zxvf - && cd libsodium-1.0.18 && ./configure && make check && make -j 4 install
30-
RUN cpan App::cpanminus && cpan TAP::Parser::SourceHandler::pgTAP
30+
RUN cpan App::cpanminus && cpan TAP::Parser::SourceHandler::pgTAP && cpan App::prove
31+
32+
RUN git clone --depth 1 https://github.com/lacanoid/pgddl.git
33+
RUN cd pgddl && make && make install && cd ..
34+
3135
RUN mkdir "/home/postgres/pgsodium"
3236
WORKDIR "/home/postgres/pgsodium"
3337
COPY . .

META.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "pgsodium",
33
"abstract": "Postgres extension for libsodium functions",
44
"description": "pgsodium is a PostgreSQL extension that exposes modern libsodium based cryptographic functions to SQL.",
5-
"version": "3.1.5",
5+
"version": "3.1.9",
66
"maintainer": [
77
"Michel Pelletier <[email protected]>"
88
],
@@ -13,7 +13,7 @@
1313
"abstract": "Postgres extension for libsodium functions",
1414
"file": "src/pgsodium.h",
1515
"docfile": "README.md",
16-
"version": "3.1.5"
16+
"version": "3.1.9"
1717
}
1818
},
1919
"prereqs": {

example/tce.sql

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ CREATE SCHEMA "tce-example";
1010
SET search_path = "tce-example", pg_catalog;
1111

1212
CREATE TABLE test (
13-
secret text
13+
secret text,
14+
name text unique
1415
);
1516

1617
CREATE TABLE test2 (
@@ -73,6 +74,6 @@ CREATE TABLE "tce-example"."bob-testt" (
7374
);
7475

7576
SECURITY LABEL FOR pgsodium ON COLUMN "bob-testt"."secret2-test" IS
76-
'ENCRYPT WITH KEY COLUMN secret2_key_id-test ASSOCIATED (associated2-test) NONCE nonce2-test';
77+
'ENCRYPT WITH KEY COLUMN secret2_key_id-test ASSOCIATED (associated2-test) NONCE nonce2-test SECURITY INVOKER';
7778

7879
select pgsodium.update_masks(true);

pgsodium.control

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pgsodium extension
22
comment = 'Postgres extension for libsodium functions'
3-
default_version = '3.1.5'
3+
default_version = '3.1.9'
44
relocatable = false
55
schema = pgsodium

pgsodium_tapgen.pl

+20-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Getopt::Long;
88
use File::Spec;
99

10-
my $PGSODIUM_VERSION = '3.1.5';
10+
my $PGSODIUM_VERSION = '3.1.7';
1111

1212
my $curr;
1313
my $rs;
@@ -46,12 +46,15 @@
4646

4747
################################################################################
4848

49-
print "BEGIN;\n",
50-
"CREATE EXTENSION IF NOT EXISTS pgtap;\n",
51-
"CREATE EXTENSION IF NOT EXISTS pgsodium;\n\n",
52-
"SET search_path TO 'public';\n\n";
49+
print "SET search_path TO 'public';\n";
5350

54-
print "SELECT plan(1); -- FIXME!\n";
51+
print "\n\n\n---- POSTGRESQL MINIMAL VERSION\n";
52+
print "SELECT cmp_ok("
53+
."current_setting('server_version_num')::int, "
54+
."'>=', "
55+
."130000, "
56+
."format('PostgreSQL version %s >= 13', current_setting('server_version'))"
57+
.");\n";
5558

5659
print "\n\n\n---- EXTENSION VERSION\n";
5760

@@ -71,16 +74,15 @@
7174
WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass
7275
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'pgsodium')
7376
AND deptype = 'e'
74-
ORDER BY 1;
77+
ORDER BY pg_catalog.pg_describe_object(classid, objid, 0) COLLATE "C"
7578
}) or die;
7679

77-
print q{SELECT results_eq($$
80+
print q{SELECT bag_eq($$
7881
SELECT pg_catalog.pg_describe_object(classid, objid, 0)
7982
FROM pg_catalog.pg_depend
8083
WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass
8184
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'pgsodium')
82-
AND deptype = 'e'
83-
ORDER BY 1$$,
85+
AND deptype = 'e'$$,
8486
$$ VALUES
8587
}, join(",\n ", @$rs), q{
8688
$$,
@@ -113,30 +115,27 @@
113115
printf "SELECT is_member_of( %s, %s );\n", $r->[0], $r->[1];
114116
}
115117

118+
print "\n\n\n---- SCHEMAS\n\n";
119+
116120
$rs = $dbh->selectall_arrayref(q{
117121
SELECT quote_literal(nspname),
118122
quote_literal(pg_catalog.pg_get_userbyid(nspowner))
119123
FROM pg_catalog.pg_namespace
120-
WHERE nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
124+
WHERE nspname NOT IN ('public', 'pg_catalog', 'pg_toast', 'information_schema')
121125
ORDER BY nspname
122126
}) or die;
123127

124-
print "\n\n\n---- SCHEMAS\n\n";
125-
126-
print "SELECT schemas_are(ARRAY[\n ",
127-
join(",\n ", map {$_->[0]} @$rs),
128-
"\n]);\n";
129-
130128
foreach my $r ( @$rs ) {
131-
printf "SELECT schema_owner_is(%-10s, %s);\n", @$r;
129+
printf "SELECT has_schema(%s);\n", $r->[0];
130+
printf "SELECT schema_owner_is(%s, %s);\n\n", @$r;
132131
}
133132

134133
print "\n\n\n---- EVENT TRIGGERS\n\n";
135134

136135
# pgtap doesn't support event triggers yet.
137136
$rs = $dbh->selectall_arrayref(q{
138137
SELECT quote_literal(evtname), quote_literal(evtevent),
139-
quote_literal(evtenabled), ARRAY(SELECT quote_literal(unnest(evttags)) ORDER BY 1),
138+
quote_literal(evtenabled::text), ARRAY(SELECT quote_literal(unnest(evttags)) ORDER BY 1),
140139
quote_literal(pg_catalog.pg_get_userbyid(evtowner)),
141140
quote_literal(evtfoid::regproc)
142141
FROM pg_catalog.pg_event_trigger
@@ -429,8 +428,6 @@
429428
"]);\n";
430429
}
431430

432-
print "\n\nROLLBACK;\n";
433-
434431
$dbh->rollback;
435432

436433
exit;
@@ -528,6 +525,7 @@ sub privs_tests {
528525
WHEN r.relkind = 'S' THEN has_sequence_privilege(a.oid, r.oid, s.p)
529526
END
530527
AND s.k = r.relkind
528+
AND a.rolname NOT IN ('pg_read_all_data', 'pg_write_all_data')
531529
GROUP BY a.rolname, r.nspname, r.relname
532530
ORDER BY a.rolname}, undef, $schema, $tname);
533531

@@ -542,7 +540,7 @@ sub privs_tests {
542540
."FROM pg_catalog.pg_roles\n"
543541
."WHERE rolname NOT IN (%s);\n",
544542
$type, $privs->[0][2], $privs->[0][3],
545-
join(',', map { $_->[0] } @$privs);
543+
join(',', ("'pg_read_all_data'", "'pg_write_all_data'", map { $_->[0] } @$privs ));
546544
}
547545

548546
sub idxs_tests {

sql/pgsodium--3.1.5--3.1.6.sql

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
SELECT pg_catalog.pg_extension_config_dump('pgsodium.key_key_id_seq', '');

sql/pgsodium--3.1.6--3.1.7.sql

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
CREATE OR REPLACE VIEW pgsodium.masking_rule AS
2+
WITH const AS (
3+
SELECT
4+
'encrypt +with +key +id +([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})'
5+
AS pattern_key_id,
6+
'encrypt +with +key +column +([\w\"\-$]+)'
7+
AS pattern_key_id_column,
8+
'(?<=associated) +\(([\w\"\-$, ]+)\)'
9+
AS pattern_associated_columns,
10+
'(?<=nonce) +([\w\"\-$]+)'
11+
AS pattern_nonce_column,
12+
'(?<=decrypt with view) +([\w\"\-$]+\.[\w\"\-$]+)'
13+
AS pattern_view_name,
14+
'(?<=security invoker)'
15+
AS pattern_security_invoker
16+
),
17+
rules_from_seclabels AS (
18+
SELECT
19+
sl.objoid AS attrelid,
20+
sl.objsubid AS attnum,
21+
c.relnamespace::regnamespace,
22+
c.relname,
23+
a.attname,
24+
pg_catalog.format_type(a.atttypid, a.atttypmod),
25+
sl.label AS col_description,
26+
(regexp_match(sl.label, k.pattern_key_id_column, 'i'))[1] AS key_id_column,
27+
(regexp_match(sl.label, k.pattern_key_id, 'i'))[1] AS key_id,
28+
(regexp_match(sl.label, k.pattern_associated_columns, 'i'))[1] AS associated_columns,
29+
(regexp_match(sl.label, k.pattern_nonce_column, 'i'))[1] AS nonce_column,
30+
coalesce((regexp_match(sl2.label, k.pattern_view_name, 'i'))[1],
31+
c.relnamespace::regnamespace || '.' || quote_ident('decrypted_' || c.relname)) AS view_name,
32+
100 AS priority,
33+
(regexp_match(sl.label, k.pattern_security_invoker, 'i'))[1] IS NOT NULL AS security_invoker
34+
FROM const k,
35+
pg_catalog.pg_seclabel sl
36+
JOIN pg_catalog.pg_class c ON sl.classoid = c.tableoid AND sl.objoid = c.oid
37+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND sl.objsubid = a.attnum
38+
LEFT JOIN pg_catalog.pg_seclabel sl2 ON sl2.objoid = c.oid AND sl2.objsubid = 0
39+
WHERE a.attnum > 0
40+
AND c.relnamespace::regnamespace != 'pg_catalog'::regnamespace
41+
AND NOT a.attisdropped
42+
AND sl.label ilike 'ENCRYPT%'
43+
AND sl.provider = 'pgsodium'
44+
)
45+
SELECT
46+
DISTINCT ON (attrelid, attnum) *
47+
FROM rules_from_seclabels
48+
ORDER BY attrelid, attnum, priority DESC;
49+
50+
CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text)
51+
RETURNS void AS
52+
$$
53+
BEGIN
54+
EXECUTE format(
55+
'GRANT SELECT ON pgsodium.key TO %s',
56+
masked_role);
57+
58+
EXECUTE format(
59+
'GRANT pgsodium_keyiduser, pgsodium_keyholder TO %s',
60+
masked_role);
61+
62+
EXECUTE format(
63+
'GRANT ALL ON %s TO %s',
64+
view_name,
65+
masked_role);
66+
RETURN;
67+
END
68+
$$
69+
LANGUAGE plpgsql
70+
SECURITY DEFINER
71+
SET search_path='pg_catalog'
72+
;
73+
74+
CREATE OR REPLACE FUNCTION pgsodium.create_mask_view(relid oid, subid integer, debug boolean = false)
75+
RETURNS void AS
76+
$$
77+
DECLARE
78+
m record;
79+
body text;
80+
source_name text;
81+
view_owner regrole = session_user;
82+
rule pgsodium.masking_rule;
83+
privs aclitem[];
84+
priv record;
85+
BEGIN
86+
SELECT DISTINCT * INTO STRICT rule FROM pgsodium.masking_rule WHERE attrelid = relid AND attnum = subid;
87+
88+
source_name := relid::regclass::text;
89+
90+
BEGIN
91+
SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = rule.view_name::regclass::oid;
92+
EXCEPTION
93+
WHEN undefined_table THEN
94+
SELECT relacl INTO STRICT privs FROM pg_catalog.pg_class WHERE oid = relid;
95+
END;
96+
97+
body = format(
98+
$c$
99+
DROP VIEW IF EXISTS %1$s;
100+
CREATE VIEW %1$s %5$s AS SELECT %2$s
101+
FROM %3$s;
102+
ALTER VIEW %1$s OWNER TO %4$s;
103+
$c$,
104+
rule.view_name,
105+
pgsodium.decrypted_columns(relid),
106+
source_name,
107+
view_owner,
108+
CASE WHEN rule.security_invoker THEN 'WITH (security_invoker=true)' ELSE '' END
109+
);
110+
IF debug THEN
111+
RAISE NOTICE '%', body;
112+
END IF;
113+
EXECUTE body;
114+
115+
FOR priv IN SELECT * FROM pg_catalog.aclexplode(privs) LOOP
116+
body = format(
117+
$c$
118+
GRANT %s ON %s TO %s;
119+
$c$,
120+
priv.privilege_type,
121+
rule.view_name,
122+
priv.grantee::regrole::text
123+
);
124+
IF debug THEN
125+
RAISE NOTICE '%', body;
126+
END IF;
127+
EXECUTE body;
128+
END LOOP;
129+
130+
FOR m IN SELECT * FROM pgsodium.mask_columns where attrelid = relid LOOP
131+
IF m.key_id IS NULL AND m.key_id_column is NULL THEN
132+
CONTINUE;
133+
ELSE
134+
body = format(
135+
$c$
136+
DROP FUNCTION IF EXISTS %1$s."%2$s_encrypt_secret_%3$s"() CASCADE;
137+
138+
CREATE OR REPLACE FUNCTION %1$s."%2$s_encrypt_secret_%3$s"()
139+
RETURNS TRIGGER
140+
LANGUAGE plpgsql
141+
AS $t$
142+
BEGIN
143+
%4$s;
144+
RETURN new;
145+
END;
146+
$t$;
147+
148+
ALTER FUNCTION %1$s."%2$s_encrypt_secret_%3$s"() OWNER TO %5$s;
149+
150+
DROP TRIGGER IF EXISTS "%2$s_encrypt_secret_trigger_%3$s" ON %6$s;
151+
152+
CREATE TRIGGER "%2$s_encrypt_secret_trigger_%3$s"
153+
BEFORE INSERT OR UPDATE OF "%3$s" ON %6$s
154+
FOR EACH ROW
155+
EXECUTE FUNCTION %1$s."%2$s_encrypt_secret_%3$s" ();
156+
$c$,
157+
rule.relnamespace,
158+
rule.relname,
159+
m.attname,
160+
pgsodium.encrypted_column(relid, m),
161+
view_owner,
162+
source_name
163+
);
164+
if debug THEN
165+
RAISE NOTICE '%', body;
166+
END IF;
167+
EXECUTE body;
168+
END IF;
169+
END LOOP;
170+
171+
raise notice 'about to masking role % %', source_name, rule.view_name;
172+
PERFORM pgsodium.mask_role(oid::regrole, source_name, rule.view_name)
173+
FROM pg_roles WHERE pgsodium.has_mask(oid::regrole, source_name);
174+
175+
RETURN;
176+
END
177+
$$
178+
LANGUAGE plpgsql
179+
VOLATILE
180+
SET search_path='pg_catalog'
181+
;

sql/pgsodium--3.1.7--3.1.8.sql

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
CREATE OR REPLACE FUNCTION pgsodium.trg_mask_update()
3+
RETURNS EVENT_TRIGGER AS
4+
$$
5+
DECLARE
6+
r record;
7+
BEGIN
8+
IF (SELECT bool_or(in_extension) FROM pg_event_trigger_ddl_commands()) THEN
9+
RAISE NOTICE 'skipping pgsodium mask regeneration in extension';
10+
RETURN;
11+
END IF;
12+
13+
FOR r IN
14+
SELECT e.*
15+
FROM pg_event_trigger_ddl_commands() e
16+
WHERE EXISTS (
17+
SELECT FROM pg_catalog.pg_class c
18+
JOIN pg_catalog.pg_seclabel s ON s.classoid = c.tableoid
19+
AND s.objoid = c.oid
20+
WHERE c.tableoid = e.classid
21+
AND e.objid = c.oid
22+
AND s.provider = 'pgsodium'
23+
)
24+
LOOP
25+
IF r.object_type in ('table', 'table column')
26+
THEN
27+
PERFORM pgsodium.update_mask(r.objid);
28+
END IF;
29+
END LOOP;
30+
END
31+
$$
32+
LANGUAGE plpgsql
33+
SET search_path=''
34+
;

0 commit comments

Comments
 (0)