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"