@pgpm/encrypted-secrets 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/Makefile +6 -0
- package/README.md +5 -0
- package/__tests__/__snapshots__/secrets.test.ts.snap +28 -0
- package/__tests__/secrets.test.ts +193 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_bytea_to_text.sql +17 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_crypt.sql +17 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_crypt_verify.sql +37 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_pgp.sql +17 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_pgp_get.sql +18 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_pgp_getter.sql +42 -0
- package/deploy/schemas/encrypted_secrets/procedures/encrypt_field_set.sql +16 -0
- package/deploy/schemas/encrypted_secrets/procedures/secrets_delete.sql +40 -0
- package/deploy/schemas/encrypted_secrets/procedures/secrets_getter.sql +43 -0
- package/deploy/schemas/encrypted_secrets/procedures/secrets_table_upsert.sql +67 -0
- package/deploy/schemas/encrypted_secrets/procedures/secrets_upsert.sql +33 -0
- package/deploy/schemas/encrypted_secrets/procedures/secrets_verify.sql +46 -0
- package/deploy/schemas/encrypted_secrets/schema.sql +8 -0
- package/jest.config.js +15 -0
- package/launchql-encrypted-secrets.control +8 -0
- package/launchql.plan +17 -0
- package/package.json +30 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_bytea_to_text.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_crypt.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_crypt_verify.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_pgp.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_pgp_get.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_pgp_getter.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/encrypt_field_set.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/secrets_delete.sql +8 -0
- package/revert/schemas/encrypted_secrets/procedures/secrets_getter.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/secrets_table_upsert.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/secrets_upsert.sql +7 -0
- package/revert/schemas/encrypted_secrets/procedures/secrets_verify.sql +7 -0
- package/revert/schemas/encrypted_secrets/schema.sql +7 -0
- package/sql/launchql-encrypted-secrets--0.1.0.sql +231 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_bytea_to_text.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_crypt.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_crypt_verify.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_pgp.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_pgp_get.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_pgp_getter.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/encrypt_field_set.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/secrets_delete.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/secrets_getter.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/secrets_table_upsert.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/secrets_upsert.sql +7 -0
- package/verify/schemas/encrypted_secrets/procedures/secrets_verify.sql +7 -0
- package/verify/schemas/encrypted_secrets/schema.sql +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Interweb, Inc.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/Makefile
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`encrypted secrets encrypt_field_bytea_to_text 1`] = `"value-there-and-back"`;
|
|
4
|
+
|
|
5
|
+
exports[`encrypted secrets encrypt_field_pgp_get 1`] = `"my-secret"`;
|
|
6
|
+
|
|
7
|
+
exports[`encrypted secrets encrypt_field_set 1`] = `
|
|
8
|
+
{
|
|
9
|
+
"data": [
|
|
10
|
+
109,
|
|
11
|
+
121,
|
|
12
|
+
118,
|
|
13
|
+
97,
|
|
14
|
+
108,
|
|
15
|
+
117,
|
|
16
|
+
101,
|
|
17
|
+
],
|
|
18
|
+
"type": "Buffer",
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
exports[`encrypted secrets secrets_getter 1`] = `"my-secret"`;
|
|
23
|
+
|
|
24
|
+
exports[`encrypted secrets secrets_upsert 1`] = `true`;
|
|
25
|
+
|
|
26
|
+
exports[`encrypted secrets secrets_upsert 2`] = `true`;
|
|
27
|
+
|
|
28
|
+
exports[`encrypted secrets secrets_verify 1`] = `true`;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { getConnections, PgTestClient } from 'pgsql-test';
|
|
2
|
+
|
|
3
|
+
let pg: PgTestClient;
|
|
4
|
+
let teardown: () => Promise<void>;
|
|
5
|
+
|
|
6
|
+
const user_id = 'dc474833-318a-41f5-9239-ee563ab657a6';
|
|
7
|
+
|
|
8
|
+
describe('encrypted secrets', () => {
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
({ pg, teardown } = await getConnections());
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async () => {
|
|
14
|
+
await teardown();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
await pg.beforeEach();
|
|
19
|
+
|
|
20
|
+
// Insert test data
|
|
21
|
+
await pg.any(`
|
|
22
|
+
INSERT INTO secrets_schema.secrets_table
|
|
23
|
+
( secrets_owned_field,
|
|
24
|
+
name,
|
|
25
|
+
secrets_value_field,
|
|
26
|
+
secrets_enc_field
|
|
27
|
+
) VALUES
|
|
28
|
+
(
|
|
29
|
+
$1::uuid,
|
|
30
|
+
'my-secret-name',
|
|
31
|
+
'my-secret'::bytea,
|
|
32
|
+
'pgp'
|
|
33
|
+
)
|
|
34
|
+
`, [user_id]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(async () => {
|
|
38
|
+
await pg.afterEach();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('encrypt_field_pgp_get', async () => {
|
|
42
|
+
const [{ encrypt_field_pgp_get }] = await pg.any(
|
|
43
|
+
`SELECT encrypted_secrets.encrypt_field_pgp_get(secrets_value_field, secrets_owned_field::text)
|
|
44
|
+
FROM secrets_schema.secrets_table
|
|
45
|
+
WHERE secrets_owned_field = $1`,
|
|
46
|
+
[user_id]
|
|
47
|
+
);
|
|
48
|
+
expect(encrypt_field_pgp_get).toMatchSnapshot();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('encrypt_field_set', async () => {
|
|
52
|
+
const [{ encrypt_field_set }] = await pg.any(
|
|
53
|
+
`SELECT encrypted_secrets.encrypt_field_set('myvalue')`
|
|
54
|
+
);
|
|
55
|
+
expect(encrypt_field_set).toMatchSnapshot();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('encrypt_field_bytea_to_text', async () => {
|
|
59
|
+
const [{ encrypt_field_bytea_to_text }] = await pg.any(
|
|
60
|
+
`SELECT encrypted_secrets.encrypt_field_bytea_to_text(
|
|
61
|
+
encrypted_secrets.encrypt_field_set('value-there-and-back')
|
|
62
|
+
)`
|
|
63
|
+
);
|
|
64
|
+
expect(encrypt_field_bytea_to_text).toMatchSnapshot();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('secrets_getter', async () => {
|
|
68
|
+
const [{ secrets_getter }] = await pg.any(
|
|
69
|
+
`SELECT encrypted_secrets.secrets_getter(
|
|
70
|
+
$1::uuid,
|
|
71
|
+
'my-secret-name'
|
|
72
|
+
)`,
|
|
73
|
+
[user_id]
|
|
74
|
+
);
|
|
75
|
+
expect(secrets_getter).toMatchSnapshot();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('secrets_verify', async () => {
|
|
79
|
+
const [{ secrets_verify }] = await pg.any(
|
|
80
|
+
`SELECT encrypted_secrets.secrets_verify(
|
|
81
|
+
$1::uuid,
|
|
82
|
+
'my-secret-name',
|
|
83
|
+
'my-secret'
|
|
84
|
+
)`,
|
|
85
|
+
[user_id]
|
|
86
|
+
);
|
|
87
|
+
expect(secrets_verify).toMatchSnapshot();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('secrets_upsert', async () => {
|
|
91
|
+
const [{ secrets_upsert }] = await pg.any(
|
|
92
|
+
`SELECT encrypted_secrets.secrets_upsert(
|
|
93
|
+
$1::uuid,
|
|
94
|
+
'my-secret-name',
|
|
95
|
+
'my-secret-other-value'
|
|
96
|
+
)`,
|
|
97
|
+
[user_id]
|
|
98
|
+
);
|
|
99
|
+
expect(secrets_upsert).toMatchSnapshot();
|
|
100
|
+
|
|
101
|
+
const [{ secrets_verify }] = await pg.any(
|
|
102
|
+
`SELECT encrypted_secrets.secrets_verify(
|
|
103
|
+
$1::uuid,
|
|
104
|
+
'my-secret-name',
|
|
105
|
+
'my-secret-other-value'
|
|
106
|
+
)`,
|
|
107
|
+
[user_id]
|
|
108
|
+
);
|
|
109
|
+
expect(secrets_verify).toMatchSnapshot();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('secrets_delete single', async () => {
|
|
113
|
+
// First verify the secret exists
|
|
114
|
+
const [beforeDelete] = await pg.any(
|
|
115
|
+
`SELECT encrypted_secrets.secrets_getter($1::uuid, 'my-secret-name')`,
|
|
116
|
+
[user_id]
|
|
117
|
+
);
|
|
118
|
+
expect(beforeDelete.secrets_getter).toBe('my-secret');
|
|
119
|
+
|
|
120
|
+
// Delete the secret
|
|
121
|
+
await pg.any(
|
|
122
|
+
`SELECT encrypted_secrets.secrets_delete($1::uuid, 'my-secret-name')`,
|
|
123
|
+
[user_id]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Verify it's gone
|
|
127
|
+
const [afterDelete] = await pg.any(
|
|
128
|
+
`SELECT encrypted_secrets.secrets_getter($1::uuid, 'my-secret-name', 'default-value')`,
|
|
129
|
+
[user_id]
|
|
130
|
+
);
|
|
131
|
+
expect(afterDelete.secrets_getter).toBe('default-value');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('secrets_delete multiple', async () => {
|
|
135
|
+
// Add multiple secrets
|
|
136
|
+
await pg.any(
|
|
137
|
+
`SELECT encrypted_secrets.secrets_upsert($1::uuid, 'secret-1', 'value-1')`,
|
|
138
|
+
[user_id]
|
|
139
|
+
);
|
|
140
|
+
await pg.any(
|
|
141
|
+
`SELECT encrypted_secrets.secrets_upsert($1::uuid, 'secret-2', 'value-2')`,
|
|
142
|
+
[user_id]
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Delete multiple secrets
|
|
146
|
+
await pg.any(
|
|
147
|
+
`SELECT encrypted_secrets.secrets_delete($1::uuid, $2::text[])`,
|
|
148
|
+
[user_id, ['secret-1', 'secret-2']]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Verify they're gone
|
|
152
|
+
const [result1] = await pg.any(
|
|
153
|
+
`SELECT encrypted_secrets.secrets_getter($1::uuid, 'secret-1', 'not-found')`,
|
|
154
|
+
[user_id]
|
|
155
|
+
);
|
|
156
|
+
const [result2] = await pg.any(
|
|
157
|
+
`SELECT encrypted_secrets.secrets_getter($1::uuid, 'secret-2', 'not-found')`,
|
|
158
|
+
[user_id]
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(result1.secrets_getter).toBe('not-found');
|
|
162
|
+
expect(result2.secrets_getter).toBe('not-found');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
xit('encrypt_field_pgp_getter', async () => {
|
|
166
|
+
const [{ encrypt_field_pgp_getter }] = await pg.any(
|
|
167
|
+
`SELECT encrypted_secrets.encrypt_field_pgp_getter(
|
|
168
|
+
$1::uuid,
|
|
169
|
+
'secrets_value_field',
|
|
170
|
+
'secrets_enc_field'
|
|
171
|
+
)`,
|
|
172
|
+
[user_id]
|
|
173
|
+
);
|
|
174
|
+
expect(encrypt_field_pgp_getter).toMatchSnapshot();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
xit('secrets_table_upsert', async () => {
|
|
178
|
+
const [{ secrets_table_upsert }] = await pg.any(
|
|
179
|
+
`SELECT encrypted_secrets.secrets_table_upsert(
|
|
180
|
+
$1::uuid,
|
|
181
|
+
$2::json
|
|
182
|
+
)`,
|
|
183
|
+
[
|
|
184
|
+
user_id,
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
myOther: 'secret',
|
|
187
|
+
hiOther: 'here'
|
|
188
|
+
})
|
|
189
|
+
]
|
|
190
|
+
);
|
|
191
|
+
expect(secrets_table_upsert).toMatchSnapshot();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_bytea_to_text to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_bytea_to_text(
|
|
8
|
+
secret_value bytea
|
|
9
|
+
)
|
|
10
|
+
RETURNS text
|
|
11
|
+
AS $$
|
|
12
|
+
SELECT
|
|
13
|
+
convert_from(encrypt_field_bytea_to_text.secret_value, 'SQL_ASCII');
|
|
14
|
+
$$
|
|
15
|
+
LANGUAGE 'sql' IMMUTABLE;
|
|
16
|
+
|
|
17
|
+
COMMIT;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_crypt to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_crypt()
|
|
8
|
+
RETURNS TRIGGER
|
|
9
|
+
AS $CODEZ$
|
|
10
|
+
BEGIN
|
|
11
|
+
NEW.field_name = crypt(NEW.field_name::text, gen_salt('bf'));
|
|
12
|
+
RETURN NEW;
|
|
13
|
+
END;
|
|
14
|
+
$CODEZ$
|
|
15
|
+
LANGUAGE plpgsql VOLATILE;
|
|
16
|
+
|
|
17
|
+
COMMIT;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_crypt_verify to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_crypt_verify(
|
|
8
|
+
secrets_owned_field uuid,
|
|
9
|
+
secret_value_field text,
|
|
10
|
+
secret_verify_value text
|
|
11
|
+
)
|
|
12
|
+
RETURNS bool
|
|
13
|
+
AS $$
|
|
14
|
+
DECLARE
|
|
15
|
+
result bool;
|
|
16
|
+
rec secrets_schema.secrets_table;
|
|
17
|
+
s_value text;
|
|
18
|
+
BEGIN
|
|
19
|
+
|
|
20
|
+
SELECT * FROM secrets_schema.secrets_table s
|
|
21
|
+
WHERE s.secrets_owned_field = encrypt_field_crypt_verify.secrets_owned_field
|
|
22
|
+
INTO rec;
|
|
23
|
+
|
|
24
|
+
EXECUTE format('SELECT ($1).%s::text', secret_value_field)
|
|
25
|
+
USING rec
|
|
26
|
+
INTO s_value;
|
|
27
|
+
|
|
28
|
+
SELECT
|
|
29
|
+
s_value = crypt(secret_verify_value, s_value)
|
|
30
|
+
INTO result;
|
|
31
|
+
|
|
32
|
+
RETURN result;
|
|
33
|
+
END;
|
|
34
|
+
$$
|
|
35
|
+
LANGUAGE 'plpgsql' STABLE;
|
|
36
|
+
|
|
37
|
+
COMMIT;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_pgp to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_pgp()
|
|
8
|
+
RETURNS TRIGGER
|
|
9
|
+
AS $CODEZ$
|
|
10
|
+
BEGIN
|
|
11
|
+
NEW.field_name = pgp_sym_encrypt(encode(NEW.field_name::bytea, 'hex'), NEW.encode_field::text, 'compress-algo=1, cipher-algo=aes256');
|
|
12
|
+
RETURN NEW;
|
|
13
|
+
END;
|
|
14
|
+
$CODEZ$
|
|
15
|
+
LANGUAGE plpgsql VOLATILE;
|
|
16
|
+
|
|
17
|
+
COMMIT;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_pgp_get to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_pgp_get(
|
|
8
|
+
secret_value bytea,
|
|
9
|
+
secret_encode text
|
|
10
|
+
)
|
|
11
|
+
RETURNS text
|
|
12
|
+
AS $$
|
|
13
|
+
SELECT
|
|
14
|
+
convert_from(decode(pgp_sym_decrypt(encrypt_field_pgp_get.secret_value, encrypt_field_pgp_get.secret_encode), 'hex'), 'SQL_ASCII');
|
|
15
|
+
$$
|
|
16
|
+
LANGUAGE 'sql';
|
|
17
|
+
|
|
18
|
+
COMMIT;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_pgp_getter to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_pgp_getter(
|
|
8
|
+
secrets_owned_field uuid,
|
|
9
|
+
secret_value_field text,
|
|
10
|
+
secret_encode_field text
|
|
11
|
+
)
|
|
12
|
+
RETURNS text
|
|
13
|
+
AS $$
|
|
14
|
+
DECLARE
|
|
15
|
+
result text;
|
|
16
|
+
rec secrets_schema.secrets_table;
|
|
17
|
+
s_value bytea;
|
|
18
|
+
s_enc text;
|
|
19
|
+
BEGIN
|
|
20
|
+
|
|
21
|
+
SELECT * FROM secrets_schema.secrets_table s
|
|
22
|
+
WHERE s.secrets_owned_field = encrypt_field_pgp_getter.secrets_owned_field
|
|
23
|
+
INTO rec;
|
|
24
|
+
|
|
25
|
+
EXECUTE format('SELECT ($1).%s::text', secret_value_field)
|
|
26
|
+
USING rec
|
|
27
|
+
INTO s_value;
|
|
28
|
+
|
|
29
|
+
EXECUTE format('SELECT ($1).%s::text', secret_encode_field)
|
|
30
|
+
USING rec
|
|
31
|
+
INTO s_enc;
|
|
32
|
+
|
|
33
|
+
SELECT
|
|
34
|
+
convert_from(decode(pgp_sym_decrypt(s_value, s_enc), 'hex'), 'SQL_ASCII')
|
|
35
|
+
INTO result;
|
|
36
|
+
|
|
37
|
+
RETURN result;
|
|
38
|
+
END;
|
|
39
|
+
$$
|
|
40
|
+
LANGUAGE 'plpgsql' STABLE;
|
|
41
|
+
|
|
42
|
+
COMMIT;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/encrypt_field_set to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_set(
|
|
8
|
+
secret_value text
|
|
9
|
+
)
|
|
10
|
+
RETURNS bytea
|
|
11
|
+
AS $$
|
|
12
|
+
SELECT encrypt_field_set.secret_value::bytea;
|
|
13
|
+
$$
|
|
14
|
+
LANGUAGE 'sql';
|
|
15
|
+
|
|
16
|
+
COMMIT;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/secrets_delete to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.secrets_delete(
|
|
8
|
+
secrets_owned_field uuid,
|
|
9
|
+
secret_name text
|
|
10
|
+
)
|
|
11
|
+
RETURNS void
|
|
12
|
+
AS $$
|
|
13
|
+
BEGIN
|
|
14
|
+
DELETE FROM secrets_schema.secrets_table s
|
|
15
|
+
WHERE s.secrets_owned_field = secrets_delete.secrets_owned_field
|
|
16
|
+
AND s.name = secrets_delete.secret_name;
|
|
17
|
+
END
|
|
18
|
+
$$
|
|
19
|
+
LANGUAGE 'plpgsql'
|
|
20
|
+
VOLATILE;
|
|
21
|
+
|
|
22
|
+
CREATE FUNCTION encrypted_secrets.secrets_delete(
|
|
23
|
+
secrets_owned_field uuid,
|
|
24
|
+
secret_names text[]
|
|
25
|
+
)
|
|
26
|
+
RETURNS void
|
|
27
|
+
AS $$
|
|
28
|
+
BEGIN
|
|
29
|
+
DELETE FROM secrets_schema.secrets_table s
|
|
30
|
+
WHERE s.secrets_owned_field = secrets_delete.secrets_owned_field
|
|
31
|
+
AND s.name = ANY(secrets_delete.secret_names);
|
|
32
|
+
END
|
|
33
|
+
$$
|
|
34
|
+
LANGUAGE 'plpgsql'
|
|
35
|
+
VOLATILE;
|
|
36
|
+
|
|
37
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_delete(uuid,text) TO authenticated;
|
|
38
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_delete(uuid,text[]) TO authenticated;
|
|
39
|
+
|
|
40
|
+
COMMIT;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/secrets_getter to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.secrets_getter(
|
|
8
|
+
secrets_owned_field uuid,
|
|
9
|
+
secret_name text,
|
|
10
|
+
default_value text default null
|
|
11
|
+
)
|
|
12
|
+
RETURNS text
|
|
13
|
+
AS $$
|
|
14
|
+
DECLARE
|
|
15
|
+
v_secret secrets_schema.secrets_table;
|
|
16
|
+
BEGIN
|
|
17
|
+
SELECT
|
|
18
|
+
*
|
|
19
|
+
FROM
|
|
20
|
+
secrets_schema.secrets_table s
|
|
21
|
+
WHERE
|
|
22
|
+
s.name = secrets_getter.secret_name
|
|
23
|
+
AND s.secrets_owned_field = secrets_getter.secrets_owned_field
|
|
24
|
+
INTO v_secret;
|
|
25
|
+
|
|
26
|
+
IF (NOT FOUND OR v_secret IS NULL) THEN
|
|
27
|
+
RETURN secrets_getter.default_value;
|
|
28
|
+
END IF;
|
|
29
|
+
|
|
30
|
+
IF (v_secret.secrets_enc_field = 'crypt') THEN
|
|
31
|
+
RETURN convert_from(v_secret.secrets_value_field, 'SQL_ASCII');
|
|
32
|
+
ELSIF (v_secret.secrets_enc_field = 'pgp') THEN
|
|
33
|
+
RETURN convert_from(decode(pgp_sym_decrypt(v_secret.secrets_value_field, v_secret.secrets_owned_field::text), 'hex'), 'SQL_ASCII');
|
|
34
|
+
END IF;
|
|
35
|
+
|
|
36
|
+
RETURN convert_from(v_secret.secrets_value_field, 'SQL_ASCII');
|
|
37
|
+
|
|
38
|
+
END
|
|
39
|
+
$$
|
|
40
|
+
LANGUAGE 'plpgsql'
|
|
41
|
+
STABLE;
|
|
42
|
+
|
|
43
|
+
COMMIT;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/secrets_table_upsert to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.secrets_table_upsert(
|
|
8
|
+
secrets_owned_field uuid,
|
|
9
|
+
data json
|
|
10
|
+
)
|
|
11
|
+
RETURNS void
|
|
12
|
+
AS $$
|
|
13
|
+
DECLARE
|
|
14
|
+
rec secrets_schema.secrets_table;
|
|
15
|
+
_sql text;
|
|
16
|
+
key text;
|
|
17
|
+
|
|
18
|
+
fields text[] = ARRAY[]::text[];
|
|
19
|
+
values text[] = ARRAY[]::text[];
|
|
20
|
+
pairs text[] = ARRAY[]::text[];
|
|
21
|
+
BEGIN
|
|
22
|
+
|
|
23
|
+
SELECT * FROM secrets_schema.secrets_table s
|
|
24
|
+
WHERE s.secrets_owned_field = secrets_table_upsert.secrets_owned_field
|
|
25
|
+
INTO rec;
|
|
26
|
+
|
|
27
|
+
IF (FOUND) THEN
|
|
28
|
+
|
|
29
|
+
FOR key IN SELECT json_object_keys(data)
|
|
30
|
+
LOOP
|
|
31
|
+
pairs = array_append(pairs, format('%s=%s', key, quote_literal(data->>key)));
|
|
32
|
+
END LOOP;
|
|
33
|
+
|
|
34
|
+
_sql = 'UPDATE secrets_schema.secrets_table SET '; -- it's already quoted! look at I above...
|
|
35
|
+
_sql = _sql || format('%s', array_to_string(pairs, ','));
|
|
36
|
+
_sql = _sql || ' WHERE secrets_owned_field=';
|
|
37
|
+
_sql = _sql || quote_literal(secrets_owned_field);
|
|
38
|
+
_sql = _sql || ';';
|
|
39
|
+
|
|
40
|
+
ELSE
|
|
41
|
+
|
|
42
|
+
values = array_append(values, quote_literal(secrets_owned_field));
|
|
43
|
+
fields = array_append(fields, 'secrets_owned_field');
|
|
44
|
+
|
|
45
|
+
FOR key IN SELECT json_object_keys(data)
|
|
46
|
+
LOOP
|
|
47
|
+
values = array_append(values, quote_literal(data->>key));
|
|
48
|
+
fields = array_append(fields, key);
|
|
49
|
+
END LOOP;
|
|
50
|
+
|
|
51
|
+
_sql = 'INSERT INTO secrets_schema.secrets_table ('; -- it's already quoted! look at I above...
|
|
52
|
+
_sql = _sql || format('%s)', array_to_string(fields, ','));
|
|
53
|
+
_sql = _sql || ' VALUES (';
|
|
54
|
+
_sql = _sql || format('%s)', array_to_string(values, ','));
|
|
55
|
+
_sql = _sql || ';';
|
|
56
|
+
|
|
57
|
+
END IF;
|
|
58
|
+
|
|
59
|
+
EXECUTE _sql;
|
|
60
|
+
|
|
61
|
+
END;
|
|
62
|
+
$$
|
|
63
|
+
LANGUAGE 'plpgsql' VOLATILE;
|
|
64
|
+
|
|
65
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_table_upsert TO authenticated;
|
|
66
|
+
|
|
67
|
+
COMMIT;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/secrets_upsert to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.secrets_upsert(
|
|
8
|
+
v_secrets_owned_field uuid,
|
|
9
|
+
secret_name text,
|
|
10
|
+
secret_value text,
|
|
11
|
+
field_encoding text = 'pgp'
|
|
12
|
+
)
|
|
13
|
+
RETURNS boolean
|
|
14
|
+
AS $$
|
|
15
|
+
BEGIN
|
|
16
|
+
INSERT INTO secrets_schema.secrets_table (secrets_owned_field, name, secrets_value_field, secrets_enc_field)
|
|
17
|
+
VALUES (v_secrets_owned_field, secrets_upsert.secret_name, secrets_upsert.secret_value::bytea, secrets_upsert.field_encoding)
|
|
18
|
+
ON CONFLICT (secrets_owned_field, name)
|
|
19
|
+
DO
|
|
20
|
+
UPDATE
|
|
21
|
+
SET
|
|
22
|
+
-- don't change this, cannot use EXCLUDED, don't know why, but you have to set to the ::bytea
|
|
23
|
+
secrets_value_field = secrets_upsert.secret_value::bytea,
|
|
24
|
+
secrets_enc_field = EXCLUDED.secrets_enc_field;
|
|
25
|
+
RETURN TRUE;
|
|
26
|
+
END
|
|
27
|
+
$$
|
|
28
|
+
LANGUAGE 'plpgsql'
|
|
29
|
+
VOLATILE;
|
|
30
|
+
|
|
31
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_upsert TO authenticated;
|
|
32
|
+
|
|
33
|
+
COMMIT;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- Deploy schemas/encrypted_secrets/procedures/secrets_verify to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/encrypted_secrets/schema
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
|
|
7
|
+
CREATE FUNCTION encrypted_secrets.secrets_verify(
|
|
8
|
+
secrets_owned_field uuid,
|
|
9
|
+
secret_name text,
|
|
10
|
+
secret_value text
|
|
11
|
+
)
|
|
12
|
+
RETURNS boolean
|
|
13
|
+
AS $$
|
|
14
|
+
DECLARE
|
|
15
|
+
v_secret_text text;
|
|
16
|
+
v_secret secrets_schema.secrets_table;
|
|
17
|
+
BEGIN
|
|
18
|
+
SELECT
|
|
19
|
+
*
|
|
20
|
+
FROM
|
|
21
|
+
encrypted_secrets.secrets_getter (secrets_verify.secrets_owned_field, secrets_verify.secret_name)
|
|
22
|
+
INTO v_secret_text;
|
|
23
|
+
|
|
24
|
+
SELECT
|
|
25
|
+
*
|
|
26
|
+
FROM
|
|
27
|
+
secrets_schema.secrets_table s
|
|
28
|
+
WHERE
|
|
29
|
+
s.name = secrets_verify.secret_name
|
|
30
|
+
AND s.secrets_owned_field = secrets_verify.secrets_owned_field INTO v_secret;
|
|
31
|
+
|
|
32
|
+
IF (v_secret.secrets_enc_field = 'crypt') THEN
|
|
33
|
+
RETURN v_secret_text = crypt(secrets_verify.secret_value::bytea::text, v_secret_text);
|
|
34
|
+
ELSIF (v_secret.secrets_enc_field = 'pgp') THEN
|
|
35
|
+
RETURN secrets_verify.secret_value = v_secret_text;
|
|
36
|
+
END IF;
|
|
37
|
+
|
|
38
|
+
RETURN secrets_verify.secret_value = v_secret_text;
|
|
39
|
+
END
|
|
40
|
+
$$
|
|
41
|
+
LANGUAGE 'plpgsql'
|
|
42
|
+
STABLE;
|
|
43
|
+
|
|
44
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_verify TO authenticated;
|
|
45
|
+
|
|
46
|
+
COMMIT;
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
|
|
6
|
+
// Match both __tests__ and colocated test files
|
|
7
|
+
testMatch: ['**/?(*.)+(test|spec).{ts,tsx,js,jsx}'],
|
|
8
|
+
|
|
9
|
+
// Ignore build artifacts and type declarations
|
|
10
|
+
testPathIgnorePatterns: ['/dist/', '\\.d\\.ts$'],
|
|
11
|
+
modulePathIgnorePatterns: ['<rootDir>/dist/'],
|
|
12
|
+
watchPathIgnorePatterns: ['/dist/'],
|
|
13
|
+
|
|
14
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# launchql-encrypted-secrets extension
|
|
2
|
+
comment = 'launchql-encrypted-secrets extension'
|
|
3
|
+
default_version = '0.1.0'
|
|
4
|
+
module_pathname = '$libdir/launchql-encrypted-secrets'
|
|
5
|
+
requires = 'pgcrypto,plpgsql,uuid-ossp,launchql-encrypted-secrets-table,launchql-default-roles,launchql-verify'
|
|
6
|
+
relocatable = false
|
|
7
|
+
superuser = false
|
|
8
|
+
|
package/launchql.plan
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
%syntax-version=1.0.0
|
|
2
|
+
%project=launchql-encrypted-secrets
|
|
3
|
+
%uri=launchql-encrypted-secrets
|
|
4
|
+
|
|
5
|
+
schemas/encrypted_secrets/schema 2020-11-01T21:22:30Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/schema
|
|
6
|
+
schemas/encrypted_secrets/procedures/encrypt_field_bytea_to_text [schemas/encrypted_secrets/schema] 2020-11-01T21:22:54Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_bytea_to_text
|
|
7
|
+
schemas/encrypted_secrets/procedures/encrypt_field_crypt_verify [schemas/encrypted_secrets/schema] 2020-11-01T21:23:11Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_crypt_verify
|
|
8
|
+
schemas/encrypted_secrets/procedures/encrypt_field_crypt [schemas/encrypted_secrets/schema] 2020-11-01T21:23:19Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_crypt
|
|
9
|
+
schemas/encrypted_secrets/procedures/encrypt_field_pgp_get [schemas/encrypted_secrets/schema] 2020-11-01T21:23:29Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_pgp_get
|
|
10
|
+
schemas/encrypted_secrets/procedures/encrypt_field_pgp_getter [schemas/encrypted_secrets/schema] 2020-11-01T21:23:36Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_pgp_getter
|
|
11
|
+
schemas/encrypted_secrets/procedures/encrypt_field_pgp [schemas/encrypted_secrets/schema] 2020-11-01T21:23:45Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_pgp
|
|
12
|
+
schemas/encrypted_secrets/procedures/encrypt_field_set [schemas/encrypted_secrets/schema] 2020-11-01T21:24:05Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/encrypt_field_set
|
|
13
|
+
schemas/encrypted_secrets/procedures/secrets_getter [schemas/encrypted_secrets/schema] 2020-11-01T21:24:23Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/secrets_getter
|
|
14
|
+
schemas/encrypted_secrets/procedures/secrets_upsert [schemas/encrypted_secrets/schema] 2020-11-01T21:24:35Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/secrets_upsert
|
|
15
|
+
schemas/encrypted_secrets/procedures/secrets_verify [schemas/encrypted_secrets/schema] 2020-11-01T21:24:41Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/secrets_verify
|
|
16
|
+
schemas/encrypted_secrets/procedures/secrets_table_upsert [schemas/encrypted_secrets/schema] 2020-11-01T21:25:00Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/secrets_table_upsert
|
|
17
|
+
schemas/encrypted_secrets/procedures/secrets_delete [schemas/encrypted_secrets/schema] 2020-11-01T21:31:24Z Dan Lynch <dlynch@Dans-MBP-3> # add schemas/encrypted_secrets/procedures/secrets_delete
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pgpm/encrypted-secrets",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Encrypted secrets management for PostgreSQL",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"bundle": "lql package",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@pgpm/default-roles": "0.4.0",
|
|
15
|
+
"@pgpm/encrypted-secrets-table": "0.4.0",
|
|
16
|
+
"@pgpm/verify": "0.4.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@launchql/cli": "^4.9.0"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/launchql/extensions"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/launchql/extensions",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/launchql/extensions/issues"
|
|
28
|
+
},
|
|
29
|
+
"gitHead": "cc9f52a335caa6e21ee7751b04b77c84ce6cb809"
|
|
30
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
\echo Use "CREATE EXTENSION launchql-encrypted-secrets" to load this file. \quit
|
|
2
|
+
CREATE SCHEMA encrypted_secrets;
|
|
3
|
+
|
|
4
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_bytea_to_text(secret_value bytea) RETURNS text AS $EOFCODE$
|
|
5
|
+
SELECT
|
|
6
|
+
convert_from(encrypt_field_bytea_to_text.secret_value, 'SQL_ASCII');
|
|
7
|
+
$EOFCODE$ LANGUAGE sql IMMUTABLE;
|
|
8
|
+
|
|
9
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_crypt_verify(secrets_owned_field uuid, secret_value_field text, secret_verify_value text) RETURNS bool AS $EOFCODE$
|
|
10
|
+
DECLARE
|
|
11
|
+
result bool;
|
|
12
|
+
rec secrets_schema.secrets_table;
|
|
13
|
+
s_value text;
|
|
14
|
+
BEGIN
|
|
15
|
+
|
|
16
|
+
SELECT * FROM secrets_schema.secrets_table s
|
|
17
|
+
WHERE s.secrets_owned_field = encrypt_field_crypt_verify.secrets_owned_field
|
|
18
|
+
INTO rec;
|
|
19
|
+
|
|
20
|
+
EXECUTE format('SELECT ($1).%s::text', secret_value_field)
|
|
21
|
+
USING rec
|
|
22
|
+
INTO s_value;
|
|
23
|
+
|
|
24
|
+
SELECT
|
|
25
|
+
s_value = crypt(secret_verify_value, s_value)
|
|
26
|
+
INTO result;
|
|
27
|
+
|
|
28
|
+
RETURN result;
|
|
29
|
+
END;
|
|
30
|
+
$EOFCODE$ LANGUAGE plpgsql STABLE;
|
|
31
|
+
|
|
32
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_crypt() RETURNS trigger AS $EOFCODE$
|
|
33
|
+
BEGIN
|
|
34
|
+
NEW.field_name = crypt(NEW.field_name::text, gen_salt('bf'));
|
|
35
|
+
RETURN NEW;
|
|
36
|
+
END;
|
|
37
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
38
|
+
|
|
39
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_pgp_get(secret_value bytea, secret_encode text) RETURNS text AS $EOFCODE$
|
|
40
|
+
SELECT
|
|
41
|
+
convert_from(decode(pgp_sym_decrypt(encrypt_field_pgp_get.secret_value, encrypt_field_pgp_get.secret_encode), 'hex'), 'SQL_ASCII');
|
|
42
|
+
$EOFCODE$ LANGUAGE sql;
|
|
43
|
+
|
|
44
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_pgp_getter(secrets_owned_field uuid, secret_value_field text, secret_encode_field text) RETURNS text AS $EOFCODE$
|
|
45
|
+
DECLARE
|
|
46
|
+
result text;
|
|
47
|
+
rec secrets_schema.secrets_table;
|
|
48
|
+
s_value bytea;
|
|
49
|
+
s_enc text;
|
|
50
|
+
BEGIN
|
|
51
|
+
|
|
52
|
+
SELECT * FROM secrets_schema.secrets_table s
|
|
53
|
+
WHERE s.secrets_owned_field = encrypt_field_pgp_getter.secrets_owned_field
|
|
54
|
+
INTO rec;
|
|
55
|
+
|
|
56
|
+
EXECUTE format('SELECT ($1).%s::text', secret_value_field)
|
|
57
|
+
USING rec
|
|
58
|
+
INTO s_value;
|
|
59
|
+
|
|
60
|
+
EXECUTE format('SELECT ($1).%s::text', secret_encode_field)
|
|
61
|
+
USING rec
|
|
62
|
+
INTO s_enc;
|
|
63
|
+
|
|
64
|
+
SELECT
|
|
65
|
+
convert_from(decode(pgp_sym_decrypt(s_value, s_enc), 'hex'), 'SQL_ASCII')
|
|
66
|
+
INTO result;
|
|
67
|
+
|
|
68
|
+
RETURN result;
|
|
69
|
+
END;
|
|
70
|
+
$EOFCODE$ LANGUAGE plpgsql STABLE;
|
|
71
|
+
|
|
72
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_pgp() RETURNS trigger AS $EOFCODE$
|
|
73
|
+
BEGIN
|
|
74
|
+
NEW.field_name = pgp_sym_encrypt(encode(NEW.field_name::bytea, 'hex'), NEW.encode_field::text, 'compress-algo=1, cipher-algo=aes256');
|
|
75
|
+
RETURN NEW;
|
|
76
|
+
END;
|
|
77
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
78
|
+
|
|
79
|
+
CREATE FUNCTION encrypted_secrets.encrypt_field_set(secret_value text) RETURNS bytea AS $EOFCODE$
|
|
80
|
+
SELECT encrypt_field_set.secret_value::bytea;
|
|
81
|
+
$EOFCODE$ LANGUAGE sql;
|
|
82
|
+
|
|
83
|
+
CREATE FUNCTION encrypted_secrets.secrets_getter(secrets_owned_field uuid, secret_name text, default_value text DEFAULT NULL) RETURNS text AS $EOFCODE$
|
|
84
|
+
DECLARE
|
|
85
|
+
v_secret secrets_schema.secrets_table;
|
|
86
|
+
BEGIN
|
|
87
|
+
SELECT
|
|
88
|
+
*
|
|
89
|
+
FROM
|
|
90
|
+
secrets_schema.secrets_table s
|
|
91
|
+
WHERE
|
|
92
|
+
s.name = secrets_getter.secret_name
|
|
93
|
+
AND s.secrets_owned_field = secrets_getter.secrets_owned_field
|
|
94
|
+
INTO v_secret;
|
|
95
|
+
|
|
96
|
+
IF (NOT FOUND OR v_secret IS NULL) THEN
|
|
97
|
+
RETURN secrets_getter.default_value;
|
|
98
|
+
END IF;
|
|
99
|
+
|
|
100
|
+
IF (v_secret.secrets_enc_field = 'crypt') THEN
|
|
101
|
+
RETURN convert_from(v_secret.secrets_value_field, 'SQL_ASCII');
|
|
102
|
+
ELSIF (v_secret.secrets_enc_field = 'pgp') THEN
|
|
103
|
+
RETURN convert_from(decode(pgp_sym_decrypt(v_secret.secrets_value_field, v_secret.secrets_owned_field::text), 'hex'), 'SQL_ASCII');
|
|
104
|
+
END IF;
|
|
105
|
+
|
|
106
|
+
RETURN convert_from(v_secret.secrets_value_field, 'SQL_ASCII');
|
|
107
|
+
|
|
108
|
+
END
|
|
109
|
+
$EOFCODE$ LANGUAGE plpgsql STABLE;
|
|
110
|
+
|
|
111
|
+
CREATE FUNCTION encrypted_secrets.secrets_upsert(v_secrets_owned_field uuid, secret_name text, secret_value text, field_encoding text DEFAULT 'pgp') RETURNS boolean AS $EOFCODE$
|
|
112
|
+
BEGIN
|
|
113
|
+
INSERT INTO secrets_schema.secrets_table (secrets_owned_field, name, secrets_value_field, secrets_enc_field)
|
|
114
|
+
VALUES (v_secrets_owned_field, secrets_upsert.secret_name, secrets_upsert.secret_value::bytea, secrets_upsert.field_encoding)
|
|
115
|
+
ON CONFLICT (secrets_owned_field, name)
|
|
116
|
+
DO
|
|
117
|
+
UPDATE
|
|
118
|
+
SET
|
|
119
|
+
-- don't change this, cannot use EXCLUDED, don't know why, but you have to set to the ::bytea
|
|
120
|
+
secrets_value_field = secrets_upsert.secret_value::bytea,
|
|
121
|
+
secrets_enc_field = EXCLUDED.secrets_enc_field;
|
|
122
|
+
RETURN TRUE;
|
|
123
|
+
END
|
|
124
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
125
|
+
|
|
126
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_upsert TO authenticated;
|
|
127
|
+
|
|
128
|
+
CREATE FUNCTION encrypted_secrets.secrets_verify(secrets_owned_field uuid, secret_name text, secret_value text) RETURNS boolean AS $EOFCODE$
|
|
129
|
+
DECLARE
|
|
130
|
+
v_secret_text text;
|
|
131
|
+
v_secret secrets_schema.secrets_table;
|
|
132
|
+
BEGIN
|
|
133
|
+
SELECT
|
|
134
|
+
*
|
|
135
|
+
FROM
|
|
136
|
+
encrypted_secrets.secrets_getter (secrets_verify.secrets_owned_field, secrets_verify.secret_name)
|
|
137
|
+
INTO v_secret_text;
|
|
138
|
+
|
|
139
|
+
SELECT
|
|
140
|
+
*
|
|
141
|
+
FROM
|
|
142
|
+
secrets_schema.secrets_table s
|
|
143
|
+
WHERE
|
|
144
|
+
s.name = secrets_verify.secret_name
|
|
145
|
+
AND s.secrets_owned_field = secrets_verify.secrets_owned_field INTO v_secret;
|
|
146
|
+
|
|
147
|
+
IF (v_secret.secrets_enc_field = 'crypt') THEN
|
|
148
|
+
RETURN v_secret_text = crypt(secrets_verify.secret_value::bytea::text, v_secret_text);
|
|
149
|
+
ELSIF (v_secret.secrets_enc_field = 'pgp') THEN
|
|
150
|
+
RETURN secrets_verify.secret_value = v_secret_text;
|
|
151
|
+
END IF;
|
|
152
|
+
|
|
153
|
+
RETURN secrets_verify.secret_value = v_secret_text;
|
|
154
|
+
END
|
|
155
|
+
$EOFCODE$ LANGUAGE plpgsql STABLE;
|
|
156
|
+
|
|
157
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_verify TO authenticated;
|
|
158
|
+
|
|
159
|
+
CREATE FUNCTION encrypted_secrets.secrets_table_upsert(secrets_owned_field uuid, data pg_catalog.json) RETURNS void AS $EOFCODE$
|
|
160
|
+
DECLARE
|
|
161
|
+
rec secrets_schema.secrets_table;
|
|
162
|
+
_sql text;
|
|
163
|
+
key text;
|
|
164
|
+
|
|
165
|
+
fields text[] = ARRAY[]::text[];
|
|
166
|
+
values text[] = ARRAY[]::text[];
|
|
167
|
+
pairs text[] = ARRAY[]::text[];
|
|
168
|
+
BEGIN
|
|
169
|
+
|
|
170
|
+
SELECT * FROM secrets_schema.secrets_table s
|
|
171
|
+
WHERE s.secrets_owned_field = secrets_table_upsert.secrets_owned_field
|
|
172
|
+
INTO rec;
|
|
173
|
+
|
|
174
|
+
IF (FOUND) THEN
|
|
175
|
+
|
|
176
|
+
FOR key IN SELECT json_object_keys(data)
|
|
177
|
+
LOOP
|
|
178
|
+
pairs = array_append(pairs, format('%s=%s', key, quote_literal(data->>key)));
|
|
179
|
+
END LOOP;
|
|
180
|
+
|
|
181
|
+
_sql = 'UPDATE secrets_schema.secrets_table SET '; -- it's already quoted! look at I above...
|
|
182
|
+
_sql = _sql || format('%s', array_to_string(pairs, ','));
|
|
183
|
+
_sql = _sql || ' WHERE secrets_owned_field=';
|
|
184
|
+
_sql = _sql || quote_literal(secrets_owned_field);
|
|
185
|
+
_sql = _sql || ';';
|
|
186
|
+
|
|
187
|
+
ELSE
|
|
188
|
+
|
|
189
|
+
values = array_append(values, quote_literal(secrets_owned_field));
|
|
190
|
+
fields = array_append(fields, 'secrets_owned_field');
|
|
191
|
+
|
|
192
|
+
FOR key IN SELECT json_object_keys(data)
|
|
193
|
+
LOOP
|
|
194
|
+
values = array_append(values, quote_literal(data->>key));
|
|
195
|
+
fields = array_append(fields, key);
|
|
196
|
+
END LOOP;
|
|
197
|
+
|
|
198
|
+
_sql = 'INSERT INTO secrets_schema.secrets_table ('; -- it's already quoted! look at I above...
|
|
199
|
+
_sql = _sql || format('%s)', array_to_string(fields, ','));
|
|
200
|
+
_sql = _sql || ' VALUES (';
|
|
201
|
+
_sql = _sql || format('%s)', array_to_string(values, ','));
|
|
202
|
+
_sql = _sql || ';';
|
|
203
|
+
|
|
204
|
+
END IF;
|
|
205
|
+
|
|
206
|
+
EXECUTE _sql;
|
|
207
|
+
|
|
208
|
+
END;
|
|
209
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
210
|
+
|
|
211
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_table_upsert TO authenticated;
|
|
212
|
+
|
|
213
|
+
CREATE FUNCTION encrypted_secrets.secrets_delete(secrets_owned_field uuid, secret_name text) RETURNS void AS $EOFCODE$
|
|
214
|
+
BEGIN
|
|
215
|
+
DELETE FROM secrets_schema.secrets_table s
|
|
216
|
+
WHERE s.secrets_owned_field = secrets_delete.secrets_owned_field
|
|
217
|
+
AND s.name = secrets_delete.secret_name;
|
|
218
|
+
END
|
|
219
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
220
|
+
|
|
221
|
+
CREATE FUNCTION encrypted_secrets.secrets_delete(secrets_owned_field uuid, secret_names text[]) RETURNS void AS $EOFCODE$
|
|
222
|
+
BEGIN
|
|
223
|
+
DELETE FROM secrets_schema.secrets_table s
|
|
224
|
+
WHERE s.secrets_owned_field = secrets_delete.secrets_owned_field
|
|
225
|
+
AND s.name = ANY(secrets_delete.secret_names);
|
|
226
|
+
END
|
|
227
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
228
|
+
|
|
229
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_delete(uuid, text) TO authenticated;
|
|
230
|
+
|
|
231
|
+
GRANT EXECUTE ON FUNCTION encrypted_secrets.secrets_delete(uuid, text[]) TO authenticated;
|