Security
Authentication
ElyraSQL implements the MySQL mysql_native_password handshake (the default) and
caching_sha2_password (MySQL 8's default plugin, opt-in via
ELYRASQL_AUTH_PLUGIN=caching_sha2_password). Passwords are never stored in
plaintext — only SHA1(SHA1(password)), the same digest MySQL keeps — and each
connection uses a fresh salt.
mysql_native_passwordverifies the challenge/response against the stored digest without reconstructing the password. Works with every MySQL client.caching_sha2_passwordruns full authentication: over TLS the client sends the password (protected by the TLS channel); on a plaintext connection the client encrypts it with the server's RSA public key (RSA-OAEP). The recovered cleartext is checked against the sameSHA1(SHA1(password))digest — still never persisted in the clear. Prefer TLS so no RSA is involved.
Configure users on the command line:
# a single admin user
elyrasql serve --user root --password s3cret
# multiple users with roles
elyrasql serve \
--auth admin:adminpw:admin \
--auth app:apppw:write \
--auth analyst:ropw:read
Open mode
With no users configured, ElyraSQL accepts any login as Admin. This is
for local development only.
Safe by default: the server refuses to start in open mode when its
listener is bound to a non-loopback address (e.g. 0.0.0.0, a routable IP, or
a hostname) — the common way to accidentally expose a credential-less database.
To run anyway you must either configure accounts (--user/--password or
--auth USER:PASS:ROLE), keep the bind on localhost (the default), or set
ELYRASQL_ALLOW_OPEN_AUTH=1 to explicitly override. The default
127.0.0.1:3307 bind is unaffected, so local development is unchanged.
The replication endpoint is likewise guarded, and more strictly: it hands
a full copy of the database to every connecting peer, so it is refused
entirely (any bind address, loopback included) unless
ELYRASQL_CLUSTER_SECRET is set or ELYRASQL_ALLOW_OPEN_AUTH=1 explicitly
opts in. Replication authentication is mutual: the replica proves
knowledge of the secret, and the primary must prove it back before the
replica applies any snapshot or write-set — a replica refuses to start
without a secret (same override applies), so a spoofed primary cannot feed
it fabricated data.
A replica's MySQL listener is guarded the same way: elyrasql replica
refuses to start without accounts (--user/--password or
--auth USER:PASS:ROLE) unless ELYRASQL_ALLOW_OPEN_AUTH=1 explicitly
opts in — a credential-less replica would hand Admin access to its entire
replicated data set to anyone who can reach the port.
Encrypting replication: set ELYRASQL_CLUSTER_TLS_CERT + _KEY on the
primary and ELYRASQL_CLUSTER_TLS_CA on the replica. The replica then verifies
the primary's certificate (a mismatched cert is rejected — not accept-any), so
the stream is confidential and the primary is authenticated, while the shared
secret authenticates the replica (mutual authentication). The Raft control
plane (leader election + AppendEntries) uses the same ELYRASQL_CLUSTER_TLS_*
variables and is encrypted the same way — each node presents its certificate
and verifies its peers' certificates, so a node that cannot verify its peers
cannot join the cluster. All inter-node traffic (consensus + replication) is
therefore encrypted when the cluster TLS variables are set.
Roles
Privileges are hierarchical: read < write < admin. The engine enforces the
minimum privilege per statement.
| Role | May run |
|---|---|
read |
SELECT, transactions, session commands |
write |
the above + INSERT, UPDATE, DELETE |
admin |
the above + DDL (CREATE, DROP, ALTER, CREATE INDEX) |
A denied statement returns an access-denied error and is not executed.
Managing users with SQL
Besides the startup --auth flags (which define bootstrap accounts that always
work), accounts can be created at runtime and are persisted in the database
file, so they survive restarts:
CREATE USER 'app'@'%' IDENTIFIED BY 's3cret'; -- created read-only
GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'app'; -- promote to write
GRANT ALL PRIVILEGES ON *.* TO 'admin_user'; -- promote to admin
REVOKE ALL PRIVILEGES ON *.* FROM 'app'; -- back to read-only
SET PASSWORD FOR 'app' = 'newsecret';
SHOW GRANTS FOR 'app';
DROP USER 'app';
Notes and current limitations:
- New accounts start read-only; use
GRANTto raise them. - Global grants track the individual privileges granted as a set, so
GRANT/REVOKE ON *.*add/remove exactly the named privileges. Revoking one privilege no longer collapses the account: e.g.REVOKE INSERTfrom an admin keeps every other privilege.SHOW GRANTSlists the precise set. - DML privileges are enforced per action, per table.
INSERT,UPDATEandDELETEare checked individually against the target table's effective grant (global ∪ per-table ∪ role-inherited), so a user granted onlyINSERTcannotUPDATE/DELETE, and revoking one write privilege blocks only that action. Administrative statements and DDL (CREATE/DROP/ALTER/CREATE INDEX, triggers, procedures,BACKUP,LOAD DATA, ...) are gated at theadmintier (GRANT ALL/GRANT OPTION/SUPER). Reads are allowed at the baseline for any authenticated user (no table-levelSELECTgrant required). - Scope:
GRANT ... ON *.*(ordb.*) sets the account's global privileges;GRANT ... ON <table>(ordb.table) is a per-table grant of exactly the named privileges on that table only (stored as a privilege set, soREVOKE ... ON <table>removes just those). Reads are allowed at the global baseline, so table grants are used to give a read-only account specific write privileges on specific tables.REVOKE ON <table>removes a table grant. DROP USERpurges the account's global, per-table, per-column, and role- membership grants, so recreating a user with the same name does not inherit stale privileges.- Enforcement is deny-safe: a write/DDL statement whose target table can't be
determined (e.g. a multi-table
UPDATE) requires the global privilege.SHOW GRANTSlists the global grant and each table grant. - The host part of
'user'@'host'is accepted but ignored (accounts are host-independent). - Passwords are stored only as
SHA1(SHA1(password)). - A privilege change takes effect on the account's next connection.
- Managing users requires the admin privilege. Creating the first account
(in an otherwise open/dev server) turns authentication on for subsequent
connections — keep a bootstrap
--authadmin so you don't lock yourself out.
TLS
Provide a PEM certificate and key to enable TLS (rustls 0.23). Clients that request SSL are upgraded to an encrypted connection; others continue in plaintext.
elyrasql serve --tls-cert server.crt --tls-key server.key
Generate a self-signed certificate for testing. rustls requires an X.509 v3
certificate, so include a subjectAltName (a bare -subj alone produces a v1
certificate rustls will reject):
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
-keyout server.key -out server.crt -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
mysql -h 127.0.0.1 -P 3307 -u root -p --ssl-mode=REQUIRED
Resource limits (denial-of-service)
ElyraSQL bounds the recursion an untrusted query can trigger, so a single hostile statement cannot exhaust the worker-thread stack and abort the process:
- Expression depth. Deeply-nested expressions of any shape — arithmetic/
boolean/bitwise chains (
1+1+1..., hugeORchains), parentheses and function nesting, JSON->/->>chains, and postfix subscript/call chains (x[0][0]...) — are rejected with a normal SQL error before parsing, so they can never build a deep AST that overflows the stack. Configurable viaELYRASQL_MAX_EXPR_DEPTH(default 2000, clamped 64..5000). Wide-but-shallow queries (longINlists, large multi-rowINSERTs, multi-statement batches) are unaffected. - JSON nesting. JSON documents are parsed to a maximum nesting depth of 200 (both on write and when read by JSON functions); a deeper document is treated as invalid JSON rather than crashing.
- Other resource bounds:
ELYRASQL_TXN_MAX_BYTES(uncommitted transaction size),ELYRASQL_SORT_MAX_ROWS/ELYRASQL_GROUP_MAX_GROUPS(spill thresholds),ELYRASQL_MAX_FRAME_MB(max network/binlog/spill frame). See Configuration.
To report a vulnerability, use GitHub's private vulnerability reporting on the
repository (Security tab); see SECURITY.md.
Hardening checklist
- Configure
--user/--passwordor--auth(never run open in production). - Enable TLS with a real certificate.
- Bind to a private interface or firewall the port; only bind
0.0.0.0when intended. - Run under the dedicated
elyrasqlsystem user (the systemd unit does). - Grant each application the least privilege it needs.