Kerberos (GSSAPI) authentication
skaidb can authenticate clients with a Kerberos ticket instead of a
password, following an external-authentication model: a KDC vouches for
the principal, and skaidb only maps that principal to a role. Both the binary
protocol (drivers, skaidbsh) and REST/HTTP (browser SSO via SPNEGO) support
it, alongside — not instead of — SCRAM password auth.
Availability
Kerberos links the system Kerberos C library, so it is compiled into the
glibc .deb/.rpm builds (and macOS/Windows client builds when enabled),
but not the static-musl .tar.gz — that binary ships without it. Starting
a musl build with auth.gssapi_enabled = true is a hard startup error, never a
silent no-op. The .deb declares a runtime dependency on libgssapi-krb5-2
(.rpm: krb5-libs), so a minimal host pulls the library automatically.
Prerequisites
- A Kerberos realm and KDC (MIT krb5, Active Directory, FreeIPA, …).
- A service principal for skaidb, conventionally
skaidb/<host>@REALM, and its keytab exported to each skaidb node. - Clients with a ticket-granting ticket (from
kinit, a login manager, or a client keytab). - Clocks synchronized within the KDC's skew tolerance (default 5 minutes) — Kerberos rejects tickets outside it. Run NTP everywhere.
Create the service principal and keytab (MIT krb5)
On the KDC:
kadmin.local -q "addprinc -randkey skaidb/node1.example.com@EXAMPLE.COM"
kadmin.local -q "ktadd -k /etc/skaidb/skaidb.keytab skaidb/node1.example.com@EXAMPLE.COM"
Copy skaidb.keytab to the node, own it by the skaidb user, and lock it down:
chown skaidb:skaidb /etc/skaidb/skaidb.keytab
chmod 640 /etc/skaidb/skaidb.keytab
Server configuration
In /etc/skaidb/skaidb.toml:
[auth]
scram_enabled = true # SCRAM stays available alongside GSSAPI
gssapi_enabled = true
gssapi_keytab = "/etc/skaidb/skaidb.keytab"
# Optional: accept only this exact SPN. Empty accepts whatever the keytab holds
# (the usual case — the acceptor tries every key in the keytab).
gssapi_service_principal = "skaidb/node1.example.com@EXAMPLE.COM"
[encryption]
# Strongly recommended: GSSAPI authenticates the client but does NOT encrypt
# the SQL stream. Run it inside client TLS for confidentiality.
client_tls = "required"
Environment-variable equivalents (e.g. for Docker): SKAIDB_GSSAPI_ENABLED,
SKAIDB_GSSAPI_KEYTAB, SKAIDB_GSSAPI_SERVICE_PRINCIPAL.
The server reads the keytab once at startup (as the GSSAPI KRB5_KTNAME) and
resolves the realm from the ambient /etc/krb5.conf. A missing or unreadable
keytab fails startup loudly.
Create the external users
A Kerberos user is external — passwordless, keyed by its principal. The
principal contains @ and ., so double-quote it:
CREATE USER "alice@EXAMPLE.COM" GSSAPI;
GRANT SELECT ON DATABASE app TO "alice@EXAMPLE.COM";
The principal maps exactly to its own-named role; grants and role inheritance work identically to password users. An external user cannot authenticate with a password, and a password user is never reachable through the Kerberos path.
Client usage
skaidbsh
kinit alice@EXAMPLE.COM
skaidbsh -H node1.example.com:7000 --tls --tls-ca /etc/skaidb/tls/ca.crt \
--auth-mechanism gssapi \
--gssapi-spn skaidb/node1.example.com@EXAMPLE.COM \
-u alice@EXAMPLE.COM \
-e "SELECT 1"
No password is sent — the ambient ticket cache is used. --user is the client
principal; the authenticated identity comes from the ticket. Environment
equivalents: SKAIDB_AUTH_MECHANISM=gssapi, SKAIDB_GSSAPI_SPN=….
Rust driver
// kinit first; uses the ambient ticket cache.
let client = Client::connect_gssapi_tls(
&["node1.example.com:7000".into()],
"alice@EXAMPLE.COM", // client principal
"skaidb/node1.example.com@EXAMPLE.COM", // target service principal
Some(tls), // recommended
)?;
REST / browser (SPNEGO)
When gssapi_enabled, the REST endpoints accept Authorization: Negotiate
<token> (RFC 4559) and advertise it in the 401 WWW-Authenticate challenge
(alongside Basic). A Kerberos-configured browser gets single-sign-on to the
UI/REST; curl --negotiate -u : https://node1.example.com:7443/query -d …
works too.
SPN note for HTTP clients: browsers and
curl --negotiaterequest the service principalHTTP/<host>@REALM(the HTTP convention). To serve them, add anHTTP/<host>@REALMprincipal to the keytab as well — the acceptor tries every key in the keytab. Native skaidb clients targetskaidb/<host>directly, so they need only that one.
SPNEGO over REST is single-leg (Kerberos establishes in one token); the stateless REST path does not carry a multi-round negotiation.
How it works
- Mechanism negotiation. The client handshake carries a mechanism selector
(
AuthStart); SCRAM is the default and stays wire-compatible, so old clients are unaffected. GSSAPI adds a repeatableAuthTokenframe for the context exchange. - Context establishment. The client and server run a GSS context negotiation (mutual authentication + confidentiality required); the server reads the cryptographically-authenticated principal.
- Identity mapping. The principal is looked up as an external user; its
own-named role is the acting role. The username a client puts in
AuthStartis untrusted — only the GSS principal counts.
Operational caveats
- Clock skew. The most common failure. Symptom:
Clock skew too great. Fix: NTP on the KDC, nodes, and clients. - DNS / SPN mismatch. The client's target SPN must match a key in the
server's keytab and a principal the KDC knows. Reverse-DNS canonicalization
can rewrite the host in the SPN unexpectedly; disable it (
rdns = falseinkrb5.conf) or pingssapi_service_principaland pass the client a matching--gssapi-spn. - Keytab permissions. The keytab is a long-term secret. Keep it
0640 skaidb:skaidb, never world-readable, never in version control. - TLS. GSSAPI authenticates; it does not encrypt the SQL stream. Run
client_tls = requiredfor confidentiality (see the encryption docs). - Platform. glibc only today. The static-musl build has no Kerberos; macOS/Windows client builds are added as their CI builds are validated.
Troubleshooting
| Symptom | Likely cause |
|---|---|
gssapi init failed (did you kinit?) on the client |
No ticket — run kinit, check klist. |
no GSSAPI user for principal "…" |
The principal has no CREATE USER "…" GSSAPI account. |
GSSAPI authentication is not enabled |
auth.gssapi_enabled is false, or a non-kerberos (musl) build. |
Startup: gssapi_enabled = true but this build lacks the kerberos feature |
Running the musl binary — use the glibc .deb/.rpm. |
Clock skew too great |
Time drift — sync NTP. |
Server not found in Kerberos database |
The target SPN isn't a KDC principal / not in the keytab. |
Security scope (current)
The GSS context requires mutual authentication and confidentiality, and runs
inside client TLS. Channel binding (RFC 5929 tls-server-end-point):
every skaidb client (the driver, skaidbsh) binds its GSS context to the
server certificate it verified whenever the connection is TLS; a server
configured with
[auth]
gssapi_channel_binding = "tls-server-end-point"
verifies that binding on the binary port and on MQTT 5 AUTH — a context
initiated for another server's certificate is refused, so a skaidb
client's token cannot be relayed to a different endpoint. Kerberos keeps
its standard semantics here (MIT/Windows "allowed", not "required"): a
client that presents no bindings at all is still accepted, because the
GSS libraries skaidb builds on do not surface the channel-bound flag;
the protection therefore covers skaidb's own clients, which always bind
inside TLS. The default none ignores bindings entirely. Plaintext
connections cannot bind (pair the setting with client_tls = "required");
HTTP SPNEGO stays unbound because browsers and curl --negotiate never
bind. The binding hash follows the certificate's signature algorithm
(SHA-256, or SHA-384/512 for certificates signed with those); after a
certificate rotation the server binds to the new file as soon as its TLS
acceptor reloads it.