This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new f9244064016 Strip userinfo from ES host URL before using it as
task-log label (#65349)
f9244064016 is described below
commit f9244064016a8db45277efb0c24808e663b233f3
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu Apr 16 20:02:07 2026 +0200
Strip userinfo from ES host URL before using it as task-log label (#65349)
* Strip userinfo from ES host URL before using it as task-log label
The Elasticsearch task-log handler grouped hits by host, falling back
to the raw ``[elasticsearch] host`` config value when a hit lacked a
``host`` field. That config commonly embeds credentials
(``https://user:[email protected]:9200``), so the full URL —
including the ``user:password@`` userinfo — would appear as a
dictionary key in the task-log output, where any user with task-log
read permission could see it.
Add a ``_strip_userinfo`` helper and use it for the host fallback in
``_group_logs_by_host``. The Elasticsearch client is still connected
using the full unredacted URL, so authentication is unaffected.
Generated-by: Claude Opus 4.6 (1M context) following the guidelines at
https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions
* Apply suggestion from @potiuk
---
providers/elasticsearch/docs/changelog.rst | 8 +++++++
.../providers/elasticsearch/log/es_task_handler.py | 25 +++++++++++++++++++++-
.../unit/elasticsearch/log/test_es_task_handler.py | 16 ++++++++++++++
3 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/providers/elasticsearch/docs/changelog.rst
b/providers/elasticsearch/docs/changelog.rst
index 2742767eb33..3adf13e4f17 100644
--- a/providers/elasticsearch/docs/changelog.rst
+++ b/providers/elasticsearch/docs/changelog.rst
@@ -27,6 +27,14 @@
Changelog
---------
+When the ``[elasticsearch] host`` config embeds credentials
+(``https://user:[email protected]:9200``), the log-source label
+shown in task logs is now the host URL with the ``user:password@`` portion
+stripped. Previously the full URL (including credentials) could appear as
+a dictionary key in the task-log output when log-hits did not carry a
+``host`` field. The Elasticsearch client is still connected using the
+full URL, so authentication is unaffected.
+
6.5.2
.....
diff --git
a/providers/elasticsearch/src/airflow/providers/elasticsearch/log/es_task_handler.py
b/providers/elasticsearch/src/airflow/providers/elasticsearch/log/es_task_handler.py
index 3296f000f51..a8c676a8699 100644
---
a/providers/elasticsearch/src/airflow/providers/elasticsearch/log/es_task_handler.py
+++
b/providers/elasticsearch/src/airflow/providers/elasticsearch/log/es_task_handler.py
@@ -166,6 +166,28 @@ def getattr_nested(obj, item, default):
return default
+def _strip_userinfo(url: str) -> str:
+ """
+ Return ``url`` with any ``user:password@`` userinfo removed.
+
+ The Elasticsearch ``[elasticsearch] host`` config commonly embeds
+ credentials (``https://user:[email protected]:9200``). This
+ value is reused as a display label for log-source grouping, so the
+ credentials would otherwise end up in task logs. Anything that is
+ not a valid URL is returned unchanged.
+ """
+ try:
+ parsed = urlparse(url)
+ except (TypeError, ValueError):
+ return url
+ if not parsed.hostname or (not parsed.username and not parsed.password):
+ return url
+ netloc = parsed.hostname
+ if parsed.port is not None:
+ netloc = f"{netloc}:{parsed.port}"
+ return parsed._replace(netloc=netloc).geturl()
+
+
def _render_log_id(log_id_template: str, ti: TaskInstance | TaskInstanceKey,
try_number: int) -> str:
return log_id_template.format(
dag_id=ti.dag_id,
@@ -801,8 +823,9 @@ class ElasticsearchRemoteLogIO(LoggingMixin): # noqa: D101
def _group_logs_by_host(self, response: ElasticSearchResponse) ->
dict[str, list[Hit]]:
grouped_logs = defaultdict(list)
+ host_fallback = _strip_userinfo(self.host)
for hit in response:
- key = getattr_nested(hit, self.host_field, None) or self.host
+ key = getattr_nested(hit, self.host_field, None) or host_fallback
grouped_logs[key].append(hit)
return grouped_logs
diff --git
a/providers/elasticsearch/tests/unit/elasticsearch/log/test_es_task_handler.py
b/providers/elasticsearch/tests/unit/elasticsearch/log/test_es_task_handler.py
index 0ab4a8e2ed4..90ed1773b35 100644
---
a/providers/elasticsearch/tests/unit/elasticsearch/log/test_es_task_handler.py
+++
b/providers/elasticsearch/tests/unit/elasticsearch/log/test_es_task_handler.py
@@ -43,6 +43,7 @@ from airflow.providers.elasticsearch.log.es_task_handler
import (
_clean_date,
_format_error_detail,
_render_log_id,
+ _strip_userinfo,
get_es_kwargs_from_config,
getattr_nested,
)
@@ -228,6 +229,21 @@ class TestElasticsearchTaskHandler:
else:
assert ElasticsearchTaskHandler.format_url(host) == expected
+ @pytest.mark.parametrize(
+ ("host", "expected"),
+ [
+ ("https://user:[email protected]:9200",
"https://elk.example.com:9200"),
+ ("http://USER:[email protected]", "http://elk.example.com"),
+ ("https://elk.example.com:9200", "https://elk.example.com:9200"),
+ ("http://localhost:9200", "http://localhost:9200"),
+ ("https://[email protected]", "https://elk.example.com"),
+ ("not-a-url", "not-a-url"),
+ ("", ""),
+ ],
+ )
+ def test_strip_userinfo(self, host, expected):
+ assert _strip_userinfo(host) == expected
+
def test_client(self):
assert isinstance(self.es_task_handler.client,
elasticsearch.Elasticsearch)
assert self.es_task_handler.index_patterns == "_all"