fix(plugin-mssql): delete the Kerberos ticket cache on every failed connect, pass long SPNs through freetds.conf, and stop tunneled connects waiting on each other - #3127
Merged
Conversation
# Conflicts: # CHANGELOG.md # Plugins/MSSQLDriverPlugin/FreeTDSConnection.swift # TableProMobile/TableProMobile/CBridges/CFreeTDS/CFreeTDS.h
# Conflicts: # CHANGELOG.md
…onnect, pass long SPNs through freetds.conf, and stop tunneled connects waiting on each other
# Conflicts: # CHANGELOG.md
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #3109. That PR was merged before these review findings were fixed. This fixes three defects #3109 introduced, plus one released Kerberos bug the same change made fixable.
What was wrong
1. A failed connect left a Kerberos ticket cache on disk. With Windows Authentication and a stored principal and password,
MSSQLKerberosCredentials.acquireTicketwrites a ticket-granting ticket to$TMPDIR/tablepro-krb5-<UUID>. The only code that deleted it was thedeferinwithKerberosEnvironmentIfNeeded, and #3109 moved that call insidefreetdsConfigFile.withEntry. Three new throws came before it: the entry refusing the host,dbsetlnamerefusing a field over 128 bytes, and a failed config write. Each failed connect left one more file holding a usable TGT, and so did each health-monitor reconnect.2. An explicit Kerberos SPN over 128 bytes failed the connect.
dbsetlnamecaps every LOGINREC string at 128 bytes (TDS_MAX_LOGIN_STR_SZ). That limit exists for LOGIN7 fields, but the SPN is never sent in LOGIN7. It only feedsgss_import_name. #3109 turned the refusal into a hard error. On 0.75 the refusal was silently dropped instead, so a cross-realm server with a long name failed withKDC_ERR_S_PRINCIPAL_UNKNOWN.3. Every tunneled SQL Server connect waited on every other one. The freetds.conf lease was keyed by host name, and every SSH, Cloudflare and Cloud SQL tunnel hands the driver
127.0.0.1. A tunnel to a server that never answers held the name for its whole dbopen, 30 seconds. The app-owned deadline was armed before the wait, so a healthy connect queued behind two dead ones timed out without ever being tried. An identical connect could also join a held lease while a different one waited, so the waiter could starve.What changed
FreeTDSConnection.openConnectionowns the ticket cache once it has it. Its first statement is adeferthat deletes the file, so it goes however the attempt ends.MSSQLPluginDriver.connectdeletes it only while it still owns it, before the hand-off: an explicitconnectionOwnsKerberosCacheflag, set right beforeconn.connect(). It never deletes after the hand-off, because itscatchcan run on cancel or deadline while dbopen still reads the cache (MSSQL: cancelling a connect can't interrupt a blocked dbopen (Kerberos widens the window) #1889).spnoption of the connection's freetds.conf entry. That is FreeTDS's documented setting, and it has no 128 byte cap. Every entry statesspn, empty or not, so a host namedglobalcannot lend its own. An SPN that does not fit on a freetds.conf line (over 248 bytes) is refused with its own message.dbsetlname(DBSETSERVERPRINCIPAL)is gone.address,port, the way SQL Server writes a server and port. The section name is what db-lib sends as the LOGIN7 server name, and FreeTDS sends no TLS SNI, so a host name has to stay bare for Azure SQL to route the login. An address names no server a gateway routes by, so the port can join the name there. Connects to different ports on one address never share a lease, and every tunnel is such a connect.Another connection to <name> with other settings is still logging in.rather than a server timeout. Cancel wakes a waiter (interruptWaits), which leaves the line at once and skips dbopen. This uses a newSingleResumeGate.isSettled.[::1], is read without them, as libtds itself does. 0.75 accepted it and fix(plugin-mssql): encrypt SQL Server connections set to Required (skip verify) #3109 refused it.Why not the other shapes
dbopen("host:port")with a port-less[host]section was measured and rejected. When no section matches the full name, libtds falls back through$FREETDSCONF,~/.freetds.confand the compiled-in/tmp/freetds-macos-arm64/etc/freetds.conf(fromstrings Libs/libsybdb_arm64.a), and applies their[global]sections. That last path can be created by any local user.host,portfor host names too would remove every remaining wait, but it changes the LOGIN7 server name Azure SQL routes by. That could not be tested here, so host names keep the bare name. Connects to one host name with different ports or modes still take turns, now with a bounded wait and a clear reason.Verification
swift test): TableProMSSQLCoreTests 105/105, TableProCoreTypesTests 8/8. The tunnel-ports and first-come tests fail againstorigin/mainsources. New tests cover a bounded wait giving up and leaving the line, and an abandoned wait leaving oninterruptWaits.verify.sh buildPASS,verify.sh pluginsPASS,verify.sh test MSSQLFreeTDSConfigTests MSSQLLoginParametersTests MSSQLSSLMappingTests StringCatalogIntegrityTests63/63 PASS.verify.sh linton every changed Swift file: the only violations are three on untouched lines ofMSSQLPlugin.swift, which is outside CI lint.check-ios-shared-isolation.pyok.scripts/check-mssql-encryption.shgains checks for tunnels on one address, a bounded wait on one host name, the ticket cache after four kinds of failure, and a 130-byte SPN. Runs against Azure SQL Edge 15:localhost(Verify CA and Verify Identity included);origin/maindriver sources: 6 FAIL. A healthy connect beside two silent tunnels on 127.0.0.1 timed out after 37.3s. A connect behind two others tolocalhostreported a server timeout after 35.8s. The ticket cache survived a 129-byte database, an unreadable host and an unwritable SPN. A 130-byte SPN was refused before Kerberos.scripts/check-mssql-batch-results.sh: 50 PASS.libsybdb_arm64.a: a[127.0.0.1,14339]section connects withencrypt_optionTRUE and finalserver_name = 127.0.0.1,14339, and SQL Server accepts that login.[::1,14399]dials::1.spn = <130-byte SPN>reachesgss_import_nameintact (TDSDUMPusing kerberos name MSSQLSvc/...@CORP.CONTOSO-INTERNATIONAL.COM).spn =left empty gives the defaultMSSQLSvc/127.0.0.1:14339.main(fix(editor): tier every statement SQL Server runs without a semicolon, so Safe Mode sees each one #3110 to fix(plugins): import a SQL Server file with no GO line a statement at a time and bound the import failure alert #3125):verify.sh buildPASS,verify.sh pluginsPASS,swift test --filter TableProMSSQLCoreTests105/105. The app-target MSSQL suites above ran before that merge; they could not run again locally because the XCTest daemon was stuck behind a pending system prompt, so PR CI covers them.Skill(code-review)(Codex is unavailable). Fixed from it: the unbounded lease wait, a cancelled waiter keeping its place, the sleep-ordered test, and an ordering rule held only by a comment.Not covered
gss_import_nameand the cache is removed.freetdsClearError(for: nil)), so two failing connects at once can swap their messages. 0.75 had this for every connect. It is left for its own change.Found while fixing #3078 (#3105).