Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion airflow/api_connexion/endpoints/config_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def get_value(section: str, option: str) -> Response:
"Config not found.", detail=f"The option [{section}/{option}] is not found in config."
)

if (section, option) in conf.sensitive_config_values:
if (section.lower(), option.lower()) in conf.sensitive_config_values:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this, I think we should do what we do in the webserver

airflow/airflow/www/views.py

Lines 3840 to 3845 in f349fda

updater = configupdater.ConfigUpdater()
updater.read(AIRFLOW_CONFIG)
for sect, key in conf.sensitive_config_values:
if updater.has_option(sect, key):
updater[sect][key].value = "< hidden >"
config = str(updater)
.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot fix the problem, because it checks if the value exists in the configuration as it is. But since we don't read any configuration from the user in this view, it's safe because all the configuration in both side (sensitive dict and configuration dict) are in low format.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @hussein-awala . I think the proposed way is right. The "www/views.py" code displays all the sections/options from the config and then then from sensitive_config_values. What we are preventing here is that differently cased section/option is passed by the user, so the source of section/key are not the config itself, but external to it. We can safely assume that the config has the right sections/keys (that's why the code in views.py works). But we cannot make the same assumption about the values passed by the user so we have to lowercase them.

value = "< hidden >"
else:
value = conf.get(section, option)
Expand Down
6 changes: 5 additions & 1 deletion airflow/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,11 @@ def sensitive_config_values(self) -> Set[tuple[str, str]]: # noqa: UP006
for s, s_c in self.configuration_description.items()
for k, item in s_c.get("options").items() # type: ignore[union-attr]
}
sensitive = {(section, key) for (section, key), v in flattened.items() if v.get("sensitive") is True}
sensitive = {
(section.lower(), key.lower())
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
Expand Down
19 changes: 14 additions & 5 deletions tests/api_connexion/endpoints/test_config_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,26 @@ def test_should_respond_200_text_plain(self, mock_as_dict):
return_value=MOCK_CONF_WITH_SENSITIVE_VALUE,
)
@conf_vars({("webserver", "expose_config"): "non-sensitive-only"})
def test_should_respond_200_text_plain_with_non_sensitive_only(self, mock_as_dict):
@pytest.mark.parametrize(
"section, option",
[
("core", "sql_alchemy_conn"),
("core", "SQL_ALCHEMY_CONN"),
("corE", "sql_alchemy_conn"),
("CORE", "sql_alchemy_conn"),
],
)
def test_should_respond_200_text_plain_with_non_sensitive_only(self, mock_as_dict, section, option):
response = self.client.get(
"/api/v1/config/section/core/option/sql_alchemy_conn",
f"/api/v1/config/section/{section}/option/{option}",
headers={"Accept": "text/plain"},
environ_overrides={"REMOTE_USER": "test"},
)
assert response.status_code == 200
expected = textwrap.dedent(
"""\
[core]
sql_alchemy_conn = < hidden >
f"""\
[{section}]
{option} = < hidden >
"""
)
assert expected == response.data.decode()
Expand Down