Skip to content

feat(mssql): auto-derive Kerberos realm for cross-realm Windows Authentication#1949

Open
Seha16 wants to merge 1 commit into
TableProApp:mainfrom
Seha16:feat/mssql-kerberos-realm
Open

feat(mssql): auto-derive Kerberos realm for cross-realm Windows Authentication#1949
Seha16 wants to merge 1 commit into
TableProApp:mainfrom
Seha16:feat/mssql-kerberos-realm

Conversation

@Seha16

@Seha16 Seha16 commented Jul 22, 2026

Copy link
Copy Markdown

Fixes #1947.

Problem

SQL Server Windows Authentication (Kerberos) fails for a server whose Kerberos realm differs from the Mac's default_realm, even with a valid TGT and a working cross-realm trust. FreeTDS builds an unrealmed SPN (MSSQLSvc/host:port), and macOS Heimdal resolves that string principal against default_realm with no cross-realm referral — so the KDC returns KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN and the login is rejected.

The JDBC driver (DataGrip) works in the same environment because Java derives the service realm from the hostname via the system [domain_realm] mapping — not from the URL, and without changing default_realm.

Change

Make TablePro derive the realm the same way:

  • MSSQLKerberosRealmResolver (macOS) resolves host → realm via GSS name canonicalization, which honors the system [domain_realm] / default_realm configuration (the already-linked GSS.framework; no new dependency).
  • The resolved realm produces an explicit realm-qualified SPN (MSSQLSvc/host:port@REALM) handed to FreeTDS via DBSETSERVERPRINCIPAL. FreeTDS uses server_spn verbatim for the GSS target.
  • No new connection field and no default_realm change required — it just works, matching the JDBC driver. When resolution yields nothing, it falls back to today's unrealmed behavior (no regression).
  • iOS is unaffected (no GSS/Windows-auth path there); the SPN call is #if os(macOS)-guarded.

Also included (surfaced while testing this form):

  • Pin the Authentication method selector to the top of the connection form. Previously it jumped position when a method showed/hid the built-in username/password (e.g. SQL Server's SQL vs Windows Authentication). The fix pins only selectors whose dependent fields hide credentials, so SQL Server / Snowflake / BigQuery / DynamoDB / etc. become consistent while MySQL/PostgreSQL are unchanged.

Testing

  • Unit tests for SPN construction (MSSQLKerberosSPNTests).
  • Full app builds (Debug).
  • Verified end-to-end against a real cross-realm setup (an AG listener and a standalone instance whose SPNs live in a different realm than the client's default_realm): connections that previously failed with S_PRINCIPAL_UNKNOWN now authenticate. The FreeTDS protocol dump shows the realm-qualified SPN and GSS completing:
    gssapi.c: using kerberos name MSSQLSvc/host:1433@RESOURCE.REALM
    gss_import_name: GSS_S_COMPLETE
    login.c: quietly sending TDS 7+ login packet
    

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3dccf687a5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

let display = String(data: Data(bytes: value, count: displayBuffer.length), encoding: .utf8) ?? ""
guard let atIndex = display.lastIndex(of: "@") else { return nil }
let realm = String(display[display.index(after: atIndex)...])
return realm.isEmpty ? nil : realm

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the canonical host in Kerberos SPNs

When the user enters a short hostname or CNAME, GSS canonicalization can produce the FQDN that has the SQL Server SPN registered, but this resolver discards that canonical host and returns only the realm; the later MSSQLKerberosSPN.build(host: config.host, ...) rebuilds DBSETSERVERPRINCIPAL from the raw form field. Because that SPN is then passed verbatim to FreeTDS, connections that previously relied on FreeTDS/GSS host canonicalization can start requesting MSSQLSvc/alias:port@REALM and fail with S_PRINCIPAL_UNKNOWN. Return/build the canonical service name (or canonical host plus realm) instead of only the realm.

Useful? React with 👍 / 👎.

@Seha16
Seha16 force-pushed the feat/mssql-kerberos-realm branch from 3dccf68 to 01b0027 Compare July 22, 2026 20:11
@Seha16

Seha16 commented Jul 22, 2026

Copy link
Copy Markdown
Author

Addressed in 01b0027 — the resolver now returns the canonical host + realm, not just the realm:

  • Host canonicalization mirrors FreeTDS's own logic: a dot-less (short) hostname is canonicalized to its FQDN via getaddrinfo(AI_CANONNAME); a name that already contains a dot is used as-is. This restores the FQDN canonicalization FreeTDS performs for short names, which it skips once an explicit SPN is set.
  • The realm is resolved via GSS name canonicalization ([domain_realm] / default_realm).
  • The SPN is built from the canonical host and applied via DBSETSERVERPRINCIPAL, so short-name/CNAME and cross-realm hosts both resolve to the FQDN the service is registered under — matching the JDBC driver.

No behavior change for FQDN inputs; the short-name/alias path no longer regresses to MSSQLSvc/alias:port@REALM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQL Server Windows Authentication (Kerberos) fails when the server's realm differs from the Mac's default_realm

1 participant