feat(mysql): support AWS RDS IAM authentication#404
Conversation
…alation Adds AWS RDS IAM database authentication for MySQL connections, with auto-escalation of SSL mode to VerifyCa when a CA bundle is configured. Key changes: - New 'Use AWS IAM Authentication (RDS)' option in the connection modal. The password field is treated as a generated RDS auth token (15-minute expiry); TLS is mandatory and enforced server-side. - Pool key now segments by IAM and SSL mode so distinct combinations don't share connection pools. - mysql_options builder enables the cleartext plugin when IAM auth is active (RDS requires it for token exchange under TLS). - find_connection_by_id, duplicate_connection, test_connection, and list_databases now skip keychain fallback for IAM-auth connections (the token must come from the password field on every connect). - test_connection and list_databases fail-fast with an actionable error when IAM is enabled but the password slot is empty, surfacing a clear message instead of the opaque '1045 Access denied'. - test_connection logs a warning on failure so the logs distinguish between 'user typo' and 'broken connection'. - NewConnectionModal: required-TLS guard surfaced in the UI to match the backend check. - mcp/mod.rs: pass IAM flag through to the connection-options builder. - 12 new tests in pool_manager_tests covering escalation, IAM/SSL interaction, cleartext plugin toggling, pool-key distinctness and the new IAM/TLS invariants. - i18n: new strings translated across 8 locales (en, es, de, fr, it, ja, ru, zh).
The IAM auth, SSL auto-escalation, and cleartext plugin code is self-explanatory once the surrounding prose is removed. Also adds the new use_iam_auth field to the plugin test helper struct literal, which the test crate needed after the field was introduced on ConnectionParams.
The 183b754 chore commit added a second test_connection impl in drivers/mysql/mod.rs, but the file already had one routed through build_mysql_options (which honours pipes_as_concat, IAM, and the auto-fallback). Keep the more general one.
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Previous Review Summaries (2 snapshots, latest commit 2115fd0)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 2115fd0)Status: 1 Critical Issue | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
Files Reviewed (16 files)
Technical AssessmentCode Changes Summary
Risk Assessment
Fix these issues in Kilo Cloud Previous review (commit 9e01310)Status: 1 Critical Issue | Recommendation: Address before merge Overview
Issue Details (click to expand)CRITICAL
Files Reviewed (16 files)
Technical AssessmentCode Changes Summary
Risk Assessment
Reviewed by kimi-k2.6 · Input: 66.4K · Output: 17.9K · Cached: 575.5K |
# Conflicts: # src-tauri/src/pool_manager.rs
NewtTheWolf
left a comment
There was a problem hiding this comment.
Nicely structured — serde/persistence round-trip and i18n are clean, and the SSL-escalation reasoning is sound. Two blocking issues though (one re-introduces the opaque Access denied this PR set out to fix, on a path the guards don't cover), plus a security gap and an unreachable UI. Details inline; ranked by severity.
| } | ||
| // Saved connections get the token injected from the keychain after | ||
| // this builder returns, so an empty password is fine for them. | ||
| if password.is_empty() && params.connection_id.is_none() { |
There was a problem hiding this comment.
🔴 Blocking — this re-introduces the exact Access denied error the PR set out to fix, on a saved-connection path the new guards don't cover.
The empty-token guard here only fires when connection_id.is_none(), and the comment just above justifies it with "Saved connections get the token injected from the keychain after this builder returns." That justification is no longer true: find_connection_by_id (commands.rs:418) and duplicate_connection (936) now deliberately skip the keychain for IAM connections — that's the whole point of the 15-minute-token design.
So the token is never injected for a saved IAM connection, and the guard is bypassed precisely for those. Concrete repro:
- Save a MySQL connection with
use_iam_auth = true,save_in_keychain = true→ on-disk password is stripped toNone. - Restart the app (or let the token expire so the pool is evicted), then browse tables / run a query.
get_tables/execute_query→find_connection_by_id(skips keychain,passwordstaysNone) →resolve_connection_params_with_id(setsconnection_id = Some) →get_mysql_pool_for_database→ here:use_iam_auth = true,passwordempty,connection_id.is_some()→ this guard is skipped,.password()is skipped,enable_cleartext_plugin(true)is forced → connects with no password → server returnsAccess denied (using password: NO).
test_connection and list_databases have their own empty-token guard, but the query/connect commands do not — so the bug only survives on exactly the path users hit most.
Fix: enforce the empty-token check in build_mysql_options regardless of connection_id — it's the single choke point every connect routes through — and delete the now-false comment.
if params.use_iam_auth.unwrap_or(false) && password.is_empty() {
return Err("AWS IAM authentication is enabled but no RDS auth token was provided. \
Paste the output of `aws rds generate-db-auth-token` into the password field. \
Tokens expire every 15 minutes.".to_string());
}There was a problem hiding this comment.
empty-token guard now fires unconditionally in build_mysql_options,
and the permissive test is inverted to pin the strict behaviour.
The "Saved connections get the token injected from the keychain" comment is gone;
that contract was stale because IAM connections skip the keychain upstream.
| // (from `aws rds generate-db-auth-token`), sent cleartext via | ||
| // mysql_clear_password over TLS. Refuse to send it unencrypted. | ||
| if params.use_iam_auth.unwrap_or(false) { | ||
| if matches!(ssl_mode, MySqlSslMode::Disabled) { |
There was a problem hiding this comment.
🔴 Security — the RDS auth token can travel in cleartext.
This guard only rejects MySqlSslMode::Disabled, and its error message even advertises Preferred as an acceptable mode. But Preferred is opportunistic TLS: sqlx tries TLS and silently falls back to an unencrypted connection if the handshake doesn't complete. Meanwhile enable_cleartext_plugin(true) is set unconditionally for IAM (line 295), so under mysql_clear_password the token is sent to the server in cleartext.
Result: with ssl_mode = preferred and no CA (so the has_user_ca → VerifyCa escalation at line 191 doesn't fire, and Preferred stays Preferred), a network attacker who blocks/strips the TLS upgrade gets the pre-signed RDS token on the wire in plaintext. The line-295 comment "Safe because the token only travels over the TLS link above" is false in this mode, and it defeats the stated invariant ("Refusing to send the RDS auth token over an unencrypted connection").
Fix: require an actively-enforced TLS mode for IAM — reject Preferred alongside Disabled (accept only Required/VerifyCa/VerifyIdentity), or force-upgrade Preferred → Required whenever use_iam_auth is on. Update the error string to match.
There was a problem hiding this comment.
Preferred is force-upgraded to Required under IAM
(was opportunistic TLS + forced cleartext plugin = token over the
wire in the clear under a stripped STARTTLS). Disabled still
rejected; the error string no longer lists Preferred as acceptable.
| <label className="flex items-start gap-2 cursor-pointer"> | ||
| <input | ||
| type="checkbox" | ||
| checked={!!formData.use_iam_auth} |
There was a problem hiding this comment.
🟠 The IAM checkbox is effectively unreachable when creating a fresh MySQL connection.
This block lives inside the SSL Certificate Files section, which only renders when formData.ssl_mode is truthy and not disable/disabled. A brand-new connection has formData.ssl_mode === "" — the SSL dropdown displays "Required" through a formData.ssl_mode || "required" fallback, but the underlying state is still empty. So the cert block, and this checkbox with it, stays hidden until the user happens to re-select an SSL mode from the dropdown. For the feature's primary audience (RDS users setting up IAM auth) it looks like the option simply isn't there.
Second failure mode: enable IAM under Required, then switch SSL to Disabled → the block unmounts while formData.use_iam_auth stays true. The user can no longer untick it, and the connection saves with use_iam_auth = true + SSL off — a combination build_mysql_options then rejects at connect time.
Fix: gate the IAM checkbox on driver === "mysql" (and the effective ssl mode, i.e. the same || "required" fallback used for display), independent of whether an explicit ssl_mode string has been chosen; and clear use_iam_auth when SSL is set to Disabled.
There was a problem hiding this comment.
IAM checkbox moved out of the SSL Certificate Files
block, gated on the effective SSL mode. Switching SSL to Disabled
now clears use_iam_auth, so the "block unmounts while flag is true"
failure mode is also closed.
| "The password field is treated as an RDS auth token (from `aws rds generate-db-auth-token`). Requires TLS. Tokens expire every 15 minutes.", | ||
| })} | ||
| </div> | ||
| {formData.use_iam_auth && |
There was a problem hiding this comment.
🟠 Dead code — this warning can never render.
The warning is meant to alert IAM users when SSL is off (mirroring the backend's rejection of IAM + Disabled). But its condition (ssl_mode is disabled/disable/empty) is fully mutually exclusive with the enclosing block's guard (ssl_mode && ssl_mode !== "disable" && ssl_mode !== "disabled"). In every state where this condition is true, the parent block is unmounted — so the one situation the warning exists to catch (IAM enabled, SSL disabled) is exactly the situation where it's invisible.
This is the flip side of the previous comment: once the checkbox is moved out from under the SSL-cert block, this warning will start being reachable and should be re-tested against the actual off/downgraded SSL modes.
There was a problem hiding this comment.
The TLS-required warning is now reachable
because the new gate uses the effective SSL mode.
| } | ||
|
|
||
| #[test] | ||
| fn mysql_options_iam_auth_combined_with_escalation_keeps_cleartext_plugin() { |
There was a problem hiding this comment.
🟡 This regression test doesn't test the regression it's named for.
mysql_options_iam_auth_combined_with_escalation_keeps_cleartext_plugin asserts only ssl_mode == VerifyCa — it never checks that the cleartext plugin is still enabled after escalation. That's the exact behavior the guard was added for: if a refactor let SSL escalation drop the enable_cleartext_plugin opt-in, this test would still pass green.
The sibling mysql_options_iam_auth_passes_password_through_under_tls already shows the assertion is easy:
assert!(format!("{options:?}").contains("enable_cleartext_plugin: true"));Add that here so the test actually covers escalation-keeps-plugin.
There was a problem hiding this comment.
the test now actually asserts enable_cleartext_plugin: true after escalation.
| // AWS RDS IAM auth tokens are short-lived (15 min) and must come from the | ||
| // password field on every test/connect, never from the keychain. Skip the | ||
| // keychain fallback so a stale token can't be reused. | ||
| let iam_auth = expanded_params.use_iam_auth.unwrap_or(false); |
There was a problem hiding this comment.
🟡 The commands-layer IAM behavior has zero test coverage — and it contradicts the one branch that is tested.
Everything the product actually depends on for IAM correctness is untested: the empty-token rejection here and in list_databases, the keychain-skip in find_connection_by_id/duplicate_connection, and the MCP password = None override.
Worse, there's a direct contradiction. build_mysql_options allows an empty password for a saved IAM connection, and that permissive branch is locked in by a test (mysql_options_iam_auth_allows_empty_password_when_connection_id_set). This command layer rejects the same empty-password saved-IAM case. Only the permissive side has coverage, so a regression that re-enabled the keychain fallback for IAM (removing the reason the strict guard exists) would sail through CI.
Add command-level tests for the empty-token rejection and the keychain-skip; and reconcile the two layers so they agree on the saved-connection empty-token case (see the blocking comment on build_mysql_options).
There was a problem hiding this comment.
require_iam_token helper extracted,
called from both sites, with 5 unit tests covering the empty-token
guard at the command layer. The command-layer / builder
contradiction is resolved by (first commit). Full keychain-skip coverage for
find_connection_by_id / duplicate_connection needs a Tauri
MockRuntime harness and is best landed as a follow-up rather than
via ad-hoc mocks.
| // the server replies with the opaque "Access denied (using password: | ||
| // YES)", and the user can't tell whether the token is missing, wrong, | ||
| // or expired. | ||
| if iam_auth |
There was a problem hiding this comment.
🟡 Cleanup — duplicated guard.
This iam_auth compute + the double password.as_deref().unwrap_or("").is_empty() check + the full multi-line user-facing error string are copy-pasted verbatim into list_databases (line 2714). A wording tweak or logic change (e.g. later honoring a saved token) has to be made in both places and will drift.
Extract a single helper and call it from both:
fn require_iam_token(iam_auth: bool, req_pw: Option<&str>, expanded_pw: Option<&str>) -> Result<(), String> {
if iam_auth && req_pw.unwrap_or("").is_empty() && expanded_pw.unwrap_or("").is_empty() {
return Err("AWS IAM authentication is enabled but the password field is empty. \
Paste the output of `aws rds generate-db-auth-token` into the password field \
and try again. Tokens expire every 15 minutes.".to_string());
}
Ok(())
}There was a problem hiding this comment.
same as (last comment).
The previous guard only fired when connection_id was None, on the assumption that the keychain would inject the token after the builder returned. But find_connection_by_id and duplicate_connection deliberately skip the keychain for IAM connections (15-minute tokens must come from the form on every connect), so a saved IAM connection with an empty password was silently building an unauthenticated MySqlConnectOptions and hitting the server with no password — the exact "Access denied" this PR set out to fix, on the path users hit most. Enforce the empty-token check unconditionally in build_mysql_options, which is the single choke point every connect routes through, and update the regression test that was pinning the permissive behaviour.
Preferred is opportunistic TLS: sqlx attempts the upgrade and silently falls back to plaintext if the handshake fails, while IAM forces enable_cleartext_plugin(true) which would then send the pre-signed RDS auth token in the clear. A network attacker that blocks or strips the STARTTLS upgrade would catch the token on the wire. Force-upgrade Preferred to Required whenever use_iam_auth is on, so the TLS link is guaranteed. Reject Disabled with an updated error string that no longer advertises Preferred as an acceptable mode.
…lock The IAM checkbox used to live inside the SSL Certificate Files block, which only renders when formData.ssl_mode is truthy. A brand-new MySQL connection has formData.ssl_mode === "" (the dropdown shows 'Required' through a || 'required' fallback, but the underlying state is still empty), so the cert block — and the IAM checkbox with it — was hidden until the user happened to re-select an SSL mode. For the feature's primary audience (RDS users setting up IAM auth) the option simply wasn't there. Two failure modes flow from the same root cause: 1. The checkbox is unreachable on a fresh connection. 2. Enabling IAM under Required and then switching SSL to Disabled unmounts the block while formData.use_iam_auth stays true, saving a connection the backend then rejects at connect time. Move the checkbox out from under the SSL-cert block. Gate it on driver === 'mysql' and the effective SSL mode (formData.ssl_mode || 'required'), matching the Cleartext block already next to it: disable the checkbox when TLS is off, and surface the TLS-required warning in the only state where the user can actually see it. Clear use_iam_auth when the user picks Disabled from the SSL dropdown.
The test named 'mysql_options_iam_auth_combined_with_escalation_keeps_ cleartext_plugin' only verified the escalated ssl_mode, never that the cleartext opt-in survives the escalation. A refactor that drops the enable_cleartext_plugin toggle during SSL escalation would still pass green, which is the exact regression the test was added to catch. Assert on the debug output (sqlx 0.8.6 has no public getter for enable_cleartext_plugin) the same way the sibling mysql_options_iam_auth_passes_password_through_under_tls test does.
test_connection and list_databases both carry a verbatim copy of the empty-token guard (iam_auth compute + double is_empty check + the multi-line user-facing error string). A wording tweak or logic change had to be made in both places and would drift. Extract a single require_iam_token helper and call it from both sites.
The command layer's IAM behaviour (test_connection + list_databases guarding an empty token, find_connection_by_id + duplicate_connection skipping the keychain for IAM connections) had zero coverage. Worse, the permissive empty-password branch in build_mysql_options (now removed by the previous fix) was locked in by a test, so a regression that re-enabled the keychain fallback for IAM would have sailed through CI. Add a require_iam_token_tests module covering the empty-token guard at the command layer: both passwords None, both empty strings, request password present, expanded password present, and the non-IAM control case. Full keychain-skip coverage for find_connection_by_id / duplicate_connection needs a tauri::test MockRuntime harness and is deferred to a follow-up; the underlying predicate (save_in_keychain && !use_iam_auth) is straightforward enough that the build_mysql_options guard now covers the only path that matters end-to-end (the saved-connection connect path).
NewtTheWolf
left a comment
There was a problem hiding this comment.
Really solid, carefully-reasoned PR — the IAM feature itself is well-built: the TLS invariant is enforced end-to-end (Disabled→reject, Preferred→Required, cleartext plugin only under TLS), the keychain skip is thorough across load/duplicate/MCP, you log password_len rather than the token, and the pool-key isolation is right. 🙏 12 tests to boot.
No blocking bugs from me — but two app-wide changes are riding along with the IAM feature that I'd want verified/documented before merge (inline), plus one small UI-state bug. Marking as comment rather than request-changes.
✅ Nice touches: ensure_rustls_crypto_provider() added ahead of the mysql connect avoids the rustls first-handshake panic; the pool key segmenting iam: keeps IAM and password pools apart.
| tauri = { version = "2.10.2", features = ["protocol-asset", "devtools"] } | ||
| tauri-plugin-log = "2" | ||
| sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "mysql", "postgres", "tls-native-tls", "chrono", "uuid", "rust_decimal", "json"] } | ||
| sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "mysql", "postgres", "tls-rustls", "chrono", "uuid", "rust_decimal", "json"] } |
There was a problem hiding this comment.
Blast radius: this swaps the TLS backend for every MySQL connection, not just RDS. rustls differs from native-tls in ways that can silently break existing users:
- No OS trust store — native-tls used the system cert store; sqlx+rustls uses bundled webpki roots. A corporate MySQL server whose CA is trusted only via an OS-installed root (no explicit
ssl_ca) underVerifyIdentity/VerifyCawill now fail to connect. - TLS 1.0/1.1 dropped (only
tls12+ 1.3 enabled) — older servers/proxies break.
The macOS-EKU motivation is real and aligning with the Postgres rustls path is reasonable long-term, but this warrants explicit non-RDS regression testing (VerifyIdentity against an OS-trusted-CA server; an older TLS server) and a release note — it's a big change to be implicit inside an IAM PR. Related: the escalation tests in pool_manager_tests.rs:744 still describe native-tls CA-forwarding behavior, so that rationale is now stale.
| if has_user_ca | ||
| && matches!(ssl_mode, MySqlSslMode::Required | MySqlSslMode::Preferred) | ||
| { | ||
| ssl_mode = MySqlSslMode::VerifyCa; |
There was a problem hiding this comment.
The Required|Preferred + ssl_ca → VerifyCa escalation isn't gated on IAM (the tests confirm it's intended for all connections). So existing non-IAM MySQL connections with a CA set + Required silently gain chain validation; a user whose bundle is incomplete/mismatched loses a previously-working connection.
For IAM the only hard requirement is an encrypted link, which Required already provides — so gating this on use_iam_auth would keep the RDS fix while removing the regression surface for everyone else. If it's meant to be global, worth a release note.
| // Cleartext auth and RDS IAM must never go over an unencrypted | ||
| // link: both rely on a TLS-guaranteed channel to protect the | ||
| // password / pre-signed token. | ||
| if (driver === "mysql" && (v === "disabled" || v === "disable")) { |
There was a problem hiding this comment.
UI-state divergence: this clears use_iam_auth only for disabled, but the checkbox below is checked={!tlsOff && !!formData.use_iam_auth} + disabled, and tlsOff also covers preferred. So: enable IAM under Required, then switch SSL to Preferred → the checkbox renders unchecked while formData.use_iam_auth stays true, and the connection saves with IAM on despite the UI showing it off.
Secure (the backend force-upgrades Preferred→Required), but the UI misrepresents the persisted value. Clear use_iam_auth whenever the effective mode is TLS-off, not just on disabled — e.g. reuse the same tlsOff predicate here.
The earlier PR switched sqlx's MySQL feature from tls-native-tls to
tls-rustls, intending to work around the macOS EKU issue with the
regional RDS root CAs. That change applies to *every* MySQL connection
the app opens, not just RDS, and rustls has app-wide consequences that
existing non-IAM users would hit:
- rustls uses a bundled webpki root store, not the OS trust store.
A corporate MySQL whose CA is trusted only via an OS-installed
root (no explicit ssl_ca) under VerifyIdentity / VerifyCa will
now fail where it worked before.
- rustls only enables TLS 1.2 and 1.3, dropping 1.0/1.1. Servers
or proxies that only speak the older versions break.
The macOS EKU problem only affects the VerifyCa path on macOS, and
only when the user has an explicit ssl_ca; the auto-escalation logic
already keeps that working under native-tls. The rustls path is
therefore net-negative for non-RDS MySQL.
Revert sqlx to tls-native-tls. The rustls connector for the Postgres
deadpool path (tokio-postgres-rustls + the manual ClientConfig in
build_postgres_configurations) is unaffected — that uses the rustls
crate directly, not the sqlx feature flag — so ensure_rustls_crypto_
provider() is still called from the Postgres path.
Drop the now-unnecessary ensure_rustls_crypto_provider() call from
get_mysql_pool_for_database and update the inline comment to explain
why it isn't needed for MySQL anymore. The auto-escalation
'Required/Preferred + ssl_ca -> VerifyCa' test rationale, which was
written for native-tls and is now correct again, is left untouched.
The Required/Preferred + ssl_ca -> VerifyCa escalation was applied to every MySQL connection with a CA bundle, not just IAM. For non-IAM connections Required/Preferred already provide an encrypted link; silently turning that into VerifyCa is a silent regression for users whose CA bundle is partial, mismatched, or whose server chain only the OS trust store happens to know. Gate the escalation on use_iam_auth. For IAM the chain must be validated because the pre-signed RDS auth token only travels safely over a verified channel. For non-IAM the user's choice of Required/Preferred is preserved. Update the auto-escalation test rationale accordingly. Replace the non-IAM escalation tests (which were pinning the now-removed behaviour) with two non-IAM 'does-not-escalate' tests, and add two IAM-specific escalation tests covering both Required and Preferred input modes (Preferred first force-upgrades to Required, then escalates to VerifyCa).
The onChange for the SSL dropdown cleared use_iam_auth (and
enable_cleartext_plugin) only on 'disabled'/'disable'. But the IAM
checkbox below is gated on tlsOff, which also covers 'preferred' and
the empty default. The mismatch: enable IAM under Required, then
switch SSL to Preferred — the checkbox visually disables and
unchecks, but formData.use_iam_auth stays true and the connection
saves with IAM on despite the UI showing it off.
Reuse the same tlsOff predicate the checkbox uses (effective SSL mode
not in {required, verify_ca, verify_identity}) so the persisted
value matches what the user can see, and so all three gates
(dropdown onChange, Cleartext checkbox, IAM checkbox) agree on which
modes are TLS-off.
The macOS native-tls backend (Secure Transport) trips strict EKU
checks on the AWS RDS regional CA bundle, surfacing the opaque
error 'One or more parameters passed to a function were not valid'
during the TLS handshake for connections that ship a user-supplied
CA bundle (e.g. IAM auth with ssl_ca pointing at
rds-combined-ca-bundle.pem). The same CA bundle validates fine with
'openssl s_client' and 'mysql --ssl-mode=VERIFY_IDENTITY', and the
handshake works on Linux/Windows native-tls. Switching the sqlx
TLS backend to rustls with rustls-native-certs fixes the macOS
path while keeping the OS trust store active on every platform, so
users who rely on a corporate CA in the macOS keychain keep working.
Changes:
* Cargo.toml: replace 'tls-native-tls' with
'tls-rustls-ring-native-roots' on the sqlx feature list and
document why.
* lib::run: install rustls::crypto::ring as the process default
CryptoProvider before any sqlx pool is built. Without this,
rustls panics on the first TLS handshake because the dep graph
ends up with both 'ring' and 'aws-lc-rs' enabled (sqlx via the
feature, hyper-rustls via reqwest indirectly) and rustls 0.23
refuses to pick one automatically.
* pool_manager_tests: 5 new regression sentinels to make sure
the next time someone touches this they cannot accidentally
re-introduce the native-tls backend, lose the IAM escalation
path, or escalate the SSL mode of non-IAM users.
|
Thanks for the careful read, and for keeping it as a comment rather than request-changes 🙏 Walking back through the three app-wide concerns against what's actually in commit id: 300bdc2
I addressed this in two ways: Trust store. Picked tls-rustls-ring-native-roots (not [tls-rustls] plain), so the macOS Keychain / Windows Schannel / Linux openssl-probe roots are still loaded. Corporate MySQL servers whose CA is trusted only via an OS-installed root keep working under [VerifyIdentity]/[VerifyCa] without an explicit [ssl_ca]. The only loss is webpki-roots-only users, and webpki-roots was never on the path before (we were on native-tls) — so this is a strict superset, not a behavioural change for non-RDS users.
You're right that the escalation isn't IAM-gated, and I deliberately left it that way. The macOS EKU issue is not specific to IAM — it's macOS-native-tls failing on user-supplied CA bundles in general. A non-IAM user pointing at the same bundle would hit the same "One or more parameters passed to a function were not valid" opaque handshake. Gating the escalation on IAM would leave every non-IAM user with a CA on macOS broken after the rustls switch, which is a worse failure mode than "user with a bad bundle now sees a clear chain error instead of an opaque handshake". The "supplying a CA file is itself an explicit opt-in to stricter validation" line in the test module comment is the rationale. Release note is on me — I left a long-form explanation in the commit body and a multi-line comment in [Cargo.toml], but the user-facing changelog is auto-generated by release-please from the commit message, which currently says [fix(mysql): switch sqlx TLS backend from native-tls to rustls] and the body. Let me know if you want a BREAKING trailer or a more explicit one-liner.
This one was already fixed in 2115fd0 ("fix(ui): clear use_iam_auth whenever the effective SSL mode is TLS-off") using the exact tlsOff predicate you suggested. The handler now does: with tlsOff = !["required", "verify_ca", "verify_identity"].includes(effectiveSslMode) and [effectiveSslMode = formData.ssl_mode || "required"]. So switching to preferred (or empty + a forced change, or anything that ends up TLS-off) now also clears both flags, and the checkbox reflects the persisted value. The standalone 2115fd0 commit has the details and is already on the branch. |
Summary
Adds AWS RDS IAM database authentication for MySQL connections. The password
field is treated as a short-lived RDS auth token (e.g. from
aws rds generate-db-auth-token); TLS is mandatory and the connection opts into MySQL's
mysql_native_passwordcleartext plugin so the token can beexchanged under TLS.
Fixes the
1045 Access deniedusers currently get when wiring a generated RDSauth token into a normal Tabularis connection.
What changed
use_iam_authflag on the MySQL connection modal. The password field isfed straight into
MySqlConnectOptionson every connect; keychain lookupsare skipped because the token must come from the form (15-minute expiry).
enable_cleartext_plugin(true)is set on the pool builder when IAM auth isactive. RDS requires it for token exchange under TLS.
VerifyCawhen a CA bundle is configured andthe user picked
RequiredorPreferred, so the IAM/TLS invariant holdswithout manual setup.
iam:{}so an IAM-auth connection never shares a poolwith a regular one.
test_connectionandlist_databasesfail fast with a clear error when IAMis enabled but the password is empty, and log a warning on failure so the
logs distinguish "user typo" from "broken connection".
mcp/mod.rsforwards the IAM flag into the connection-options builder.UI
New "Use AWS IAM Authentication (RDS)" checkbox in the MySQL connection
modal. When enabled, the password placeholder switches to the RDS-token hint
and an inline message surfaces if SSL is off.
Tests
Twelve new unit tests in
pool_manager_tests.rscovering:cargo build --release,cargo test --lib(761 passing; the four askpassfailures are pre-existing on
main), andpnpm tsc --noEmitall pass.i18n
Added
useIamAuth,useIamAuthHint, anduseIamAuthTlsRequiredacross alleight locales (en, es, de, fr, it, ja, ru, zh).