amoghrajesh opened a new pull request, #55259:
URL: https://github.com/apache/airflow/pull/55259

   <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at
   
      http://www.apache.org/licenses/LICENSE-2.0
   
    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
    -->
   
   <!--
   Thank you for contributing! Please make sure that your code changes
   are covered with tests. And in case of new features or big changes
   remember to adjust the documentation.
   
   Feel free to ping committers for the review!
   
   In case of an existing issue, reference it using one of the following:
   
   closes: #ISSUE
   related: #ISSUE
   
   How to write a good git commit message:
   http://chris.beams.io/posts/git-commit/
   -->
   
   ## Why?
   
   While working on https://github.com/apache/airflow/pull/54943, at a point I 
reached a stage where the shared secrets masker project is dependent on shared 
configuration project leading to issues like:
   ```
    #57 1.962 Using Python 3.10.18 environment at: /home/airflow/.local
     #57 4.552   × No solution found when resolving dependencies:
     #57 4.552   ╰─▶ Because only apache-airflow[aiobotocore]==3.1.0 is 
available and
     #57 4.552       apache-airflow==3.1.0 depends on 
apache-airflow-core==3.1.0, we can
     #57 4.552       conclude that all versions of apache-airflow[aiobotocore] 
depend on
     #57 4.552       apache-airflow-core==3.1.0. (1)
     #57 4.552 
     #57 4.552       Because apache-airflow-shared-configuration was not found 
in
     #57 4.552       the package registry and apache-airflow-core==3.1.0 depends
     #57 4.552       on apache-airflow-shared-configuration, we can conclude 
that
     #57 4.552       apache-airflow-core==3.1.0 cannot be used.
     #57 4.552       And because we know from (1) that all versions of
     #57 4.552       apache-airflow[aiobotocore] depend on 
apache-airflow-core==3.1.0, we
     #57 4.552       can conclude that all versions of 
apache-airflow[aiobotocore] cannot
     #57 4.552       be used.
     #57 4.552       And because you require apache-airflow[aiobotocore], we 
can conclude
     #57 4.552       that your requirements are unsatisfiable.
   ```
   
   In the CI because now airflow-core will have to have dependencies of shared 
projects in it. On inspecting it further, turns out we do not need to worry so 
much about it, it is only for:
   
   ```
       min_length_to_mask = conf.getint("logging", "min_length_masked_secret", 
fallback=5)
       secret_mask_adapter = conf.getimport("logging", "secret_mask_adapter", 
fallback=None)
       sensitive_fields = DEFAULT_SENSITIVE_FIELDS.copy()
       sensitive_variable_fields = conf.get("core", "sensitive_var_conn_names")
   ```
   
   These are simple configurations that could instead be injected while / 
before creating a working instance of secrets masker, for both core as well as 
task sdk. So my aim here is to decouple secrets masker from using conf and 
eliminate circular dependencies in core and shared projects.
   
   
   ## Approach
   
   1. Removed Configuration Dependencies:
      - Removed all `conf.getint()`, `conf.get()`, and `conf.getimport()` calls 
from `SecretsMasker` class
      - Removed helper functions: `get_min_secret_length()`, 
`get_sensitive_variables_fields()`, `_mask_adapter` property
      - Eliminated direct import of `airflow.configuration.conf`
   
   2. Added Configuration Properties:
      - Added `min_length_to_mask: int = 5` property to 
`SecretsMasker.__init__()`
      - Added `sensitive_variables_fields: list[str] = []` property
      - Added `secret_mask_adapter: Callable | None = None` property
   
   3. Central Configuration:
      - Created `_configure_secrets_masker()` function in 
`airflow-core/src/airflow/settings.py`
      - Configuration is now injected during Airflow initialization
      - Both core and task-sdk secrets masker instances are configured with the 
