diff --git a/airflow-core/newsfragments/59688.improvement.rst b/airflow-core/newsfragments/59688.improvement.rst new file mode 100644 index 0000000000000..d3c592cfcc3a1 --- /dev/null +++ b/airflow-core/newsfragments/59688.improvement.rst @@ -0,0 +1 @@ +Add ``proxy`` and ``proxies`` to ``DEFAULT_SENSITIVE_FIELDS`` in secrets_masker to treat proxy configurations as sensitive by default diff --git a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py index 958a3b8f437e7..775f0097602cd 100644 --- a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py +++ b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/msgraph.py @@ -63,6 +63,8 @@ from airflow.models import Connection +from airflow_shared.secrets_masker import redact + class DefaultResponseHandler(ResponseHandler): """DefaultResponseHandler returns JSON payload or content in bytes or response headers.""" @@ -272,7 +274,7 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: self.log.info("Host: %s", host) self.log.info("Base URL: %s", base_url) self.log.info("Client id: %s", client_id) - self.log.info("Client secret: %s", client_secret) + self.log.info("Client secret: %s", redact(client_secret, name="client_secret")) self.log.info("API version: %s", api_version) self.log.info("Scope: %s", scopes) self.log.info("Verify: %s", verify) @@ -280,8 +282,8 @@ def _build_request_adapter(self, connection) -> tuple[str, RequestAdapter]: self.log.info("Trust env: %s", trust_env) self.log.info("Authority: %s", authority) self.log.info("Allowed hosts: %s", allowed_hosts) - self.log.info("Proxies: %s", proxies) - self.log.info("HTTPX Proxies: %s", httpx_proxies) + self.log.info("Proxies: %s", redact(proxies, name="proxies")) + self.log.info("HTTPX Proxies: %s", redact(httpx_proxies, name="proxies")) credentials = self.get_credentials( login=connection.login, password=connection.password, @@ -391,7 +393,7 @@ def get_credentials( self.log.info("Certificate data: %s", certificate_data is not None) self.log.info("Authority: %s", authority) self.log.info("Disable instance discovery: %s", disable_instance_discovery) - self.log.info("MSAL Proxies: %s", msal_proxies) + self.log.info("MSAL Proxies: %s", redact(msal_proxies, name="proxies")) if certificate_path or certificate_data: return CertificateCredential( tenant_id=tenant_id, diff --git a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py index affe190b35925..157d75b323019 100644 --- a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py +++ b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_msgraph.py @@ -387,6 +387,27 @@ async def test_throw_failed_responses_with_application_json_content_type(self): error_code = actual.get_child_node("error").get_child_node("code").get_str_value() assert error_code == "TenantThrottleThresholdExceeded" + @pytest.mark.asyncio + async def test_build_request_adapter_masks_secrets(self): + """Test that sensitive data is masked when building request adapter.""" + with patch( + f"{BASEHOOK_PATCH_PATH}.get_connection", + side_effect=lambda conn_id: get_airflow_connection( + conn_id=conn_id, + password="my_secret_password", + proxies={"http": "http://user:pass@proxy:3128"}, + ), + ): + with patch("airflow.providers.microsoft.azure.hooks.msgraph.redact") as mock_redact: + mock_redact.side_effect = lambda x, name=None: "***" if x else x + + hook = KiotaRequestAdapterHook(conn_id="msgraph_api") + await hook.get_async_conn() + + assert mock_redact.call_count >= 3 + mock_redact.assert_any_call({"http": "http://user:pass@proxy:3128"}, name="proxies") + mock_redact.assert_any_call("my_secret_password", name="client_secret") + class TestResponseHandler: def test_default_response_handler_when_json(self): diff --git a/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py b/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py index 5b7ff4e1c81e4..c78e39ca65ad3 100644 --- a/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py +++ b/shared/secrets_masker/src/airflow_shared/secrets_masker/secrets_masker.py @@ -58,6 +58,8 @@ def to_dict(self) -> dict[str, Any]: ... "passwd", "password", "private_key", + "proxy", + "proxies", "secret", "token", "keyfile_dict", diff --git a/task-sdk/src/airflow/sdk/log.py b/task-sdk/src/airflow/sdk/log.py index 6e81a96b2542d..1ee7b1ed1f008 100644 --- a/task-sdk/src/airflow/sdk/log.py +++ b/task-sdk/src/airflow/sdk/log.py @@ -36,12 +36,10 @@ from airflow.sdk.types import Logger, RuntimeTaskInstanceProtocol as RuntimeTI -__all__ = ["configure_logging", "reset_logging", "mask_secret"] +from airflow.sdk._shared.secrets_masker import redact def mask_logs(logger: Any, method_name: str, event_dict: EventDict) -> EventDict: - from airflow.sdk._shared.secrets_masker import redact - event_dict = redact(event_dict) # type: ignore[assignment] return event_dict