github-actions[bot] opened a new pull request, #67173:
URL: https://github.com/apache/airflow/pull/67173

   * Refuse secrets-backend fallback on Execution-API authz deny
   
   ExecutionAPISecretsBackend.get_connection / get_variable returned None
   on every ErrorResponse, conflating "not found" with "explicitly denied".
   The secrets-backend dispatcher then fell through to the next backend
   (typically EnvironmentVariablesBackend, which performs no authz checks)
   on a 401/403 from the Execution API -- letting tasks read secrets the
   Execution API had just denied them. The audit calls this a Type C gap:
   the authz control fires, but its rejection result is treated as a
   miss and routed around.
   
   Three-part fix:
   
   1. New `ErrorType.PERMISSION_DENIED` distinct from `API_SERVER_ERROR`.
   
   2. `ConnectionOperations.get` and `VariableOperations.get` map the
      API server's 401/403 to `ErrorResponse(PERMISSION_DENIED, ...)`
      instead of re-raising as a generic `ServerResponseError`. 404 still
      maps to `*_NOT_FOUND`; other statuses still raise so the existing
      API_SERVER_ERROR translation in `handle_requests` keeps working.
   
   3. `ExecutionAPISecretsBackend` (sync + async, connection + variable
      variants) now raises `PermissionError` on `PERMISSION_DENIED`. The
      surrounding `except Exception:` blocks explicitly re-raise
      `PermissionError` so the secrets-backend dispatcher sees it. NOT_FOUND
      types continue to return `None` (allow fallthrough); other
      ErrorResponses also continue to return `None` (preserve existing
      recovery behaviour for transient errors).
   
   Tests added:
   
   - `client.connections.get` and `client.variables.get` return
     `ErrorResponse(PERMISSION_DENIED)` on 401 and 403 (parametrised).
   - `ExecutionAPISecretsBackend.get_connection` / `get_variable` /
     `aget_connection` / `aget_variable` raise `PermissionError` when the
     response is `PERMISSION_DENIED`, with the resource and key in the
     message.
   
   Reported by the L3 ASVS sweep at apache/tooling-agents#24 (FINDING-017).
   
   * Refuse secrets-backend fallback at the dispatcher, not only the backend
   
   The earlier change made ExecutionAPISecretsBackend raise on 401/403, but
   the dispatcher loops in airflow.sdk.execution_time.context and the
   airflow-core get_*_from_secrets paths catch Exception and silently fall
   through to the next backend — so the deny was still being swallowed.
   
   Introduce AirflowSecretsBackendAccessDenied (subclass of PermissionError)
   so the dispatchers can special-case the authoritative deny without
   mis-treating an incidental OSError-family PermissionError from inside an
   unrelated backend. Patch the three task-SDK dispatcher loops and the two
   airflow-core dispatcher loops to re-raise it before the generic except.
   
   Add TestDispatcherRefusesFallbackOnDeny with three end-to-end tests that
   insert a spy backend after ExecutionAPISecretsBackend and assert the spy
   is never called once the first backend raises the deny — pinning the
   dispatcher behaviour, not just the backend's. Also hoist the repeated
   imports in test_secrets.py to module top per review feedback.
   
   * Hoist AirflowSecretsBackendAccessDenied imports to module top
   (cherry picked from commit 2b8c80568b5dfaea54f367ab08c33d943a80818a)
   
   Co-authored-by: Jarek Potiuk <[email protected]>


-- 
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