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 a058f7d54eb Only refuse team-scoped-looking env var ids when 
multi_team is on (#71079)
a058f7d54eb is described below

commit a058f7d54eb91cdb87dc6e636a4109067b452fce
Author: Amogh Desai <[email protected]>
AuthorDate: Wed Aug 5 09:19:14 2026 +0530

    Only refuse team-scoped-looking env var ids when multi_team is on (#71079)
    
    * Only refuse team-scoped-looking env var ids when multi_team is on
    
    * Only refuse team-scoped-looking env var ids when multi_team is on
---
 .../src/airflow/secrets/environment_variables.py     | 20 +++++++++++++++-----
 airflow-core/tests/unit/always/test_secrets.py       |  2 ++
 .../always/test_secrets_environment_variables.py     | 19 +++++++++++++++++++
 3 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/airflow-core/src/airflow/secrets/environment_variables.py 
b/airflow-core/src/airflow/secrets/environment_variables.py
index 2432c084f5d..6eb36978d98 100644
--- a/airflow-core/src/airflow/secrets/environment_variables.py
+++ b/airflow-core/src/airflow/secrets/environment_variables.py
@@ -21,6 +21,7 @@ from __future__ import annotations
 
 import os
 
+from airflow.configuration import conf
 from airflow.secrets import BaseSecretsBackend
 
 CONN_ENV_PREFIX = "AIRFLOW_CONN_"
@@ -34,10 +35,20 @@ TEAM_SEP = "___"
 class EnvironmentVariablesBackend(BaseSecretsBackend):
     """Retrieves Connection object and Variable from environment variable."""
 
+    @staticmethod
+    def _names_a_team_namespace(secret_id: str) -> bool:
+        """
+        Whether ``secret_id`` spells out a team scoped secret name.
+
+        Only checked in multi-team mode: ``team_name`` is never non-``None`` 
otherwise, so no
+        team scoped variable can exist to collide with.
+        """
+        if not conf.getboolean("core", "multi_team", fallback=False):
+            return False
+        return TEAM_SEP in secret_id
+
     def get_conn_value(self, conn_id: str, team_name: str | None = None) -> 
str | None:
-        if TEAM_SEP in conn_id:
-            # An id containing the separator could collide with another team's 
namespace
-            # even on the scoped lookup below, so it must be refused before 
either runs.
+        if self._names_a_team_namespace(conn_id):
             return None
 
         if team_name and (
@@ -56,8 +67,7 @@ class EnvironmentVariablesBackend(BaseSecretsBackend):
         :param team_name: Team name associated to the task trying to access 
the variable (if any)
         :return: Variable Value
         """
-        if TEAM_SEP in key:
-            # Same collision risk as get_conn_value, see its code comment.
+        if self._names_a_team_namespace(key):
             return None
 
         if team_name and (
diff --git a/airflow-core/tests/unit/always/test_secrets.py 
b/airflow-core/tests/unit/always/test_secrets.py
index 0c2749ca9d1..ff9aebb8e75 100644
--- a/airflow-core/tests/unit/always/test_secrets.py
+++ b/airflow-core/tests/unit/always/test_secrets.py
@@ -122,6 +122,7 @@ class TestConnectionsFromSecrets:
         assert conn.get_uri() == "mysql://airflow:airflow@host:5432/airflow"
 
     @pytest.mark.db_test
+    @conf_vars({("core", "multi_team"): "True"})
     @mock.patch.dict(
         "os.environ",
         {
@@ -218,6 +219,7 @@ class TestVariableFromSecrets:
         mock_secret_get.return_value = "a_secret_value"
         assert Variable.get(key="not_myvar") == "a_secret_value"
 
+    @conf_vars({("core", "multi_team"): "True"})
     @mock.patch.dict(
         "os.environ",
         {
diff --git 
a/airflow-core/tests/unit/always/test_secrets_environment_variables.py 
b/airflow-core/tests/unit/always/test_secrets_environment_variables.py
index 9f8e8379efc..200b545bf03 100644
--- a/airflow-core/tests/unit/always/test_secrets_environment_variables.py
+++ b/airflow-core/tests/unit/always/test_secrets_environment_variables.py
@@ -26,6 +26,8 @@ from airflow.secrets.environment_variables import (
     EnvironmentVariablesBackend,
 )
 
+from tests_common.test_utils.config import conf_vars
+
 # A team specific secret is stored as ``<PREFIX>_<TEAM_NAME>___<SECRET_ID>``. 
Team names may contain
 # underscores (they are validated against ``^[a-zA-Z0-9_-]{3,50}$``), so both 
shapes are exercised.
 TEAM_NAMES = ["team_a", "teama"]
@@ -57,6 +59,11 @@ def lookup(env_prefix: str, method: str, secret_id: str, 
team_name: str | None)
 class TestEnvironmentVariablesBackendTeamScope:
     """A team specific secret must only be resolvable for the team it is 
stored for."""
 
+    @pytest.fixture(autouse=True)
+    def _multi_team_enabled(self):
+        with conf_vars({("core", "multi_team"): "True"}):
+            yield
+
     @pytest.mark.parametrize(("env_prefix", "method"), LOOKUPS)
     @pytest.mark.parametrize("team_name", TEAM_NAMES)
     def test_team_scoped_secret_is_not_resolved_without_a_team_scope(
@@ -186,3 +193,15 @@ class TestEnvironmentVariablesBackendTeamScope:
         monkeypatch.delenv(env_prefix + SECRET_ID.upper(), raising=False)
 
         assert lookup(env_prefix, method, SECRET_ID, team_name) is None
+
+
+class TestEnvironmentVariablesBackendMultiTeamDisabled:
+    """No team scoped variable can exist without multi-team mode, so there is 
no ambiguity
+    to refuse -- an ordinary id containing the separator must resolve 
normally."""
+
+    @pytest.mark.parametrize(("env_prefix", "method"), LOOKUPS)
+    def test_ambiguous_id_resolves_when_multi_team_is_disabled(self, 
monkeypatch, env_prefix, method):
+        secret_id = f"prod{TEAM_SEP}{SECRET_ID}"
+        monkeypatch.setenv(env_prefix + secret_id.upper(), GLOBAL_VALUE)
+
+        assert lookup(env_prefix, method, secret_id, None) == GLOBAL_VALUE

Reply via email to