This is an automated email from the ASF dual-hosted git repository.

ashb 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 e886dfd05ee Allow missing `api_auth.jwt_secret` for 
`InProcessExecutionAPI` (#68980)
e886dfd05ee is described below

commit e886dfd05eee4b8b38efdbd34f2fae5ae49ac6cd
Author: Nick Stenning <[email protected]>
AuthorDate: Thu Jun 25 13:05:48 2026 +0100

    Allow missing `api_auth.jwt_secret` for `InProcessExecutionAPI` (#68980)
    
    PR #68840 fixed an issue where exceptions thrown by the FastAPI
    lifecycle hook were swallowed. In doing so, it exposed a pre-existing
    problem where the lifecycle hook couldn't run when the JWT secret was
    not provided.
    
    As the `InProcessExecutionAPI` overrides auth, it doesn't need a JWT
    secret, and we certainly don't want to start crashing processes that
    previously ran fine as a result of a missing secret that we don't need.
    
    This commit stubs out the registered `JWTValidator` in the FastAPI app
    created for the `InProcessExecutionAPI`. This is done in an isolated
    services registry to ensure we don't leak this into any real app
    instantiations.
---
 .../src/airflow/api_fastapi/execution_api/app.py      | 19 +++++++++++++++----
 .../tests/unit/api_fastapi/execution_api/test_app.py  | 10 ++++++++++
 task-sdk/dev/generate_task_sdk_models.py              |  2 --
 3 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/airflow-core/src/airflow/api_fastapi/execution_api/app.py 
b/airflow-core/src/airflow/api_fastapi/execution_api/app.py
index 449019db799..5a87cdb81e0 100644
--- a/airflow-core/src/airflow/api_fastapi/execution_api/app.py
+++ b/airflow-core/src/airflow/api_fastapi/execution_api/app.py
@@ -99,8 +99,11 @@ async def lifespan(app: FastAPI, registry: svcs.Registry):
     app.state.svcs_registry = registry
 
     registry.register_factory(JWTGenerator, _jwt_generator)
-    # Create an app scoped validator, so that we don't have to fetch it every 
time
-    registry.register_value(JWTValidator, _jwt_validator(), 
ping=JWTValidator.status)
+
+    # InProcessExecutionAPI stubs out JWTValidator: don't re-register in that 
case.
+    if JWTValidator not in registry:
+        # Create an app scoped validator, so that we don't have to fetch it 
every time
+        registry.register_value(JWTValidator, _jwt_validator(), 
ping=JWTValidator.status)
 
     yield
 
@@ -282,7 +285,7 @@ def _inject_trace_context_dep(routes, mode: str) -> None:
                     route.dependencies.append(dep)
 
 
-def create_task_execution_api_app() -> FastAPI:
+def create_task_execution_api_app(lifespan: svcs.fastapi.lifespan = lifespan) 
-> FastAPI:
     """Create FastAPI app for task execution API."""
     from airflow.api_fastapi.execution_api.routes import execution_api_router
     from airflow.api_fastapi.execution_api.versions import bundle
@@ -388,7 +391,15 @@ class InProcessExecutionAPI:
             from airflow.api_fastapi.execution_api.routes.xcoms import 
has_xcom_access
             from airflow.api_fastapi.execution_api.security import _jwt_bearer
 
-            self._app = create_task_execution_api_app()
+            # Give this app its own lifespan + services registry so that 
stubbing services
+            # (e.g. JWTValidator) doesn't affect the module-level 
``lifespan.registry``.
+            registry = svcs.Registry()
+            private_lifespan = attrs.evolve(lifespan, registry=registry)
+            self._app = 
create_task_execution_api_app(lifespan=private_lifespan)
+
+            # In-process callers don't need a real JWTValidator: auth is 
bypassed below via
+            # ``dependency_overrides``.
+            registry.register_value(JWTValidator, None)
 
             # Set up dag_bag in app state for dependency injection
             self._app.state.dag_bag = create_dag_bag()
diff --git a/airflow-core/tests/unit/api_fastapi/execution_api/test_app.py 
b/airflow-core/tests/unit/api_fastapi/execution_api/test_app.py
index 32880773ade..d4b9ce5ac88 100644
--- a/airflow-core/tests/unit/api_fastapi/execution_api/test_app.py
+++ b/airflow-core/tests/unit/api_fastapi/execution_api/test_app.py
@@ -22,6 +22,7 @@ import threading
 from unittest import mock
 from uuid import UUID
 
+import httpx
 import pytest
 from fastapi import Request
 from fastapi.params import Security as SecurityParam
@@ -137,6 +138,15 @@ def 
test_routes_with_task_instance_id_param_enforce_ti_self(client):
     )
 
 
+@conf_vars({("api_auth", "jwt_secret"): None})
+def test_in_process_execution_api_runs_without_jwt_secret():
+    """The in-process API must not require ``api_auth/jwt_secret`` to be 
configured."""
+    api = InProcessExecutionAPI()
+    with httpx.Client(transport=api.transport) as client:
+        response = client.get("http://localhost/health";)
+    assert response.status_code == 200
+
+
 def test_in_process_execution_api_transport_lifecycle():
     """The background loop + thread lifecycle is tied to the ``.transport``, 
not the factory instance.
 
diff --git a/task-sdk/dev/generate_task_sdk_models.py 
b/task-sdk/dev/generate_task_sdk_models.py
index 74a1cb7f756..bb19fffc449 100644
--- a/task-sdk/dev/generate_task_sdk_models.py
+++ b/task-sdk/dev/generate_task_sdk_models.py
@@ -33,8 +33,6 @@ from datamodel_code_generator import (
 from openapi_spec_validator import validate_spec
 
 os.environ["_AIRFLOW__AS_LIBRARY"] = "1"
-# Set a placeholder secret to allow the in-process FastAPI app to run its 
lifecycle hooks.
-os.environ.setdefault("AIRFLOW__API_AUTH__JWT_SECRET", 
"task-sdk-model-generation")
 
 AIRFLOW_ROOT_PATH = Path(__file__).parents[2].resolve()
 AIRFLOW_TASK_SDK_ROOT_PATH = AIRFLOW_ROOT_PATH / "task-sdk"

Reply via email to