feat(mysql/user): support non-default authentication plugins for IAM auth#363
feat(mysql/user): support non-default authentication plugins for IAM auth#363ronlevy1211 wants to merge 5 commits into
Conversation
…auth
Add an optional AuthenticationPlugin field on the MySQL User resource. When
set, the user is created with CREATE USER ... IDENTIFIED WITH <plugin>
[AS '<authString>'] instead of IDENTIFIED BY <password>. No password is
generated, the connection secret omits the password key, and ALTER USER
... IDENTIFIED BY is skipped on Update so the provider does not downgrade
plugin-auth users to native password auth.
Primary motivation is AWS RDS IAM authentication, where the DB user has
no static password and clients fetch a short-lived auth token from AWS
at connect time:
spec:
forProvider:
authenticationPlugin:
name: AWSAuthenticationPlugin
authString: RDS
The same field also works for other auth plugins (auth_socket,
caching_sha2_password, etc.).
Backward compatible: when AuthenticationPlugin is unset the existing
password-based flow is preserved.
Applied to both the cluster and namespaced API variants. Plugin name is
quoted as a SQL identifier; auth string is quoted as a SQL value.
Signed-off-by: Ron Levy <rlevy@fireblocks.com>
E2E test resultsValidated end-to-end against an AWS Aurora MySQL (Aurora 3 / MySQL 8.0 compatible) on a non-prod cluster. Built the provider from this branch as a custom OCI image and deployed it on Crossplane pointing at the cluster. SetupapiVersion: mysql.sql.crossplane.io/v1alpha1
kind: User
metadata:
name: iam-auth-test
annotations:
crossplane.io/external-name: iam-auth-test
spec:
forProvider:
authenticationPlugin:
name: AWSAuthenticationPlugin
authString: RDS
providerConfigRef:
name: mysql-shell-services
writeConnectionSecretToRef:
name: iam-auth-test-conn
namespace: defaultPlus a Results
Direct DB query proving the plugin was applied: |
|
Hi @fernandezcuesta @chlunde @JevgeniF — friendly ping for a first pass when you have a moment. The PR adds |
| // for this user. If no reference is given, a password will be auto-generated. | ||
| // for this user. If no reference is given and AuthenticationPlugin is | ||
| // unset, a password will be auto-generated. | ||
| // Ignored when AuthenticationPlugin is set. |
There was a problem hiding this comment.
Can those be explicitly set mutually exclusive?
I mean either kubebuilder annotation or lateinitializer ignoredFields.
If you're able to restrict that from the API/CRD, the logic can be simplified a bit
There was a problem hiding this comment.
Done — pushed af1d37f. Added a +kubebuilder:validation:XValidation rule on UserParameters that rejects setting both passwordSecretRef and authenticationPlugin (matches the pattern used by MSSQL User). The runtime branches in Observe/Create/UpdatePassword stay because they encode the semantic that plugin-auth users have no password, not a defensive guard. Doc comments on both fields tightened to drop the "ignored when..." wording.
…ually exclusive Add a CRD-level XValidation rule rejecting any User where both passwordSecretRef and authenticationPlugin are set, addressing review feedback. The runtime branches in Observe/Create/UpdatePassword stay because they encode the semantic that plugin-auth users have no password (they are not defensive guards against both fields being set). Tighten doc comments on both fields to reflect that the two cannot coexist. Signed-off-by: Ron Levy <rlevy@fireblocks.com>
3763ae5 to
af1d37f
Compare
|
Hey @fernandezcuesta — no rush, just a gentle nudge in case this slipped past. Pushed af1d37f last Monday with the Also pinging @chlunde @JevgeniF for any second opinion if Fernando is heads-down elsewhere. |
| "max_updates, " + | ||
| "max_connections, " + | ||
| "max_user_connections " + | ||
| "FROM mysql.user WHERE User = ? AND Host = ?" |
There was a problem hiding this comment.
Could you get plugin here for drift detection and add support for changing method in Update? crossplane managed resources should be able to update any field as long as the upstream system supports it, so we don't silently ignore some types of changes
There was a problem hiding this comment.
Done - pushed 12cd322. Three pieces:
-
Observe now SELECTs
plugin, authentication_stringalongside the resource-options columns. Hydratesobserved.AuthenticationPluginonly when the observed plugin is non-default-password (caching_sha2_password/mysql_native_password/sha256_passwordare treated as "user has a password" and left as nil - they're the implicit default and would otherwise force drift on every reconcile of a password user). -
upToDatecomparesAuthenticationPlugin(name + authString, nil-aware) ahead of the resource-options checks so plugin drift is caught even whenResourceOptionsis nil. -
Update detects plugin drift via
Status.AtProvider.AuthenticationPlugin(cached by Observe) and handles three transitions:- password → plugin: emits
ALTER USER ... IDENTIFIED WITH <plugin> [AS '<authString>'](newexecuteAlterUserWithPluginQueryhelper, mirrors the Create-time quoting - identifier-quote plugin name, value-quote authString). - plugin → password: falls through to the existing
UpdatePasswordpath.ALTER USER ... IDENTIFIED BY '<pw>'both sets the password and restores the default password plugin in one statement. - same plugin, different authString: same
ALTER USER ... IDENTIFIED WITHquery with the new auth string.
- password → plugin: emits
The API-level XValidation rule already enforces mutual exclusivity of passwordSecretRef and authenticationPlugin, so the spec always represents one mode and the transition is unambiguous.
Tests cover the three transitions above plus an updated AuthenticationPluginSkipsAlterPassword that now sets observed == desired and asserts neither IDENTIFIED BY nor IDENTIFIED WITH runs.
chlunde
left a comment
There was a problem hiding this comment.
Could you add drift detection, or did you already discuss this?
Address @chlunde's review on crossplane-contrib#363: Observe didn't read the user's actual `plugin` from mysql.user, and Update silently ignored authenticationPlugin changes. Crossplane managed resources should reconcile every field the upstream system supports. Changes: Observe — extend the SELECT to also fetch `plugin` and `authentication_string`. Hydrate observed.AuthenticationPlugin only when the observed plugin is a non-default-password plugin (caching_sha2_password / mysql_native_password / sha256_password represent "use a password" rather than an opt-in plugin choice and are left as nil in the observed state). upToDate — compare AuthenticationPlugin (name + authString). nil vs non-nil is a drift; same nil-ness with matching name+authString is up-to-date. Update — when authenticationPlugin drift is detected and the desired spec sets a plugin, emit ALTER USER <user>@<host> IDENTIFIED WITH <plugin> [AS '<authString>'] via a new executeAlterUserWithPluginQuery helper that mirrors the Create-time query's quoting rules (identifier-quote the plugin name, value-quote the authString). When the desired spec drops the plugin (plugin → password transition), the existing UpdatePassword path is reused — ALTER USER ... IDENTIFIED BY '<pw>' both sets the password and restores the default password plugin in one statement. Status — UserObservation gains an AuthenticationPlugin field, set by Observe and consumed by Update for drift detection across reconciliation cycles. Tests cover three new transitions: - password → plugin (observed nil, desired set) - plugin → different authString (same name, different AS clause) - existing AuthenticationPluginSkipsAlterPassword updated to set observed == desired and now asserts no IDENTIFIED WITH OR BY runs Signed-off-by: ronlevy <rlevy@fireblocks.com>
Description of your changes
Adds an optional
AuthenticationPluginfield on the MySQLUserresource. When set, the user is created withCREATE USER ... IDENTIFIED WITH <plugin> [AS '<authString>']instead ofIDENTIFIED BY <password>. No password is generated, the connection secret omits the password key, andALTER USER ... IDENTIFIED BYis skipped on Update so the provider does not downgrade plugin-auth users to native password auth.Primary motivation is AWS RDS IAM authentication, where the DB user has no static password and clients fetch a short-lived auth token from AWS at connect time:
The same field also works for other auth plugins (
auth_socket,caching_sha2_password, etc.).Backward compatibility
When
AuthenticationPluginis unset, the existing password-based flow is preserved. Existing users / manifests are unaffected.Notable changes
AuthenticationPlugin{Name, AuthString}struct onUserParameters(cluster + namespaced variants)Create: branches toIDENTIFIED WITHwhen the plugin is set; password generation skippedObserve: skips password drift check when the plugin is set (no password to drift)UpdatePassword: no-op when the plugin is set (avoids downgrading the user back to password auth)examples/cluster/mysql/user_iam_auth.yamlandexamples/namespaced/mysql/user_iam_auth.yamlFixes #106
I have:
make reviewableto ensure this PR is ready for review.How has this code been tested