Use single source of truth for sensitive config items (#31820)
Previously we had them defined both in constant and in config.yml.
Now just config.yml
diff --git a/airflow/config_templates/config.yml b/airflow/config_templates/config.yml
index f946588..ea2bb03 100644
--- a/airflow/config_templates/config.yml
+++ b/airflow/config_templates/config.yml
@@ -434,6 +434,7 @@
description: Kwargs to supply to dataset manager.
version_added: 2.4.0
type: string
+ sensitive: true
default: ~
example: '{"some_param": "some_value"}'
database_access_isolation:
@@ -662,6 +663,7 @@
log files will be deleted after they are uploaded to remote location.
version_added: 2.6.0
type: string
+ sensitive: true
example: '{"delete_local_copy": true}'
default: ""
encrypt_s3_logs:
@@ -1052,6 +1054,7 @@
``{{"connections_prefix": "/airflow/connections", "profile_name": "default"}}``
version_added: 1.10.10
type: string
+ sensitive: true
example: ~
default: ""
cli:
@@ -1929,6 +1932,7 @@
description: ~
version_added: 1.10.6
type: string
+ sensitive: true
example: ~
default: ""
before_send:
@@ -2200,6 +2204,7 @@
https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/redis.html#configuration
version_added: 2.7.0
type: string
+ sensitive: true
example: '{"password": "password_for_redis_server"}'
default: ~
dask:
diff --git a/airflow/configuration.py b/airflow/configuration.py
index 2b64da8..ea3be51 100644
--- a/airflow/configuration.py
+++ b/airflow/configuration.py
@@ -37,7 +37,7 @@
from contextlib import contextmanager, suppress
from json.decoder import JSONDecodeError
from re import Pattern
-from typing import IO, Any, Dict, Iterable, Tuple, Union
+from typing import IO, Any, Dict, Iterable, Set, Tuple, Union
from urllib.parse import urlsplit
from typing_extensions import overload
@@ -146,21 +146,6 @@
return yaml.safe_load(config_file)
-SENSITIVE_CONFIG_VALUES = {
- ("database", "sql_alchemy_conn"),
- ("core", "fernet_key"),
- ("celery", "broker_url"),
- ("celery", "flower_basic_auth"),
- ("celery", "result_backend"),
- ("atlas", "password"),
- ("smtp", "smtp_password"),
- ("webserver", "secret_key"),
- ("secrets", "backend_kwargs"),
- # The following options are deprecated
- ("core", "sql_alchemy_conn"),
-}
-
-
class AirflowConfigParser(ConfigParser):
"""Custom Airflow Configparser supporting defaults and deprecated options."""
@@ -170,7 +155,19 @@
# These configs can also be fetched from Secrets backend
# following the "{section}__{name}__secret" pattern
- sensitive_config_values: set[tuple[str, str]] = SENSITIVE_CONFIG_VALUES
+ @functools.cached_property
+ def sensitive_config_values(self) -> Set[tuple[str, str]]: # noqa: UP006
+ default_config = default_config_yaml()
+ flattened = {
+ (s, k): item for s, s_c in default_config.items() for k, item in s_c.get("options").items()
+ }
+ sensitive = {(section, key) for (section, key), v in flattened.items() if v.get("sensitive") is True}
+ depr_option = {self.deprecated_options[x][:-1] for x in sensitive if x in self.deprecated_options}
+ depr_section = {
+ (self.deprecated_sections[s][0], k) for s, k in sensitive if s in self.deprecated_sections
+ }
+ sensitive.update(depr_section, depr_option)
+ return sensitive
# A mapping of (new section, new option) -> (old section, old option, since_version).
# When reading new option, the old option will be checked to see if it exists. If it does a
diff --git a/airflow/www/views.py b/airflow/www/views.py
index d63f0f3..23c00d0 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -4159,11 +4159,9 @@
# TODO remove "if raw" usage in Airflow 3.0. Configuration can be fetched via the REST API.
if raw:
if expose_config == "non-sensitive-only":
- from airflow.configuration import SENSITIVE_CONFIG_VALUES
-
updater = configupdater.ConfigUpdater()
updater.read(AIRFLOW_CONFIG)
- for sect, key in SENSITIVE_CONFIG_VALUES:
+ for sect, key in conf.sensitive_config_values:
if updater.has_option(sect, key):
updater[sect][key].value = "< hidden >"
config = str(updater)
diff --git a/tests/core/test_configuration.py b/tests/core/test_configuration.py
index 2afc78b..90aa795 100644
--- a/tests/core/test_configuration.py
+++ b/tests/core/test_configuration.py
@@ -36,6 +36,7 @@
AirflowConfigException,
AirflowConfigParser,
conf,
+ default_config_yaml,
expand_env_var,
get_airflow_config,
get_airflow_home,
@@ -1447,3 +1448,35 @@
w = captured.pop()
assert "your `conf.get*` call to use the new name" in str(w.message)
assert w.category == FutureWarning
+
+
+def test_sensitive_values():
+ from airflow.settings import conf
+
+ # this list was hardcoded prior to 2.6.2
+ # included here to avoid regression in refactor
+ # inclusion of keys ending in "password" or "kwargs" is automated from 2.6.2
+ # items not matching this pattern must be added here manually
+ sensitive_values = {
+ ("database", "sql_alchemy_conn"),
+ ("core", "fernet_key"),
+ ("celery", "broker_url"),
+ ("celery", "flower_basic_auth"),
+ ("celery", "result_backend"),
+ ("atlas", "password"),
+ ("smtp", "smtp_password"),
+ ("webserver", "secret_key"),
+ ("secrets", "backend_kwargs"),
+ ("sentry", "sentry_dsn"),
+ ("database", "sql_alchemy_engine_args"),
+ ("core", "sql_alchemy_conn"),
+ }
+ default_config = default_config_yaml()
+ all_keys = {(s, k) for s, v in default_config.items() for k in v.get("options")}
+ suspected_sensitive = {(s, k) for (s, k) in all_keys if k.endswith(("password", "kwargs"))}
+ exclude_list = {
+ ("kubernetes_executor", "delete_option_kwargs"),
+ }
+ suspected_sensitive -= exclude_list
+ sensitive_values.update(suspected_sensitive)
+ assert sensitive_values == conf.sensitive_config_values
diff --git a/tests/www/views/test_views_configuration.py b/tests/www/views/test_views_configuration.py
index be27477..a6ea406 100644
--- a/tests/www/views/test_views_configuration.py
+++ b/tests/www/views/test_views_configuration.py
@@ -18,7 +18,7 @@
import html
-from airflow.configuration import SENSITIVE_CONFIG_VALUES, conf
+from airflow.configuration import conf
from tests.test_utils.config import conf_vars
from tests.test_utils.www import check_content_in_response, check_content_not_in_response
@@ -36,7 +36,7 @@
@conf_vars({("webserver", "expose_config"): "True"})
def test_user_can_view_configuration(admin_client):
resp = admin_client.get("configuration", follow_redirects=True)
- for section, key in SENSITIVE_CONFIG_VALUES:
+ for section, key in conf.sensitive_config_values:
value = conf.get(section, key, fallback="")
if not value:
continue
@@ -46,7 +46,7 @@
@conf_vars({("webserver", "expose_config"): "non-sensitive-only"})
def test_configuration_redacted(admin_client):
resp = admin_client.get("configuration", follow_redirects=True)
- for section, key in SENSITIVE_CONFIG_VALUES:
+ for section, key in conf.sensitive_config_values:
value = conf.get(section, key, fallback="")
if not value or value == "airflow":
continue
@@ -58,7 +58,7 @@
@conf_vars({("webserver", "expose_config"): "non-sensitive-only"})
def test_configuration_redacted_in_running_configuration(admin_client):
resp = admin_client.get("configuration", follow_redirects=True)
- for section, key in SENSITIVE_CONFIG_VALUES:
+ for section, key in conf.sensitive_config_values:
value = conf.get(section, key, fallback="")
if not value or value == "airflow":
continue