-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathturbineproxy.example.toml
More file actions
440 lines (399 loc) · 20.7 KB
/
turbineproxy.example.toml
File metadata and controls
440 lines (399 loc) · 20.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# TurbineProxy Configuration
# Copy this file to turbineproxy.toml and adjust for your environment.
#
# ── Configuration Formats ─────────────────────────────────────────────────────
#
# TurbineProxy supports two configuration formats:
#
# NEW (recommended) — unified [shared] block
# Backend credentials and pool settings live under [shared]. Both the
# MySQL and PostgreSQL listeners inherit them automatically. Use this
# when running PostgreSQL-only or dual-protocol (MySQL + PostgreSQL).
#
# LEGACY — flat top-level keys
# The original MySQL-only format. Top-level [primary], listen_addr, etc.
# Still fully supported for backward compatibility. When top-level
# [primary] or listen_addr is present the MySQL listener starts
# automatically; the [shared] block is ignored.
#
# ── NEW unified format (shown below) ─────────────────────────────────────────
# ── Shared backend settings ───────────────────────────────────────────────────
# Settings here are inherited by both [mysql] and [pgsql] listeners when those
# sections do not override them.
[shared]
max_connections = 1000
pool_size = 20
# connection_max_idle_secs = 55
# auth_cache_ttl_secs = 300
# read_your_own_writes_ms = 0
# ── Bounded wait queue (optional) ────────────────────────────────────────────
# When a backend's max_connections is reached, up to pool_wait_queue_size requests
# will wait for a free slot instead of being rejected immediately.
# 0 = reject-fast (default, legacy behaviour).
# pool_wait_queue_size = 0
# Maximum time (ms) a request waits in the queue before being rejected. Default: 5000.
# pool_wait_timeout_ms = 5000
# Primary (read-write) backend. Both MySQL and PostgreSQL listeners share this
# backend unless overridden inside [mysql] or [pgsql].
[shared.primary]
addr = "127.0.0.1:3306"
user = "dbuser"
password = "secret"
database = "myapp"
# External secret references — TurbineProxy resolves these at connection time,
# so the actual secret value is never written to the config file or the SQLite
# database. Supported schemes:
#
# password = "env:DB_PASSWORD" # reads $DB_PASSWORD at runtime
# password = "file:/run/secrets/db_pw" # reads the file, trims whitespace
# # (works with Docker Secrets and
# # Kubernetes Secrets volume mounts)
# password = "literal-value" # plain string (default, no prefix)
# TLS for the primary connection (optional)
# tls_mode = "off" # off | required | verify-ca | verify-identity
# tls_ca = "/etc/ssl/ca.pem"
# tls_cert = "/etc/ssl/cert.pem"
# tls_key = "/etc/ssl/key.pem"
# ── Shared replica backends ───────────────────────────────────────────────────
# Receive read queries from both MySQL and PostgreSQL listeners.
# Add multiple [[shared.replicas]] sections for additional replicas.
# [[shared.replicas]]
# addr = "replica-1:3306"
# user = "dbuser"
# password = "secret"
# database = "myapp"
# weight = 100 # relative weight for weighted round-robin (default: 100)
# backup = false # if true, only used when all non-backup replicas are down
# max_connections = 50 # hard cap on open connections to this backend; omit for unlimited
# ── Shared per-user access rules (optional) ───────────────────────────────────
# When non-empty, only listed users can connect. Omit to accept any credentials
# (transparent / open mode — backward compatible).
# [[shared.users]]
# name = "app"
# password = "apppass"
# allow_writes = true # false = SELECT / SHOW / EXPLAIN only
# max_connections = 0 # 0 = unlimited
# ──────────────────────────────────────────────────────────────────────────────
# LEGACY format reminder (MySQL-only, still fully supported):
#
# listen_addr = "0.0.0.0:3307"
# max_connections = 1000
# pool_size = 20
#
# [primary]
# addr = "127.0.0.1:3306"
# user = "root"
# password = ""
# database = "myapp"
#
# [[replicas]]
# addr = "127.0.0.1:3307"
# weight = 100
# ──────────────────────────────────────────────────────────────────────────────
# ── MySQL listener (optional) ─────────────────────────────────────────────────
# The MySQL listener starts automatically only when [primary] or listen_addr
# appear at the top level (legacy format). In the new unified format ([shared]),
# the MySQL listener is DISABLED unless you add an explicit [mysql] section with
# enabled = true.
#
# [mysql]
# enabled = true
# listen_addr = "0.0.0.0:3307"
# # All other settings (primary, replicas, pool_size …) are inherited from [shared]
# # unless overridden here.
#
# # PROXY Protocol v2 — enable when sitting behind HAProxy, AWS NLB, or any
# # load balancer that prepends PROXY headers so the real client IP is preserved.
# # Both v1 (text) and v2 (binary) formats are auto-detected.
# [mysql.proxy_protocol]
# enabled = true
#
# # Server version string sent to MySQL clients in the initial handshake.
# # Useful when clients or frameworks require a specific version string.
# # Default: "8.0.36-TurbineProxy"
# [mysql]
# server_version = "5.7.44-aurora"
# ── PostgreSQL listener ───────────────────────────────────────────────────────
# When enabled, the proxy also listens on a PostgreSQL port and routes
# client connections to the backend(s) defined in [shared] (or overridden here).
[pgsql]
enabled = true
listen_addr = "0.0.0.0:5432"
# Database used for internal health probes (SELECT 1, pg_is_in_recovery()).
# Client sessions may connect to any database; probes always use this one.
# Must exist on every backend. Default: "postgres".
health_check_database = "postgres"
# Override pool settings for PostgreSQL only (optional — inherits from [shared]):
# pool_size = 20
# max_connections = 1000
# connection_max_idle_secs = 55
# read_your_own_writes_ms = 0
# PROXY Protocol v2 for PostgreSQL — same behaviour as MySQL above.
# [pgsql.proxy_protocol]
# enabled = true
# Server version string reported to PostgreSQL clients during the startup
# handshake. Useful when migrating to/from Aurora, Cloud SQL, etc.
# Default: "16.0".
# [pgsql]
# server_version = "15.4-aurora"
# PostgreSQL-specific backend override (optional — inherits from [shared.primary]):
# [pgsql.primary]
# addr = "pg-primary:5432"
# user = "postgres"
# password = "secret"
# database = "myapp"
# PostgreSQL health-check & failover thresholds (optional — inherits from [ha]):
# health_check_interval_secs = 10
# max_replica_lag_ms = 5000
# primary_failover_threshold = 3
# PostgreSQL frontend TLS (client → proxy, optional):
# ssl_cert = "/etc/turbineproxy/pg.crt"
# ssl_key = "/etc/turbineproxy/pg.key"
# ── Frontend TLS (client → MySQL proxy, optional) ─────────────────────────────
# Encrypt connections from your application to the MySQL listener.
# Requires a certificate and key in PEM format.
#
# [frontend_tls]
# enabled = false
# cert = "/etc/turbineproxy/server.crt"
# key = "/etc/turbineproxy/server.key"
# ── Security ──────────────────────────────────────────────────────────────────
# Applied to both MySQL and PostgreSQL listeners.
# SQL injection detection.
# When enabled, every inbound query is checked against a built-in library of
# classic and modern injection patterns (UNION SELECT, stacked queries, SLEEP(),
# BENCHMARK(), INTO OUTFILE, hex encoding, xp_cmdshell, etc.).
# Blocked queries return an error and are counted in the dashboard.
# sql_injection_protection = false
# Append-only NDJSON audit log.
# Every query is recorded with: timestamp, user, client IP, SQL text, routing
# destination (primary/replica), duration_ms, and whether it errored.
# Rotate externally with logrotate; the proxy re-opens the file on SIGHUP.
# audit_log = "/var/log/turbineproxy/audit.log"
# Query allowlist (whitelist mode).
# When non-empty, ONLY queries whose normalised fingerprint appears in this list
# are permitted to execute. All others are blocked with an error.
# query_whitelist = [
# "SELECT * FROM users WHERE id = ?",
# "INSERT INTO events (user_id, event) VALUES (?, ?)",
# ]
# ── Per-connection timeouts (optional) ────────────────────────────────────────
# Maximum duration (ms) for an open transaction before the proxy aborts it.
# 0 = disabled (default).
# max_transaction_time_ms = 30000
# Maximum duration (ms) for a single query. 0 = disabled (default).
# max_query_time_ms = 5000
# Maximum idle time (ms) for an open transaction. 0 = disabled (default).
# max_transaction_idle_ms = 0
# ── Query analytics ───────────────────────────────────────────────────────────
[analytics]
enabled = true
db_path = "turbineproxy_analytics.db"
# Queries slower than this threshold (in ms) are logged as slow queries
slow_query_ms = 100
# How many days of time-series data to retain before pruning. Default: 30.
retention_days = 30
# ── Web dashboard ─────────────────────────────────────────────────────────────
[dashboard]
enabled = true
listen_addr = "0.0.0.0:8080"
# Dashboard authentication (optional but recommended).
# When username and password are set, the UI requires login.
# Leave both empty (or omit) to disable auth — useful during local development.
username = ""
password = ""
# Optional read-only user: can view all panels but cannot create/update/delete
# routing rules, rewrite rules, or backends. Leave empty to disable.
# readonly_username = ""
# readonly_password = ""
# Session token lifetime in seconds. Tokens are stored as SHA-256 hashes in
# memory. A background sweeper evicts expired tokens every 60 s.
# 0 = tokens never expire. Default: 86400 (24 h).
# token_ttl_secs = 86400
# Maximum failed login attempts per source IP within a 60-second window before
# the endpoint returns HTTP 429. 0 = disabled. Default: 5.
# login_max_attempts = 5
# ── High-availability & health checks ────────────────────────────────────────
[ha]
enabled = true
# How often to run backend health checks (seconds). Default: 5.
health_check_interval_secs = 5
# Replica replication lag above this threshold (ms) is considered unhealthy. Default: 5000.
max_replica_lag_ms = 5000
# Consecutive primary health check failures before triggering failover. Default: 3.
primary_failover_threshold = 3
# After primary recovery, keep failover active for this many seconds before clearing.
# Prevents flapping when the primary is unstable. 0 = clear immediately. Default: 30.
# failover_cooldown_secs = 30
# Consecutive successful health checks required before clearing a failover.
# Symmetric to primary_failover_threshold. Default: 3.
# failover_min_recovery_checks = 3
# ── Per-backend circuit breaker ──────────────────────────────────────────────
# Opens the breaker for a backend after N consecutive errors, stopping traffic
# until recovery_ms elapses. States: Closed → Open → HalfOpen → Closed.
# Default threshold: 5 consecutive failures.
# circuit_breaker_threshold = 5
# Default recovery window: 10 000 ms (10 s).
# circuit_breaker_recovery_ms = 10000
# Galera / Percona XtraDB Cluster node-state checks.
# When true, the health checker queries SHOW GLOBAL STATUS LIKE 'wsrep_local_state'
# on every node. A node is only included in the read pool when wsrep_local_state = 4
# (SYNCED). Nodes that are Joining (1), Donor/Desynced (2), or Joined (3) are excluded.
# Safe to enable on standard async replication — nodes without wsrep are unaffected.
# Default: false.
# galera_check = false
# ── MySQL Group Replication / InnoDB Cluster (optional) ───────────────────────
# When enabled, TurbineProxy polls performance_schema.replication_group_members
# on any reachable backend and automatically re-routes writes to the elected
# PRIMARY — without a proxy restart.
#
# Requirements:
# - MySQL Group Replication or InnoDB Cluster must be configured on the servers
# - The proxy user must have SELECT on performance_schema
# - All group members must be listed as primary or replicas in this config
#
# [group_replication]
# enabled = true
# check_interval_secs = 5 # how often to poll for primary changes (default: 5)
# ── TurbineProxy Cluster (multi-instance config sync) ─────────────────────────
#
# When running multiple TurbineProxy instances (e.g. behind a load balancer),
# config changes made on one node can be automatically pushed to all peers.
#
# Every reload triggered by SIGHUP, POST /api/reload, or POST /api/reload/backends
# pushes the current config TOML to each peer's POST /api/sync endpoint.
# The peer validates the request using the shared secret and applies the config
# atomically (hot-swaps the backend pool; no restart required).
#
# Security notes:
# - The shared secret is sent as `Authorization: Bearer <secret>` over HTTP.
# Use HTTPS termination (nginx/caddy) in front of the dashboard for production.
# - A node does NOT push to itself — list only the OTHER nodes as peers.
# - Set the same secret on every node; empty secret disables sync entirely.
#
# [cluster]
# peers = ["http://node2.internal:8080", "http://node3.internal:8080"]
# secret = "change-this-to-a-strong-random-string"
# ── Query Routing Rules (optional) ────────────────────────────────────────────
#
# Rules are evaluated in declaration order. The first match wins.
# If no rule matches, the built-in heuristic applies (SELECT → replica).
#
# Fields:
# match_pattern — PCRE regex matched against the raw SQL text
# match_digest — Exact normalised query fingerprint (overrides match_pattern)
# user — Restrict to a specific MySQL user (omit = all users)
# schema — Restrict to a specific database/schema (omit = all)
# destination — "primary" | "replica" | "any" (default heuristic)
# destination_hostgroup — Route to a specific hostgroup index: 0=primary, 1..N=replica N
# Takes precedence over `destination` when specified.
# cache_ttl_secs — 0 = no cache; >0 = cache with default TTL
# mirror_to — Address (host:port) to also send matching queries to
# (fire-and-forget, client always gets the real backend response).
# Use this to shadow traffic to a canary or new server.
# rollout_pct — 1–100: percentage of matching queries routed via this rule.
# Remaining traffic falls through to the next rule / heuristic.
# Omit or set to 0 to apply the rule to all matching queries.
# dry_run — true = log the match but do NOT apply the rule; the query
# falls through to the next matching rule or the default
# heuristic. Useful for testing new rules in production
# without affecting traffic.
# qps_limit — Maximum queries per second this rule will forward to the
# backend. Implemented as a token-bucket: bursts up to
# `qps_limit` tokens are absorbed instantly; sustained traffic
# above the limit is rejected with "Too many requests".
# 0 or omit = unlimited.
# fast_forward — true = bypass routing/analytics/security for queries matching
# this rule and send directly to the primary. More surgical than
# the global `fast_forward` option — only queries matching this
# rule's pattern/user/schema are affected. Default: false.
# comment — Human-readable description shown in the dashboard
#
# Example: force read-after-write queries to the primary
# [[query_rules]]
# match_pattern = "SELECT .* FROM users WHERE id = \\?"
# destination = "primary"
# cache_ttl_secs = 0
# comment = "Read-after-write for user lookups"
#
# Example: cache expensive report queries on replica
# [[query_rules]]
# match_pattern = "(?i)SELECT .* FROM (orders|invoices)"
# destination = "replica"
# cache_ttl_secs = 30
# comment = "Heavy reporting queries — replica + cache"
#
# Example: force a specific user's traffic to primary
# [[query_rules]]
# user = "admin"
# destination = "primary"
# comment = "Admin user always uses primary"
#
# Example: pin reporting queries to a specific replica (hostgroup 2)
# [[query_rules]]
# match_pattern = "(?i)SELECT .* FROM report_"
# destination_hostgroup = 2
# cache_ttl_secs = 60
# comment = "Reports pinned to replica 2 (hostgroup 2)"
#
# Example: shadow 10 % of SELECT traffic to a canary server
# [[query_rules]]
# match_pattern = "(?i)^SELECT "
# destination = "replica"
# mirror_to = "canary-db:3306"
# rollout_pct = 10
# comment = "Shadow 10% of reads to canary for validation"
#
# Example: test a new rule without affecting traffic (dry run)
# [[query_rules]]
# match_pattern = "(?i)SELECT .* FROM new_table"
# destination = "replica"
# dry_run = true
# comment = "Dry-run: log matches but don't route yet"
#
# Example: rate-limit expensive analytics queries to 20 QPS
# [[query_rules]]
# match_pattern = "(?i)SELECT .* FROM analytics"
# destination = "replica"
# qps_limit = 20
# comment = "Analytics queries capped at 20 QPS (token bucket)"
#
# Example: fast-forward a hot status-check query (skip analytics/rules overhead)
# [[query_rules]]
# match_pattern = "(?i)^SELECT 1$"
# destination = "primary"
# fast_forward = true
# comment = "Health-check ping: zero-overhead fast path"
# ── Query Rewriting Rules (optional) ──────────────────────────────────────────
#
# Rewrite rules alter the SQL text before it is dispatched to the backend.
# They run before routing rules and the query cache.
#
# Fields:
# match_pattern — PCRE regex matched against the raw SQL text (required)
# replace_with — Regex replacement string; supports $1/$2 backreferences
# add_limit — Append LIMIT N if the SELECT has no existing LIMIT clause
# add_timeout_ms — Inject /*+ MAX_EXECUTION_TIME(N) */ after SELECT keyword
# block — true = reject the query and return an error to the client
# comment — Human-readable description shown in the Rewrite Rules tab
#
# Example: protect a large table from unbounded scans
# [[rewrite_rules]]
# match_pattern = "(?i)SELECT .* FROM large_table"
# add_limit = 1000
# comment = "Force LIMIT 1000 on large_table queries"
# Example: cap all SELECTs at 5 seconds execution time
# [[rewrite_rules]]
# match_pattern = "(?i)^SELECT "
# add_timeout_ms = 5000
# comment = "Cap all SELECTs at 5s"
# Example: replace SELECT * with explicit columns (audit helper)
# [[rewrite_rules]]
# match_pattern = "(?i)SELECT \\* FROM users"
# replace_with = "SELECT id, email, created_at FROM users"
# comment = "Rewrite SELECT * on users to explicit column list"
# Example: block DDL statements from application connections
# [[rewrite_rules]]
# match_pattern = "(?i)^(DROP|TRUNCATE|ALTER)\\s"
# block = true
# comment = "Block destructive DDL from application layer"