This is an automated email from the ASF dual-hosted git repository.
dstandish 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 cab342ee01 Use single source of truth for sensitive config items
(#31820)
cab342ee01 is described below
commit cab342ee010bfd048006ca458c760b37470b6ea5
Author: Daniel Standish <[email protected]>
AuthorDate: Fri Jun 9 14:38:44 2023 -0700
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
---
airflow/config_templates/config.yml | 5 +++++
airflow/configuration.py | 31 ++++++++++++---------------
airflow/www/views.py | 4 +---
tests/core/test_configuration.py | 33 +++++++++++++++++++++++++++++
tests/www/views/test_views_configuration.py | 8 +++----
5 files changed, 57 insertions(+), 24 deletions(-)
diff --git a/airflow/config_templates/config.yml
b/airflow/config_templates/config.yml
index f946588893..ea2bb03fa3 100644
--- a/airflow/config_templates/config.yml
+++ b/airflow/config_templates/config.yml
@@ -434,6 +434,7 @@ core:
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 @@ logging:
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 @@ secrets:
``{{"connections_prefix": "/airflow/connections", "profile_name":
"default"}}``
version_added: 1.10.10
type: string
+ sensitive: true
example: ~
default: ""
cli:
@@ -1929,6 +1932,7 @@ sentry:
description: ~
version_added: 1.10.6
type: string
+ sensitive: true
example: ~
default: ""
before_send:
@@ -2200,6 +2204,7 @@ celery_broker_transport_options:
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 2b64da8f46..ea3be518ae 100644
--- a/airflow/configuration.py
+++ b/airflow/configuration.py
@@ -37,7 +37,7 @@ from configparser import _UNSET, ConfigParser, NoOptionError,
NoSectionError #
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 @@ def default_config_yaml() -> dict[str, Any]:
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 @@ class AirflowConfigParser(ConfigParser):
# 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 d63f0f36c4..23c00d0346 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -4159,11 +4159,9 @@ class ConfigurationView(AirflowBaseView):
# 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 2afc78bcaf..90aa7959ea 100644
--- a/tests/core/test_configuration.py
+++ b/tests/core/test_configuration.py
@@ -36,6 +36,7 @@ from airflow.configuration import (
AirflowConfigException,
AirflowConfigParser,
conf,
+ default_config_yaml,
expand_env_var,
get_airflow_config,
get_airflow_home,
@@ -1447,3 +1448,35 @@ sql_alchemy_conn=sqlite://test
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 be27477953..a6ea406e54 100644
--- a/tests/www/views/test_views_configuration.py
+++ b/tests/www/views/test_views_configuration.py
@@ -18,7 +18,7 @@ from __future__ import annotations
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 @@ def test_user_cant_view_configuration(admin_client):
@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 @@ def test_user_can_view_configuration(admin_client):
@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 @@ def test_configuration_redacted(admin_client):
@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