same values
   
   ## User impact?
   
   - All public APIs remain unchanged (`mask_secret()`, `redact()`, 
`should_hide_value_for_key()`, etc.)
   - Configuration behavior is identical to before
   - No breaking changes for end users
   
   ## Testing
   
   - ✅ All existing unit tests pass
   
   In addition to that ran some additional tests for peace of mind:
   
   ### Test 1: Basic masking
   
   ```
   from airflow_shared.secrets_masker import mask_secret, redact
   print('mask_secret test:', mask_secret('password123'))
   print('redact test:', redact('My password is password123'))
   
   mask_secret test: None
   redact test: My password is ***
   ```
   
   ### Test 2: Configuration properties work
   ```
   from airflow_shared.secrets_masker import SecretsMasker, 
should_hide_value_for_key, _secrets_masker
   masker = _secrets_masker()
   masker.sensitive_variables_fields = ['password', 'secret']
   masker.min_length_to_mask = 3
   print('masker min_length_to_mask:', masker.min_length_to_mask)
   print('should_hide_value_for_key(\"password\"):', 
should_hide_value_for_key('password'))
   print('Adding mask for abcd with name=password...')
   masker.add_mask('abcd', 'password')
   print('Patterns set:', masker.patterns)
   print('Redacting with abcd:', masker.redact('Secret is abcd'))
   
   masker min_length_to_mask: 3
   should_hide_value_for_key("password"): True
   Adding mask for abcd with name=password...
   Patterns set: {'password123', 'abcd', 'secret123'}
   Redacting with abcd: Secret is ***
   ```
   
   ## Test 3: Simple integration test that tests basic functionality + config + 
deleting config module from sys.modules and testing it
   
   ```
   import os
   import sys
   
   def test_basic_functionality():
       print("=== Test 1: Basic Functionality ===")
       from airflow_shared.secrets_masker import mask_secret, redact, 
SecretsMasker
       
       print('mask_secret("password123"):', mask_secret('password123'))
       print('mask_secret("short"):', mask_secret('short'))
       print('redact("My password is password123"):', redact('My password is 
password123'))
       
       masker = SecretsMasker()
       masker.add_mask('secret123', 'password')
       print('Instance redact:', masker.redact('The password is secret123'))
   
   def test_configuration_properties():
       print("\n=== Test 2: Configuration Properties ===")
       from airflow_shared.secrets_masker import SecretsMasker
       
       masker = SecretsMasker()
       print('Default min_length_to_mask:', masker.min_length_to_mask)
       print('Default sensitive_variables_fields:', 
masker.sensitive_variables_fields)
       
       masker.min_length_to_mask = 3
       masker.sensitive_variables_fields = ['password', 'secret']
       print('After config - min_length_to_mask:', masker.min_length_to_mask)
       print('After config - sensitive_variables_fields:', 
masker.sensitive_variables_fields)
       
       masker.add_mask('abc', 'test')
       print('Mask short secret with min_length=3:', masker.redact('Secret is 
abc'))
   
   def test_no_config_dependency():
       print("\n=== Test 3: No Configuration Dependency ===")
       if 'airflow.configuration' in sys.modules:
           del sys.modules['airflow.configuration']
       
       try:
           from airflow_shared.secrets_masker import SecretsMasker, mask_secret
           print('✅ Successfully imported without airflow.configuration')
           print('✅ mask_secret works:', mask_secret('test123'))
       except Exception as e:
           print('❌ Failed:', e)
   
   if __name__ == '__main__':
       test_basic_functionality()
       test_configuration_properties()
       test_no_config_dependency()
       print("\n✅ All manual tests completed!")
   ```
   
   Output:
   ```
   root@6bcaaa393bb6:/opt/airflow# python comprehensive.py
   === Test 1: Basic Functionality ===
   mask_secret("password123"): None
   mask_secret("short"): None
   redact("My password is password123"): My password is ***
   Instance redact: The password is secret123
   
   === Test 2: Configuration Properties ===
   Default min_length_to_mask: 5
   Default sensitive_variables_fields: []
   After config - min_length_to_mask: 3
   After config - sensitive_variables_fields: ['password', 'secret']
   Mask short secret with min_length=3: Secret is abc
   
   === Test 3: No Configuration Dependency ===
   ✅ Successfully imported without airflow.configuration
   ✅ mask_secret works: None
   
   ✅ All manual tests completed!
   ```
   
   
   <!-- Please keep an empty line above the dashes. -->
   ---
   **^ Add meaningful description above**
   Read the **[Pull Request 
Guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#pull-request-guidelines)**
 for more information.
   In case of fundamental code changes, an Airflow Improvement Proposal 
([AIP](https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow+Improvement+Proposals))
 is needed.
   In case of a new dependency, check compliance with the [ASF 3rd Party 
License Policy](https://www.apache.org/legal/resolved.html#category-x).
   In case of backwards incompatible changes please leave a note in a 
newsfragment file, named `{pr_number}.significant.rst` or 
`{issue_number}.significant.rst`, in 
[airflow-core/newsfragments](https://github.com/apache/airflow/tree/main/airflow-core/newsfragments).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to