@sysnee/pgs 0.1.6 → 0.1.7-rc.10
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/README.md +155 -188
- package/docker-compose.yml +9 -6
- package/docs/ARCHITECTURE_RECOMMENDATIONS.md +11 -9
- package/docs/CRITICAL_REVIEW.md +10 -8
- package/docs/EXECUTIVE_SUMMARY.md +9 -9
- package/docs/PROJECT.md +130 -77
- package/manager.js +192 -97
- package/package.json +2 -2
- package/traefik.yml +15 -0
- package/haproxy-lua/pg-route.lua +0 -177
- package/haproxy.cfg +0 -35
package/docs/PROJECT.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
### What It Is
|
|
6
6
|
|
|
7
|
-
A **dynamic PostgreSQL multi-tenant management system** that provides complete database isolation by creating dedicated PostgreSQL instances per tenant. The system uses
|
|
7
|
+
A **dynamic PostgreSQL multi-tenant management system** that provides complete database isolation by creating dedicated PostgreSQL instances per tenant. The system uses **Traefik v3** with native PostgreSQL STARTTLS support to route connections based on SNI (Server Name Indication), enabling hostname-based tenant routing with TLS security.
|
|
8
8
|
|
|
9
9
|
### Core Concept
|
|
10
10
|
|
|
@@ -22,15 +22,16 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
22
22
|
- Automatic volume and network configuration
|
|
23
23
|
- Custom initialization scripts per tenant
|
|
24
24
|
|
|
25
|
-
2. **
|
|
26
|
-
-
|
|
27
|
-
- Routes connections
|
|
25
|
+
2. **SNI-Based Routing**
|
|
26
|
+
- Traefik v3 handles PostgreSQL STARTTLS protocol
|
|
27
|
+
- Routes connections based on hostname (SNI)
|
|
28
28
|
- Single external port (5432) for all tenants
|
|
29
|
+
- TLS/SSL encryption required
|
|
29
30
|
|
|
30
31
|
3. **Access Control**
|
|
31
32
|
- Per-tenant external access enable/disable
|
|
32
|
-
- Secure by default (access disabled on creation)
|
|
33
33
|
- Runtime access control without service restart
|
|
34
|
+
- Dynamic Traefik configuration updates
|
|
34
35
|
|
|
35
36
|
4. **Complete Isolation**
|
|
36
37
|
- Separate Docker containers per tenant
|
|
@@ -39,49 +40,50 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
39
40
|
- No shared processes or memory
|
|
40
41
|
|
|
41
42
|
5. **Zero-Downtime Operations**
|
|
42
|
-
-
|
|
43
|
+
- Traefik dynamic configuration (no restarts needed)
|
|
43
44
|
- Independent tenant management
|
|
44
45
|
- No impact on other tenants during operations
|
|
45
46
|
|
|
46
47
|
## Architecture
|
|
47
48
|
|
|
48
49
|
```
|
|
49
|
-
|
|
50
|
-
│
|
|
51
|
-
│
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
│
|
|
57
|
-
│
|
|
58
|
-
│ │
|
|
59
|
-
│ │ -
|
|
60
|
-
│ │ -
|
|
61
|
-
│ │ -
|
|
62
|
-
│ │ -
|
|
63
|
-
│
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
┌──────────────┐
|
|
70
|
-
│
|
|
71
|
-
│
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
│
|
|
78
|
-
│
|
|
79
|
-
│
|
|
80
|
-
│
|
|
81
|
-
│
|
|
82
|
-
│
|
|
83
|
-
│
|
|
84
|
-
|
|
50
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
51
|
+
│ External Access │
|
|
52
|
+
│ (tenant-id.pgs.domain.com:5432 + TLS/SNI) │
|
|
53
|
+
└───────────────────────────┬─────────────────────────────────────┘
|
|
54
|
+
│
|
|
55
|
+
▼
|
|
56
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
57
|
+
│ Traefik v3 Proxy │
|
|
58
|
+
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
59
|
+
│ │ EntryPoint: postgres (port 5432) │ │
|
|
60
|
+
│ │ - TLS termination with wildcard certificate │ │
|
|
61
|
+
│ │ - PostgreSQL STARTTLS protocol support │ │
|
|
62
|
+
│ │ - SNI-based routing (HostSNI rule) │ │
|
|
63
|
+
│ │ - Dynamic configuration via dynamic.yml │ │
|
|
64
|
+
│ └───────────────────────────────────────────────────────────┘ │
|
|
65
|
+
└───────────────────────────┬─────────────────────────────────────┘
|
|
66
|
+
│
|
|
67
|
+
┌──────────────────┼──────────────────┐
|
|
68
|
+
│ │ │
|
|
69
|
+
▼ ▼ ▼
|
|
70
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
71
|
+
│ TCP Router │ │ TCP Router │ │ TCP Router │
|
|
72
|
+
│ tenant1 │ │ tenant2 │ │ tenant3 │
|
|
73
|
+
│ HostSNI() │ │ HostSNI() │ │ HostSNI() │
|
|
74
|
+
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
|
|
75
|
+
│ │ │
|
|
76
|
+
▼ ▼ ▼
|
|
77
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
78
|
+
│ PostgreSQL │ │ PostgreSQL │ │ PostgreSQL │
|
|
79
|
+
│ Container 1 │ │ Container 2 │ │ Container 3 │
|
|
80
|
+
│ │ │ │ │ │
|
|
81
|
+
│ Port: 5432 │ │ Port: 5432 │ │ Port: 5432 │
|
|
82
|
+
│ (internal) │ │ (internal) │ │ (internal) │
|
|
83
|
+
│ │ │ │ │ │
|
|
84
|
+
│ Volume: │ │ Volume: │ │ Volume: │
|
|
85
|
+
│ pgdata_1 │ │ pgdata_2 │ │ pgdata_3 │
|
|
86
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
85
87
|
```
|
|
86
88
|
|
|
87
89
|
## Technical Implementation
|
|
@@ -91,37 +93,88 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
91
93
|
1. **Manager Script (manager.js)**
|
|
92
94
|
- Node.js CLI tool for tenant lifecycle management
|
|
93
95
|
- Dynamically generates docker-compose.yml entries
|
|
94
|
-
- Manages
|
|
96
|
+
- Manages Traefik dynamic configuration (dynamic.yml)
|
|
95
97
|
- Controls tenant access permissions
|
|
96
98
|
|
|
97
|
-
2. **
|
|
98
|
-
- TCP-level
|
|
99
|
-
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
99
|
+
2. **Traefik v3 Reverse Proxy**
|
|
100
|
+
- TCP-level routing with TLS termination
|
|
101
|
+
- Native PostgreSQL STARTTLS protocol support
|
|
102
|
+
- SNI-based routing via HostSNI() rule
|
|
103
|
+
- Dynamic configuration without restarts
|
|
102
104
|
|
|
103
|
-
3. **
|
|
104
|
-
- Parses binary PostgreSQL startup packet
|
|
105
|
-
- Extracts username and connection parameters
|
|
106
|
-
- Handles SSL negotiation
|
|
107
|
-
- Enforces access control policies
|
|
108
|
-
|
|
109
|
-
4. **Docker Infrastructure**
|
|
105
|
+
3. **Docker Infrastructure**
|
|
110
106
|
- Separate container per tenant
|
|
111
107
|
- Bridge network for internal communication
|
|
112
108
|
- Persistent volumes for data
|
|
113
109
|
- Isolated execution environments
|
|
114
110
|
|
|
111
|
+
### Why Traefik v3 for PostgreSQL?
|
|
112
|
+
|
|
113
|
+
PostgreSQL uses a non-standard TLS negotiation called STARTTLS:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
Standard TLS (HTTPS):
|
|
117
|
+
Client → TLS ClientHello (with SNI) → Server
|
|
118
|
+
|
|
119
|
+
PostgreSQL STARTTLS:
|
|
120
|
+
Client → SSLRequest (PG protocol) → Server
|
|
121
|
+
Server → 'S' (SSL supported) → Client
|
|
122
|
+
Client → TLS ClientHello (with SNI) → Server
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Most SNI proxies expect TLS ClientHello as the first packet. PostgreSQL sends its own protocol first, then TLS.
|
|
126
|
+
|
|
127
|
+
**Traefik v3** is one of the few proxies that natively understands this PostgreSQL-specific flow:
|
|
128
|
+
- Detects PostgreSQL SSLRequest
|
|
129
|
+
- Responds with 'S'
|
|
130
|
+
- Captures TLS ClientHello with SNI
|
|
131
|
+
- Routes based on hostname
|
|
132
|
+
|
|
115
133
|
### Connection Flow
|
|
116
134
|
|
|
117
|
-
1. Client connects to `
|
|
118
|
-
2.
|
|
119
|
-
3.
|
|
120
|
-
4.
|
|
121
|
-
5.
|
|
122
|
-
6.
|
|
135
|
+
1. Client connects to `tenant-id.pgs.domain.com:5432` with `sslmode=require`
|
|
136
|
+
2. Traefik receives connection and detects PostgreSQL SSLRequest
|
|
137
|
+
3. Traefik responds 'S' (SSL supported)
|
|
138
|
+
4. Client sends TLS ClientHello with SNI (hostname)
|
|
139
|
+
5. Traefik extracts SNI and matches against configured routers in dynamic.yml
|
|
140
|
+
6. If tenant has access enabled, routes to backend `pgs_{tenant_id}:5432`
|
|
123
141
|
7. Connection established with complete isolation
|
|
124
142
|
|
|
143
|
+
### Configuration Files
|
|
144
|
+
|
|
145
|
+
**traefik.yml** (Static Configuration):
|
|
146
|
+
```yaml
|
|
147
|
+
entryPoints:
|
|
148
|
+
postgres:
|
|
149
|
+
address: ":5432"
|
|
150
|
+
|
|
151
|
+
providers:
|
|
152
|
+
file:
|
|
153
|
+
filename: /etc/traefik/dynamic.yml
|
|
154
|
+
watch: true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**dynamic.yml** (Dynamic Configuration - Auto-generated):
|
|
158
|
+
```yaml
|
|
159
|
+
tcp:
|
|
160
|
+
routers:
|
|
161
|
+
router_tenant1:
|
|
162
|
+
entryPoints:
|
|
163
|
+
- postgres
|
|
164
|
+
rule: "HostSNI(`tenant1-abc123.pgs.domain.com`)"
|
|
165
|
+
service: svc_tenant1
|
|
166
|
+
tls: {}
|
|
167
|
+
services:
|
|
168
|
+
svc_tenant1:
|
|
169
|
+
loadBalancer:
|
|
170
|
+
servers:
|
|
171
|
+
- address: "pgs_tenant1-abc123:5432"
|
|
172
|
+
tls:
|
|
173
|
+
certificates:
|
|
174
|
+
- certFile: /etc/traefik/certs/fullchain.pem
|
|
175
|
+
keyFile: /etc/traefik/certs/privkey.pem
|
|
176
|
+
```
|
|
177
|
+
|
|
125
178
|
## Comparison with Similar Solutions
|
|
126
179
|
|
|
127
180
|
### Shared Database Architecture
|
|
@@ -152,7 +205,7 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
152
205
|
- **Difference**: Shards data across nodes; this creates separate instances per tenant
|
|
153
206
|
- **Use Case**: Horizontal scaling vs. tenant isolation
|
|
154
207
|
|
|
155
|
-
#### 3. **Patroni + HAProxy**
|
|
208
|
+
#### 3. **Patroni + Traefik/HAProxy**
|
|
156
209
|
- **Purpose**: High availability and load balancing
|
|
157
210
|
- **Difference**: Replicates single database; this creates isolated instances
|
|
158
211
|
- **Use Case**: HA for single database vs. multi-tenant isolation
|
|
@@ -173,18 +226,18 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
173
226
|
- Not just database or schema separation
|
|
174
227
|
- Complete process and memory isolation
|
|
175
228
|
|
|
176
|
-
2. **
|
|
229
|
+
2. **SNI-based routing with single external port**
|
|
177
230
|
- No need for port management
|
|
178
|
-
- Automatic routing based on
|
|
231
|
+
- Automatic routing based on hostname
|
|
232
|
+
- TLS/SSL security by default
|
|
179
233
|
|
|
180
|
-
3. **
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
- Handles SSL negotiation
|
|
234
|
+
3. **PostgreSQL STARTTLS support**
|
|
235
|
+
- Traefik v3 handles non-standard PG protocol
|
|
236
|
+
- Proper SNI extraction after PG handshake
|
|
184
237
|
|
|
185
238
|
4. **Runtime access control**
|
|
186
239
|
- Enable/disable tenant access without restart
|
|
187
|
-
-
|
|
240
|
+
- Dynamic Traefik configuration
|
|
188
241
|
|
|
189
242
|
5. **Docker-native architecture**
|
|
190
243
|
- Leverages container isolation
|
|
@@ -211,9 +264,9 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
211
264
|
## Advantages
|
|
212
265
|
|
|
213
266
|
✅ **Maximum Isolation**: Complete process and data separation
|
|
214
|
-
✅ **Security**: Zero risk of cross-tenant data access
|
|
267
|
+
✅ **Security**: Zero risk of cross-tenant data access + TLS encryption
|
|
215
268
|
✅ **Flexibility**: Independent scaling and management per tenant
|
|
216
|
-
✅ **Simplicity**: Single external port, automatic routing
|
|
269
|
+
✅ **Simplicity**: Single external port, automatic SNI routing
|
|
217
270
|
✅ **Compliance**: Easier to meet regulatory requirements
|
|
218
271
|
✅ **Debugging**: Isolated environments simplify troubleshooting
|
|
219
272
|
|
|
@@ -223,15 +276,16 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
223
276
|
⚠️ **Management Overhead**: More containers to manage
|
|
224
277
|
⚠️ **Scaling Limits**: Practical limit on number of tenants per host
|
|
225
278
|
⚠️ **Backup Complexity**: Need to backup multiple instances
|
|
279
|
+
⚠️ **SSL Required**: Clients must support TLS with SNI
|
|
226
280
|
|
|
227
281
|
## Technology Stack
|
|
228
282
|
|
|
229
283
|
- **Runtime**: Node.js (ES Modules)
|
|
230
284
|
- **Container Orchestration**: Docker Compose
|
|
231
|
-
- **Reverse Proxy**:
|
|
285
|
+
- **Reverse Proxy**: Traefik v3 (PostgreSQL STARTTLS + SNI routing)
|
|
232
286
|
- **Database**: PostgreSQL 18+
|
|
233
|
-
- **
|
|
234
|
-
- **Configuration**: YAML (docker-compose.yml), JSON (tenant-access.json)
|
|
287
|
+
- **TLS**: Wildcard SSL certificate
|
|
288
|
+
- **Configuration**: YAML (docker-compose.yml, traefik.yml, dynamic.yml), JSON (tenant-access.json)
|
|
235
289
|
|
|
236
290
|
## Future Enhancements
|
|
237
291
|
|
|
@@ -242,9 +296,8 @@ Instead of sharing a single PostgreSQL instance with multiple databases (shared
|
|
|
242
296
|
- [ ] Tenant migration tools
|
|
243
297
|
- [ ] Kubernetes support
|
|
244
298
|
- [ ] Connection pooling per tenant
|
|
245
|
-
- [ ]
|
|
299
|
+
- [ ] Web dashboard
|
|
246
300
|
|
|
247
301
|
## License & Status
|
|
248
302
|
|
|
249
|
-
This is a custom solution built for specific multi-tenant requirements. It combines open-source tools (
|
|
250
|
-
|
|
303
|
+
This is a custom solution built for specific multi-tenant requirements. It combines open-source tools (Traefik v3, PostgreSQL, Docker) with SNI-based routing to achieve instance-per-tenant isolation with intelligent connection routing.
|