arno-fukuda opened a new issue, #72681:
URL: https://github.com/apache/airflow/issues/72681

   ### Under which category would you file this issue?
   
   Providers
   
   ### Apache Airflow version
   
   3.3.1
   
   ### What happened and how to reproduce it?
   
   Reproduced both inside Cloud Composer and standalone with the provider's own 
`CloudSqlProxyRunner`. The auth cache was cleared before **every** trial, since 
a single clear only makes the first attempt cold.
   
   Reporting here rather than through the managed-service channel because the 
defect is in the provider code, not in Composer — Composer only installs the 
package, and the same failure reproduces on a plain virtualenv install.
   
   ### What happens
   
   `CloudSqlProxyRunner` always launches **Cloud SQL Auth Proxy v1**, and v1 
cannot complete `caching_sha2_password` **full authentication**. Any task 
connecting to a Cloud SQL for MySQL 8.4 instance therefore fails with `1045 
Access denied` on the first connection after the server's auth cache is emptied 
— which happens on every restart, failover or maintenance event.
   
   This is easy to miss, because `caching_sha2_password` has two paths. **Fast 
auth** is served from the server's in-memory cache and works fine over v1. 
**Full auth** is only taken when that cache is cold. The cache never expires on 
its own, so once anything primes it the deployment looks healthy indefinitely — 
until the next restart, at which point tasks start failing for no visible 
reason.
   
   The runner is hard-wired to v1 and there is no way to opt out:
   
   - `CLOUD_SQL_PROXY_DOWNLOAD_URL` / `CLOUD_SQL_PROXY_VERSION_DOWNLOAD_URL` 
point at the v1 buckets
   - `_build_command_line_parameters()` emits `-dir` and `-instances`, both 
removed in v2 (`cloud_sql.py:595-596`, unchanged in 22.4.0)
   - `sql_proxy_version` only selects among **v1** releases
   - `sql_proxy_binary_path` does not help either — a v2 binary rejects those 
flags
   
   ### Why this matters now
   
   Cloud SQL for MySQL 8.4 makes `caching_sha2_password` the default for all 
new users, and **an account cannot be moved back**: `ALTER USER ... IDENTIFIED 
WITH mysql_native_password` returns `ERROR 4052`, and Cloud SQL exposes no flag 
for `authentication_policy` or `mysql_native_password`. So an affected 
deployment has no client-side workaround short of re-priming the auth cache by 
hand after every restart.
   
   Cloud SQL Auth Proxy v1 is also no longer receiving development, and its own 
startup banner recommends migrating to v2.
   
   ### How to reproduce
   
   Against a Cloud SQL for MySQL 8.4 instance with a user on 
`caching_sha2_password`:
   
   1. Empty the auth cache with `FLUSH PRIVILEGES` — this is what a restart 
