Skip to content
Merged
1 change: 1 addition & 0 deletions airflow-core/newsfragments/59688.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``proxy`` and ``proxies`` to ``DEFAULT_SENSITIVE_FIELDS`` in secrets_masker to treat proxy configurations as sensitive by default
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@
TaskDeferred as TaskDeferred,
XComNotFound as XComNotFound,
)
from airflow.sdk.observability.stats import Stats # noqa: F401
from airflow.sdk.log import redact as redact
from airflow.sdk.observability.stats import Stats as Stats

# Airflow 3-only exceptions (conditionally imported)
if AIRFLOW_V_3_0_PLUS:
Expand Down Expand Up @@ -239,6 +240,15 @@
# Observability
# ============================================================================
"Stats": ("airflow.sdk.observability.stats", "airflow.stats"),
# ============================================================================
# Secrets Masking
# ============================================================================
"redact": (
"airflow.sdk.log",
"airflow.sdk._shared.secrets_masker",
"airflow.sdk.execution_time.secrets_masker",
"airflow.utils.log.secrets_masker",
),
}

# Airflow 3-only exceptions (not available in Airflow 2)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@

from airflow.providers.common.compat.sdk import Connection

from airflow.providers.common.compat.sdk import redact

PaginationCallable = Callable[..., tuple[str, dict[str, Any] | None]]

Expand Down Expand Up @@ -303,16 +304,16 @@ 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)
self.log.info("Timeout: %s", self.timeout)
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,
Expand Down Expand Up @@ -433,7 +434,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from json import JSONDecodeError
from os.path import dirname
from typing import TYPE_CHECKING, cast
from unittest.mock import Mock
from unittest.mock import Mock, patch

import pytest
from httpx import Response
Expand Down Expand Up @@ -412,6 +412,26 @@ async def test_paginated_run(self):
assert isinstance(actual, list)
assert actual == [users, next_users]

@pytest.mark.asyncio
async def test_build_request_adapter_masks_secrets(self):
"""Test that sensitive data is masked when building request adapter."""
with patch_hook(
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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ def to_dict(self) -> dict[str, Any]: ...
"passwd",
"password",
"private_key",
"proxy",
"proxies",
"secret",
"token",
"keyfile_dict",
Expand Down
4 changes: 1 addition & 3 deletions task-sdk/src/airflow/sdk/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from airflow.sdk.types import Logger, RuntimeTaskInstanceProtocol as RuntimeTI


__all__ = ["configure_logging", "reset_logging", "mask_secret"]
from airflow.sdk._shared.secrets_masker import redact


class _WarningsInterceptor:
Expand All @@ -64,8 +64,6 @@ def emit_warning(*args: Any) -> None:


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

Expand Down
Loading