kaxil commented on code in PR #73797:
URL: https://github.com/apache/airflow/pull/73797#discussion_r4262244842


##########
shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py:
##########
@@ -54,24 +54,33 @@ def to_dict(self) -> dict[str, Any]: ...
         "access_token",
         "api_key",
         "apikey",
+        "app_key",
         "auth_header",
         "authorization",
         "bearer",
+        "certificate",
         "connection_string",
+        "credential",

Review Comment:
   These are substring matches with no scoping by connection type, so some 
catch fields that aren't secrets. `kube_config` matches `kube_config_path` (a 
plain string in the kubernetes provider.yaml), and `credential` matches 
google's `credential_config_file`. When a key matches, `add_mask` adds the 
value to the log patterns, so a kubeconfig path turns into `***` in task logs. 
And users can't take a name back out through `sensitive_var_conn_names`.
   
   I'd narrow these to exact field names. Using the `format: password` that 
provider.yaml already declares per field would get there too, and would also 
catch apprise `config`.



##########
airflow-core/tests/unit/always/test_providers_manager.py:
##########
@@ -263,6 +263,42 @@ def test_connection_form_widgets(self, 
yaml_ui_metadata_counts):
             # widgets loaded from YAML metadata only
             assert len(connections_form_widgets) == yaml_widgets
 
+    def test_sensitive_connection_fields_are_redactable(self):
+        """
+        Every connection field a provider declares as sensitive must be in the 
redaction allowlist.
+
+        Providers already state which fields are secret; this asserts 
``DEFAULT_SENSITIVE_FIELDS``
+        never drifts apart from them.
+        """
+        pytest.importorskip("flask_appbuilder", reason="connection form 
widgets need flask_appbuilder")
+
+        from airflow._shared.secrets_masker import DEFAULT_SENSITIVE_FIELDS
+        from airflow._shared.secrets_masker.secrets_masker import SecretsMasker
+
+        # Check the shipped defaults rather than the process-wide masker, whose
+        # sensitive_variables_fields is only populated once settings have been 
configured.
+        masker = SecretsMasker()
+        masker.sensitive_variables_fields = list(DEFAULT_SENSITIVE_FIELDS)
+
+        # "config" is too generic a field name to add to the allowlist; 
apprise's
+        # "config" field is handled separately.

Review Comment:
   I couldn't find where apprise `config` is handled. 
`should_hide_value_for_key("config")` is False, so the connections API returns 
it unredacted inside `extra`, and the apprise docs put a webhook URL in it. 
Could the comment say this is a known unredacted secret, so the exemption 
doesn't read as already covered?



##########
airflow-core/tests/unit/always/test_providers_manager.py:
##########
@@ -263,6 +263,42 @@ def test_connection_form_widgets(self, 
yaml_ui_metadata_counts):
             # widgets loaded from YAML metadata only
             assert len(connections_form_widgets) == yaml_widgets
 
+    def test_sensitive_connection_fields_are_redactable(self):

Review Comment:
   `connection_form_widgets` goes through `_import_info_from_all_hooks()`, and 
the comment on the test above says whichever test hits that first can exceed 
the default 60s. Should this one get `@pytest.mark.execution_timeout(120)` as 
well, since xdist ordering can make it first on its worker?



##########
airflow-core/src/airflow/providers_manager.py:
##########
@@ -270,6 +270,21 @@ class ConnectionFormWidgetInfo(NamedTuple):
     is_sensitive: bool
 
 
+def _is_sensitive_form_field(field: Any) -> bool:

Review Comment:
   I don't think this changes anything. `field` is an unbound field, so 
`field.field_class.widget` is the class-level default widget, not the `widget=` 
kwarg the provider passed. For any `PasswordField` that is `PasswordInput` with 
`input_type == "password"`, so the asb `credential` was already 
`is_sensitive=True` before this PR. The `__mro__` branch only matters for a 
subclass, and subclasses are rejected earlier by the `widget.field_class not in 
allowed_field_classes` check. The docstring is off too: 
`StringField(widget=PasswordInput())` still comes out non-sensitive. Could we 
keep the original one-line expression, and drop the "regardless of the widget" 
sentence from the description?



##########
airflow-core/tests/unit/always/test_providers_manager.py:
##########
@@ -263,6 +263,42 @@ def test_connection_form_widgets(self, 
yaml_ui_metadata_counts):
             # widgets loaded from YAML metadata only
             assert len(connections_form_widgets) == yaml_widgets
 
+    def test_sensitive_connection_fields_are_redactable(self):
+        """
+        Every connection field a provider declares as sensitive must be in the 
redaction allowlist.
+
+        Providers already state which fields are secret; this asserts 
``DEFAULT_SENSITIVE_FIELDS``
+        never drifts apart from them.
+        """
+        pytest.importorskip("flask_appbuilder", reason="connection form 
widgets need flask_appbuilder")
+
+        from airflow._shared.secrets_masker import DEFAULT_SENSITIVE_FIELDS

Review Comment:
   Nit: both are exported from `airflow._shared.secrets_masker`, so these can 
be one import at the top of the file.



-- 
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