diff --git a/airflow/config_templates/config.yml b/airflow/config_templates/config.yml index 25bfe163ec..0cfcd4d687 100644 --- a/airflow/config_templates/config.yml +++ b/airflow/config_templates/config.yml @@ -1678,6 +1678,13 @@ type: string example: ~ default: "True" + - name: sensitive_variable_fields + description: | + A comma-separated list of sensitive keywords to look for in variables names. + version_added: ~ + type: string + example: ~ + default: "" - name: elasticsearch description: ~ options: diff --git a/airflow/config_templates/default_airflow.cfg b/airflow/config_templates/default_airflow.cfg index 2dabd96e88..cb4f22c796 100644 --- a/airflow/config_templates/default_airflow.cfg +++ b/airflow/config_templates/default_airflow.cfg @@ -795,6 +795,9 @@ api_rev = v3 # UI to hide sensitive variable fields when set to True hide_sensitive_variable_fields = True +# A comma-separated list of sensitive keywords to look for in variables names. +sensitive_variable_fields = + [elasticsearch] # Elasticsearch host host = diff --git a/airflow/config_templates/default_test.cfg b/airflow/config_templates/default_test.cfg index 304e835387..0b35ad55ce 100644 --- a/airflow/config_templates/default_test.cfg +++ b/airflow/config_templates/default_test.cfg @@ -110,6 +110,7 @@ max_tis_per_query = 512 [admin] hide_sensitive_variable_fields = True +sensitive_variable_fields = [elasticsearch] host = diff --git a/airflow/www/utils.py b/airflow/www/utils.py index 3c71b0d919..b0ae07bd10 100644 --- a/airflow/www/utils.py +++ b/airflow/www/utils.py @@ -39,7 +39,7 @@ from airflow.utils.state import State from airflow.www.forms import DateTimeWithTimezoneField from airflow.www.widgets import AirflowDateTimePickerWidget -DEFAULT_SENSITIVE_VARIABLE_FIELDS = ( +DEFAULT_SENSITIVE_VARIABLE_FIELDS = { 'password', 'secret', 'passwd', @@ -47,15 +47,19 @@ DEFAULT_SENSITIVE_VARIABLE_FIELDS = ( 'api_key', 'apikey', 'access_token', -) +} + +sensitive_variable_fields = conf.get('admin', 'sensitive_variable_fields') +if sensitive_variable_fields: + DEFAULT_SENSITIVE_VARIABLE_FIELDS.update(sensitive_variable_fields.split(',')) def should_hide_value_for_key(key_name): # It is possible via importing variables from file that a key is empty. if key_name: - config_set = conf.getboolean('admin', - 'hide_sensitive_variable_fields') - field_comp = any(s in key_name.lower() for s in DEFAULT_SENSITIVE_VARIABLE_FIELDS) + config_set = conf.getboolean('admin', 'hide_sensitive_variable_fields') + + field_comp = any(s in key_name.strip().lower() for s in DEFAULT_SENSITIVE_VARIABLE_FIELDS) return config_set and field_comp return False diff --git a/docs/security.rst b/docs/security.rst index a1f9609818..c8f6e1a57f 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -421,3 +421,15 @@ the new key to the ``fernet_key`` setting, run #. Set ``fernet_key`` to ``new_fernet_key,old_fernet_key`` #. Run ``airflow rotate_fernet_key`` to re-encrypt existing credentials with the new fernet key #. Set ``fernet_key`` to ``new_fernet_key`` + +Sensitive Variable fields +------------------------- + +By default, Airflow Value of a variable will be hidden if the key contains any words in +(‘password’, ‘secret’, ‘passwd’, ‘authorization’, ‘api_key’, ‘apikey’, ‘access_token’), but can be configured +to extend this list by using the following configurations option: + +.. code-block:: ini + + [admin] + hide_sensitive_variable_fields = comma_seperated_sensitive_variable_fields_list diff --git a/docs/ui.rst b/docs/ui.rst index 5e50a026fe..d93954e0ad 100644 --- a/docs/ui.rst +++ b/docs/ui.rst @@ -79,6 +79,13 @@ of a variable used during jobs. Value of a variable will be hidden if the key co any words in ('password', 'secret', 'passwd', 'authorization', 'api_key', 'apikey', 'access_token') by default, but can be configured to show in clear-text. +It's also can be configured to extend this list by using the following configurations option: + +.. code-block:: ini + + [admin] + hide_sensitive_variable_fields = comma_seperated_sensitive_variable_fields_list + ------------ .. image:: img/variable_hidden.png diff --git a/scripts/ci/kubernetes/app/templates/configmaps.template.yaml b/scripts/ci/kubernetes/app/templates/configmaps.template.yaml index 8525b7b231..e25654aeaf 100644 --- a/scripts/ci/kubernetes/app/templates/configmaps.template.yaml +++ b/scripts/ci/kubernetes/app/templates/configmaps.template.yaml @@ -326,6 +326,9 @@ data: # UI to hide sensitive variable fields when set to True hide_sensitive_variable_fields = True + # A comma-separated list of sensitive keywords to look for in variables names. + sensitive_variable_fields = + [elasticsearch] host = # yamllint enable rule:line-length diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py index b775d78649..d1c6a2a7f7 100644 --- a/tests/www/test_utils.py +++ b/tests/www/test_utils.py @@ -21,8 +21,10 @@ from datetime import datetime from urllib.parse import parse_qs from bs4 import BeautifulSoup +from parameterized import parameterized from airflow.www import utils +from tests.test_utils.config import conf_vars class TestUtils(unittest.TestCase): @@ -39,6 +41,34 @@ class TestUtils(unittest.TestCase): def test_sensitive_variable_should_be_hidden_ic(self): self.assertTrue(utils.should_hide_value_for_key("GOOGLE_API_KEY")) + @parameterized.expand( + [ + ('key', 'TRELLO_KEY', True), + ('key', 'TRELLO_API_KEY', True), + ('key', 'GITHUB_APIKEY', True), + ('key, token', 'TRELLO_TOKEN', True), + ('mysecretword, mysensitivekey', 'GITHUB_mysecretword', True), + ], + ) + def test_sensitive_variable_fields_should_be_hidden( + self, sensitive_variable_fields, key, expected_result + ): + with conf_vars({('admin', 'sensitive_variable_fields'): str(sensitive_variable_fields)}): + self.assertEqual(expected_result, utils.should_hide_value_for_key(key)) + + @parameterized.expand( + [ + (None, 'TRELLO_API', False), + ('token', 'TRELLO_KEY', False), + ('token, mysecretword', 'TRELLO_KEY', False) + ], + ) + def test_normal_variable_fields_should_not_be_hidden( + self, sensitive_variable_fields, key, expected_result + ): + with conf_vars({('admin', 'sensitive_variable_fields'): str(sensitive_variable_fields)}): + self.assertEqual(expected_result, utils.should_hide_value_for_key(key)) + def check_generate_pages_html(self, current_page, total_pages, window=7, check_middle=False): extra_links = 4 # first, prev, next, last