@pgpm/encrypted-secrets-table 0.4.0 → 0.6.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/Makefile
CHANGED
package/README.md
CHANGED
|
@@ -2,4 +2,363 @@
|
|
|
2
2
|
|
|
3
3
|
Table-based encrypted secrets storage and retrieval.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@pgpm/encrypted-secrets-table` provides the foundational storage layer for encrypted secrets management in PostgreSQL. This package creates the `secrets_schema.secrets_table` table that stores encrypted sensitive data such as API keys, passwords, tokens, and other credentials in a structured, secure format.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Structured Storage**: Dedicated table for encrypted secrets with proper indexing
|
|
12
|
+
- **UUID-Based Ownership**: Links secrets to owning entities via UUID foreign keys
|
|
13
|
+
- **Dual Encryption Support**: Supports both bytea (binary) and text-encoded encrypted values
|
|
14
|
+
- **Unique Constraints**: Prevents duplicate secrets per owner
|
|
15
|
+
- **Automatic Hashing**: Trigger-based hashing for secret verification
|
|
16
|
+
- **Foundation Layer**: Provides storage for higher-level secrets management APIs
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
If you have `pgpm` installed:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pgpm install @pgpm/encrypted-secrets-table
|
|
24
|
+
pgpm deploy
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This is a quick way to get started. The sections below provide more detailed installation options.
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Install pgpm globally
|
|
33
|
+
npm install -g pgpm
|
|
34
|
+
|
|
35
|
+
# Start PostgreSQL
|
|
36
|
+
pgpm docker start
|
|
37
|
+
|
|
38
|
+
# Set environment variables
|
|
39
|
+
eval "$(pgpm env)"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Deploy
|
|
43
|
+
|
|
44
|
+
#### Option 1: Deploy by installing with pgpm
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pgpm install @pgpm/encrypted-secrets-table
|
|
48
|
+
pgpm deploy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Option 2: Deploy from Package Directory
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd packages/security/encrypted-secrets-table
|
|
55
|
+
pgpm deploy --createdb
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Option 3: Deploy from Workspace Root
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Install workspace dependencies
|
|
62
|
+
pnpm install
|
|
63
|
+
|
|
64
|
+
# Deploy with dependencies
|
|
65
|
+
pgpm deploy mydb1 --yes --createdb
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Database Schema
|
|
69
|
+
|
|
70
|
+
### secrets_schema.secrets_table
|
|
71
|
+
|
|
72
|
+
The core table for storing encrypted secrets:
|
|
73
|
+
|
|
74
|
+
```sql
|
|
75
|
+
CREATE TABLE secrets_schema.secrets_table (
|
|
76
|
+
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
77
|
+
secrets_owned_field uuid NOT NULL, -- Owner entity ID
|
|
78
|
+
name text NOT NULL, -- Secret name/identifier
|
|
79
|
+
secrets_value_field bytea NULL, -- Binary encrypted value
|
|
80
|
+
secrets_enc_field text NULL, -- Text-encoded encrypted value
|
|
81
|
+
UNIQUE(secrets_owned_field, name) -- One secret per name per owner
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Columns:**
|
|
86
|
+
- `id`: Unique identifier for the secret record
|
|
87
|
+
- `secrets_owned_field`: UUID of the entity that owns this secret (e.g., user ID, organization ID)
|
|
88
|
+
- `name`: Human-readable name for the secret (e.g., "github_token", "stripe_api_key")
|
|
89
|
+
- `secrets_value_field`: Binary encrypted value (bytea format)
|
|
90
|
+
- `secrets_enc_field`: Text-encoded encrypted value (for PGP armor format)
|
|
91
|
+
|
|
92
|
+
**Constraints:**
|
|
93
|
+
- Primary key on `id`
|
|
94
|
+
- Unique constraint on `(secrets_owned_field, name)` ensures each owner can have only one secret with a given name
|
|
95
|
+
|
|
96
|
+
### Triggers
|
|
97
|
+
|
|
98
|
+
#### hash_secrets
|
|
99
|
+
|
|
100
|
+
Automatically hashes secrets for verification purposes:
|
|
101
|
+
|
|
102
|
+
```sql
|
|
103
|
+
CREATE TRIGGER hash_secrets
|
|
104
|
+
BEFORE INSERT OR UPDATE ON secrets_schema.secrets_table
|
|
105
|
+
FOR EACH ROW
|
|
106
|
+
EXECUTE FUNCTION secrets_schema.hash_secrets_trigger();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This trigger ensures secrets are properly hashed before storage, enabling verification without exposing the raw values.
|
|
110
|
+
|
|
111
|
+
## Usage
|
|
112
|
+
|
|
113
|
+
### Storing a Secret
|
|
114
|
+
|
|
115
|
+
```sql
|
|
116
|
+
-- Insert an encrypted secret
|
|
117
|
+
INSERT INTO secrets_schema.secrets_table (
|
|
118
|
+
secrets_owned_field,
|
|
119
|
+
name,
|
|
120
|
+
secrets_value_field
|
|
121
|
+
) VALUES (
|
|
122
|
+
'user-uuid-here',
|
|
123
|
+
'api_key',
|
|
124
|
+
pgp_sym_encrypt('my-secret-value', 'encryption-password')
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Retrieving a Secret
|
|
129
|
+
|
|
130
|
+
```sql
|
|
131
|
+
-- Get encrypted secret
|
|
132
|
+
SELECT
|
|
133
|
+
id,
|
|
134
|
+
name,
|
|
135
|
+
pgp_sym_decrypt(secrets_value_field, 'encryption-password') AS decrypted_value
|
|
136
|
+
FROM secrets_schema.secrets_table
|
|
137
|
+
WHERE secrets_owned_field = 'user-uuid-here'
|
|
138
|
+
AND name = 'api_key';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Updating a Secret
|
|
142
|
+
|
|
143
|
+
```sql
|
|
144
|
+
-- Update existing secret
|
|
145
|
+
UPDATE secrets_schema.secrets_table
|
|
146
|
+
SET secrets_value_field = pgp_sym_encrypt('new-secret-value', 'encryption-password')
|
|
147
|
+
WHERE secrets_owned_field = 'user-uuid-here'
|
|
148
|
+
AND name = 'api_key';
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Deleting a Secret
|
|
152
|
+
|
|
153
|
+
```sql
|
|
154
|
+
-- Remove a secret
|
|
155
|
+
DELETE FROM secrets_schema.secrets_table
|
|
156
|
+
WHERE secrets_owned_field = 'user-uuid-here'
|
|
157
|
+
AND name = 'api_key';
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Common Patterns
|
|
161
|
+
|
|
162
|
+
### Multi-Tenant Secrets
|
|
163
|
+
|
|
164
|
+
```sql
|
|
165
|
+
-- Each organization has its own secrets
|
|
166
|
+
INSERT INTO secrets_schema.secrets_table (
|
|
167
|
+
secrets_owned_field,
|
|
168
|
+
name,
|
|
169
|
+
secrets_value_field
|
|
170
|
+
) VALUES
|
|
171
|
+
('org-1-uuid', 'aws_access_key', pgp_sym_encrypt('...', 'password')),
|
|
172
|
+
('org-1-uuid', 'aws_secret_key', pgp_sym_encrypt('...', 'password')),
|
|
173
|
+
('org-2-uuid', 'aws_access_key', pgp_sym_encrypt('...', 'password'));
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### User-Specific Secrets
|
|
177
|
+
|
|
178
|
+
```sql
|
|
179
|
+
-- Each user has their own API tokens
|
|
180
|
+
INSERT INTO secrets_schema.secrets_table (
|
|
181
|
+
secrets_owned_field,
|
|
182
|
+
name,
|
|
183
|
+
secrets_value_field
|
|
184
|
+
) VALUES
|
|
185
|
+
('user-1-uuid', 'github_token', pgp_sym_encrypt('...', 'password')),
|
|
186
|
+
('user-2-uuid', 'github_token', pgp_sym_encrypt('...', 'password'));
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Listing All Secrets for an Owner
|
|
190
|
+
|
|
191
|
+
```sql
|
|
192
|
+
-- Get all secret names for an owner (without values)
|
|
193
|
+
SELECT id, name
|
|
194
|
+
FROM secrets_schema.secrets_table
|
|
195
|
+
WHERE secrets_owned_field = 'user-uuid-here'
|
|
196
|
+
ORDER BY name;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Integration with Other Packages
|
|
200
|
+
|
|
201
|
+
### With @pgpm/encrypted-secrets
|
|
202
|
+
|
|
203
|
+
The `@pgpm/encrypted-secrets` package builds on this storage layer to provide:
|
|
204
|
+
- High-level API functions for secret management
|
|
205
|
+
- Role-based access control
|
|
206
|
+
- Encryption/decryption helpers
|
|
207
|
+
- Secret verification functions
|
|
208
|
+
|
|
209
|
+
```sql
|
|
210
|
+
-- @pgpm/encrypted-secrets provides functions like:
|
|
211
|
+
SELECT encrypted_secrets.secrets_getter('owner-uuid', 'secret-name');
|
|
212
|
+
SELECT encrypted_secrets.secrets_upsert('owner-uuid', 'secret-name', 'value');
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### With Application Tables
|
|
216
|
+
|
|
217
|
+
Link secrets to your application entities:
|
|
218
|
+
|
|
219
|
+
```sql
|
|
220
|
+
-- Users table with secrets
|
|
221
|
+
CREATE TABLE users (
|
|
222
|
+
id uuid PRIMARY KEY,
|
|
223
|
+
email text,
|
|
224
|
+
-- Secrets stored in secrets_schema.secrets_table
|
|
225
|
+
-- with secrets_owned_field = users.id
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
-- Get user's secrets
|
|
229
|
+
SELECT s.*
|
|
230
|
+
FROM users u
|
|
231
|
+
JOIN secrets_schema.secrets_table s ON s.secrets_owned_field = u.id
|
|
232
|
+
WHERE u.email = 'user@example.com';
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Security Considerations
|
|
236
|
+
|
|
237
|
+
1. **Encryption Required**: Never store plaintext secrets in this table
|
|
238
|
+
2. **Access Control**: Use PostgreSQL RLS policies to restrict access
|
|
239
|
+
3. **Encryption Keys**: Store encryption passwords securely (not in database)
|
|
240
|
+
4. **Audit Logging**: Consider logging access to secrets table
|
|
241
|
+
5. **Key Rotation**: Plan for periodic re-encryption with new keys
|
|
242
|
+
|
|
243
|
+
### Row-Level Security Example
|
|
244
|
+
|
|
245
|
+
```sql
|
|
246
|
+
-- Enable RLS
|
|
247
|
+
ALTER TABLE secrets_schema.secrets_table ENABLE ROW LEVEL SECURITY;
|
|
248
|
+
|
|
249
|
+
-- Users can only access their own secrets
|
|
250
|
+
CREATE POLICY user_secrets ON secrets_schema.secrets_table
|
|
251
|
+
FOR ALL
|
|
252
|
+
TO authenticated
|
|
253
|
+
USING (secrets_owned_field = jwt_public.current_user_id());
|
|
254
|
+
|
|
255
|
+
-- Administrators can access all secrets
|
|
256
|
+
CREATE POLICY admin_secrets ON secrets_schema.secrets_table
|
|
257
|
+
FOR ALL
|
|
258
|
+
TO administrator
|
|
259
|
+
USING (true);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Best Practices
|
|
263
|
+
|
|
264
|
+
1. **Use Unique Names**: Choose descriptive, unique names for each secret
|
|
265
|
+
2. **Consistent Ownership**: Use the same UUID scheme for `secrets_owned_field` across your application
|
|
266
|
+
3. **Binary vs Text**: Use `secrets_value_field` (bytea) for better performance, `secrets_enc_field` (text) for PGP armor format
|
|
267
|
+
4. **Don't Log Secrets**: Ensure database logs don't capture decrypted values
|
|
268
|
+
5. **Regular Cleanup**: Remove secrets when owners are deleted
|
|
269
|
+
|
|
270
|
+
## Dependencies
|
|
271
|
+
|
|
272
|
+
- `@pgpm/verify`: Verification utilities
|
|
273
|
+
|
|
274
|
+
## Testing
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
pnpm test
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Development
|
|
281
|
+
|
|
282
|
+
See the [Development](#development) section below for information on working with this package.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
### **Before You Begin**
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# 1. Install pgpm
|
|
292
|
+
npm install -g pgpm
|
|
293
|
+
|
|
294
|
+
# 2. Start Postgres (Docker or local)
|
|
295
|
+
pgpm docker start
|
|
296
|
+
|
|
297
|
+
# 3. Load PG* environment variables (PGHOST, PGUSER, ...)
|
|
298
|
+
eval "$(pgpm env)"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
### **Starting a New Project**
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# 1. Create a workspace
|
|
307
|
+
pgpm init --workspace
|
|
308
|
+
cd my-app
|
|
309
|
+
|
|
310
|
+
# 2. Create your first module
|
|
311
|
+
pgpm init
|
|
312
|
+
|
|
313
|
+
# 3. Add a migration
|
|
314
|
+
pgpm add some_change
|
|
315
|
+
|
|
316
|
+
# 4. Deploy (auto-creates database)
|
|
317
|
+
pgpm deploy --createdb
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### **Working With an Existing Project**
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
# 1. Clone and enter the project
|
|
326
|
+
git clone <repo> && cd <project>
|
|
327
|
+
|
|
328
|
+
# 2. Install dependencies
|
|
329
|
+
pnpm install
|
|
330
|
+
|
|
331
|
+
# 3. Deploy locally
|
|
332
|
+
pgpm deploy --createdb
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### **Testing a Module Inside a Workspace**
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# 1. Install workspace deps
|
|
341
|
+
pnpm install
|
|
342
|
+
|
|
343
|
+
# 2. Enter the module directory
|
|
344
|
+
cd packages/<some-module>
|
|
345
|
+
|
|
346
|
+
# 3. Run tests in watch mode
|
|
347
|
+
pnpm test:watch
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Related Tooling
|
|
351
|
+
|
|
352
|
+
* [pgpm](https://github.com/launchql/launchql/tree/main/packages/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
353
|
+
* [pgsql-test](https://github.com/launchql/launchql/tree/main/packages/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
|
|
354
|
+
* [supabase-test](https://github.com/launchql/launchql/tree/main/packages/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
355
|
+
* [graphile-test](https://github.com/launchql/launchql/tree/main/packages/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
|
|
356
|
+
* [pgsql-parser](https://github.com/launchql/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
357
|
+
* [libpg-query-node](https://github.com/launchql/libpg-query-node): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
358
|
+
* [pg-proto-parser](https://github.com/launchql/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
|
|
359
|
+
|
|
360
|
+
## Disclaimer
|
|
361
|
+
|
|
362
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
363
|
+
|
|
364
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# launchql-encrypted-secrets-table extension
|
|
2
2
|
comment = 'launchql-encrypted-secrets-table extension'
|
|
3
|
-
default_version = '0.
|
|
3
|
+
default_version = '0.5.0'
|
|
4
4
|
module_pathname = '$libdir/launchql-encrypted-secrets-table'
|
|
5
5
|
requires = 'pgcrypto,plpgsql,uuid-ossp,launchql-verify'
|
|
6
6
|
relocatable = false
|
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpm/encrypted-secrets-table",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Table-based encrypted secrets storage and retrieval",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"bundle": "
|
|
9
|
+
"bundle": "pgpm package",
|
|
10
10
|
"test": "jest",
|
|
11
11
|
"test:watch": "jest --watch"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@pgpm/verify": "0.
|
|
14
|
+
"@pgpm/verify": "0.6.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"
|
|
17
|
+
"pgpm": "^0.2.0"
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/launchql/
|
|
21
|
+
"url": "https://github.com/launchql/pgpm-modules"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/launchql/
|
|
23
|
+
"homepage": "https://github.com/launchql/pgpm-modules",
|
|
24
24
|
"bugs": {
|
|
25
|
-
"url": "https://github.com/launchql/
|
|
25
|
+
"url": "https://github.com/launchql/pgpm-modules/issues"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "c7d0eae588d7a764b382a330c8b853b341b13fb2"
|
|
28
28
|
}
|
|
File without changes
|
|
File without changes
|