does. (`ALTER USER USER() IDENTIFIED BY '<same password>'` clears a single 
account's entry and needs no privileges.)
   2. Immediately connect through proxy **v1** → `1045 Access denied`.
   3. Repeat step 1, then connect through proxy **v2** → success.
   
   Step 1 must be repeated before every trial. A single clear only makes the 
first attempt cold, because a successful connection re-primes the cache — this 
is why the problem is often misattributed to the client library.
   
   Result, with the cache flushed before each row:
   
   | client | proxy | cold cache |
   |---|---|---|
   | mysqlclient 2.2.8 | v1 | **FAIL 1045** |
   | mysqlclient 2.2.8 | v2 | OK |
   | mysql-connector-python 26.7 (`use_pure=True`) | v1 | **FAIL 1045** |
   | mysql-connector-python 26.7 (`use_pure=True`) | v2 | OK |
   
   The client library is not the variable — both send the password in cleartext 
over the unix socket on the full-auth path. Only the proxy version matters.
   
   Confirmed inside Composer with a DAG that flushes the cache before each 
probe, connecting via `MySqlHook` through the provider's own 
`CloudSqlProxyRunner` (v1) and through a v2 runner:
   
   ```
   proxy v1   FAIL  cache=cold  OperationalError: (1045, "Access denied for 
user '<user>'@'cloudsqlproxy~<ip>' (using password: YES)")
   proxy v2   OK    cache=cold  SELECT 1 returned ((1,),)
   ```
   
   Failing task traceback (v1):
   
   ```
   File ".../airflow/providers/mysql/hooks/mysql.py", line ..., in get_conn
     ...
   MySQLdb.OperationalError: (1045, "Access denied for user 
'<user>'@'cloudsqlproxy~<ip>' (using password: YES)")
   ```
   
   
   ### What you think should happen instead?
   
   `CloudSqlProxyRunner` should be able to run Auth Proxy v2, ideally by 
default.
   
   A drop-in replacement is straightforward: fetch the binary from
   
`https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/<version>/cloud-sql-proxy.<os>.<arch>`,
   invoke it as `cloud-sql-proxy --unix-socket <dir> <instance>`, and detect 
readiness from the socket
   appearing rather than by parsing the startup banner.
   
   **The socket layout is identical to v1's** (`<path_prefix>/<instance 
specification>`), so existing
   connection extras keep working and this can be a behaviour-compatible swap. 
Credentials come from
   Application Default Credentials in both versions.
   
   Suggested shape, in rough order of preference:
   
   1. Default to v2, with an escape hatch to force v1 for anyone depending on 
its behaviour.
   2. Or accept a `v2.x.x` value in `sql_proxy_version` (or add 
`sql_proxy_major_version`) and branch the
      flag construction accordingly.
   
   We have implemented (1) as a separate runner class and it works in Cloud 
Composer against MySQL 8.4
   with a cold auth cache, using the unchanged `MySqlHook`/mysqlclient client. 
Roughly 100 lines,
   including startup-timeout handling and surfacing the proxy's own stderr on 
early exit — the latter
   matters because a misconfigured quota project makes v2 fail with a 403 that 
is otherwise invisible.
   
   Two details worth carrying into any implementation:
   
   - v2 does **not** create the socket directory, where v1 did. It needs an 
explicit `mkdir`.
   - v2 attributes its Cloud SQL Admin API call to the credentials' quota 
project. Ambient ADC is correct
     on a managed worker, but a developer's local ADC often carries an 
unrelated quota project, which
     produces a confusing `403 SERVICE_DISABLED` naming a project unrelated to 
the instance. Worth an
     optional `--quota-project` passthrough.
   
   
   ### Operating System
   
   Linux — Cloud Composer 3 worker image (composer-3-airflow-3.3.1-build.0), 
Python 3.11.8
   
   ### Deployment
   
   Google Managed Service for Apache Airflow
   
   ### Apache Airflow Provider(s)
   
   google
   
   ### Versions of Apache Airflow Providers
   
   apache-airflow-providers-google==22.3.0
   
   ### Official Helm Chart version
   
   Not Applicable
   
   ### Kubernetes Version
   
   _No response_
   
   ### Helm Chart configuration
   
   _No response_
   
   ### Docker Image customizations
   
   _No response_
   
   ### Anything else?
   
   This occurs on **every** connection attempt made after the instance's auth 
cache is emptied, so in
   practice it means every restart, failover or maintenance event breaks Cloud 
SQL MySQL 8.4 tasks until
   something re-primes the cache out of band.
   
   ### Prior art
   
   - #44564 added v2 support alongside service-account impersonation, but was 
**closed as stale** in
     March 2025 after a requested rebase onto the new provider structure — it 
was never merged. This
     report is narrower: v2 support as a correctness fix, independent of 
impersonation.
   - #39546 (open) covers service-account impersonation, which is related but 
distinct.
   
   ### Note on diagnosis
   
   If anyone investigating this tries to confirm it with the `mysql` CLI: 
`--get-server-public-key`
   supplies an RSA key-exchange capability the affected clients do not have, 
**and** primes the server's
   auth cache. It will report success against an instance that is genuinely 
broken, and mask the problem
   for every subsequent connection. The MySQL error log
   (`textPayload:"Access denied"` on `resource.type="cloudsql_database"`) is a 
more reliable signal — it
   records user, source IP and timestamp, and cannot be fooled by a warm cache.
   
   
   ### Are you willing to submit PR?
   
   - [ ] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to