Repository navigation
Forward workload_identity_impersonation_path in SnowflakeHook - #74591
spcvalente wants to merge 4 commits into
Conversation
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
| * ``token_file_path``: Path to a file holding the OIDC ID token used when ``workload_identity_provider`` | ||
| is ``OIDC``. The connector reads the token from this file, which suits projected or rotated tokens | ||
| (for example a Kubernetes service-account token). | ||
| * ``workload_identity_impersonation_path``: Identities to impersonate, in order, before authenticating with |
There was a problem hiding this comment.
Should this have _path at the end of it? I would assume anything with a _path at the end is pointing to a path to a file itself.
There was a problem hiding this comment.
I think in GCP it's something like _chain. Does that make more sense?
There was a problem hiding this comment.
Kept the name on purpose: it's the connector's own parameter (workload_identity_impersonation_path, snowflake-connector-python 4.0.0), same as the other WIF extras (workload_identity_provider, token, token_file_path), so it maps 1:1 to Snowflake's docs. _chain is the Google provider's naming. Agree _path reads like a file though, so the docs now say it's a chain of identities, not a file path.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
| "impersonation_path", | ||
| [ | ||
| ["arn:aws:iam::123456789012:role/a", "arn:aws:iam::123456789012:role/b"], | ||
| "arn:aws:iam::123456789012:role/a, arn:aws:iam::123456789012:role/b", |
There was a problem hiding this comment.
Can you update the spacing a bit? It's a little confusing.
There was a problem hiding this comment.
Cleaned up: the ARNs live in one constant, the comma-separated case is built from it, and the assert is a single line.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
| if token_file_path: | ||
| conn_config["token_file_path"] = token_file_path | ||
|
|
||
| impersonation_path = self._get_field(extra_dict, "workload_identity_impersonation_path") |
There was a problem hiding this comment.
Did you validate that adding workload_identity_impersonation_path to extra doesn't already propagate it through?
There was a problem hiding this comment.
Yes. Running the new test against main's hook fails with KeyError: 'workload_identity_impersonation_path': _get_conn_params builds conn_config from known fields only, so unknown extras never reach snowflake.connector.connect.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
14dcfef to
cb3b78f
Compare
|
Maybe it will be good to add get_connection_form_widgets and get_ui_field_behaviour entries |
@frodo2000 good call, added both in 89577b8: a text field, with a placeholder for comma-separated role ARNs (AWS) or service accounts (GCP). The hook already splits a comma-separated string into the list the connector expects. Drafted-by: Claude Code (Opus 5.5) (no human review before posting) |
638e5f7 to
8b87ca5
Compare
| authenticating with Workload Identity Federation, as a list or a comma-separated string (not a file path; | ||
| the name matches the connector parameter). For ``AWS``, IAM role ARNs that the workload's credentials | ||
| assume in turn; for ``GCP``, service account emails. Snowflake then trusts the last identity in the chain | ||
| instead of the workload's own. Requires ``snowflake-connector-python>=4.0.0``. |
There was a problem hiding this comment.
The docs say “Requires >=4.0.0”, but nothing enforces it. Could we either raise exception in the hook
There was a problem hiding this comment.
Done in df40620: the hook now raises AirflowOptionalProviderFeatureException when the path is set and snowflake-connector-python is older than 4.0.0. Older connectors only warn about an unknown parameter and then authenticate as the workload itself, so failing is safer. Bumping the minimum is blocked by the sqlalchemy>=2.0 TODO in pyproject.toml.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
|
|
||
| assert "workload_identity_provider" in widgets | ||
|
|
||
| def test_connection_form_exposes_workload_identity_impersonation_path(self): |
There was a problem hiding this comment.
Add a test that the key is absent when unset, or when the value is only whitespace or commas, like the existing test_get_conn_params_omits_oidc_token_when_unset
There was a problem hiding this comment.
Added test_get_conn_params_omits_workload_identity_impersonation_path_when_empty, covering unset, "", " , " and []. The " , " case did forward an empty list before.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
|
|
||
| impersonation_path = self._get_field(extra_dict, "workload_identity_impersonation_path") | ||
|
|
||
| if impersonation_path: |
There was a problem hiding this comment.
may be?
if isinstance(impersonation_path, str):
impersonation_path = impersonation_path.split(",")
if not isinstance(impersonation_path, list):
raise ValueError("workload_identity_impersonation_path must be a list or a comma-separated string")
impersonation_path = [str(step).strip() for step in impersonation_path if str(step).strip()]
if impersonation_path:
conn_config["workload_identity_impersonation_path"] = impersonation_path
include list aswell its better to validate
There was a problem hiding this comment.
Taken as suggested: it accepts a list or a comma-separated string, strips and drops empty steps either way, and raises ValueError otherwise.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
| if impersonation_path: | ||
| if isinstance(impersonation_path, str): | ||
| impersonation_path = [step.strip() for step in impersonation_path.split(",") if step.strip()] | ||
| conn_config["workload_identity_impersonation_path"] = impersonation_path |
There was a problem hiding this comment.
I think this will break when it goes through SQLAlchemy. get_uri() / get_sqlalchemy_engine() build the URL from _get_conn_params(), and snowflake-sqlalchemy’s _url() just does str(v) on anything that isn’t a string. So the list goes into the query string as ['arn:...', 'arn:...'], and the connector gets one string back, not a list. get_conn() and Snowpark would be fine, but anything going through the engine (get_df etc.) wouldn’t get the impersonation chain.
Could we handle it the way private_key and session_parameters are handled? Add it to the exclusion list in _conn_params_to_sqlalchemy_uri and pass it through connect_args in get_sqlalchemy_engine:
There was a problem hiding this comment.
Good catch, confirmed: _url() turns the list into a string and the connector then iterates it character by character. It is now excluded from the URI and passed through connect_args, like private_key and session_parameters, with a test on get_sqlalchemy_engine.
Drafted-by: Claude Code (Opus 5.5) (no human review before posting)
8b87ca5 to
df40620
Compare
3cd3f0c to
37d0a17
Compare
|
Hey @gopidesupavan, rebased onto main and addressed your comments. Could you take another look when you have a moment? 🙏 Drafted-by: Claude Code (Opus 5.5) (no human review before posting) |
37d0a17 to
37b009a
Compare
Read workload_identity_impersonation_path from the connection extras (a list or a comma-separated string) and pass it to the connector, so a workload can impersonate a role or service account before authenticating with Workload Identity Federation. closes: apache#74590
…my engines snowflake-sqlalchemy stringifies non-string URL values, so the impersonation list reached the connector as one string and SQLAlchemy-based calls lost the chain. Connector releases before 4.0.0 ignore the parameter and authenticate as the workload's own identity, so fail loudly instead.
The provider.yaml check now requires conn-fields and placeholders to match the hook's connection form.
37b009a to
3b5f20e
Compare
SnowflakeHooknow readsworkload_identity_impersonation_pathfrom the connection extras (a list or a comma-separated string) and passes it to the connector (snowflake-connector-python>=4.0.0). A workload can then impersonate a role (AWS) or service account (GCP) before authenticating with Workload Identity Federation. Docs and unit tests included.closes: #74590
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Code following the guidelines