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

Miretpl 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 562cc3e243c Separate JWT secret env var from the standard Airflow 
environment helper (#70896)
562cc3e243c is described below

commit 562cc3e243c756d25c1c1996c38ec7a480ce7b59
Author: rohan9446 <[email protected]>
AuthorDate: Tue Aug 4 13:30:12 2026 -0700

    Separate JWT secret env var from the standard Airflow environment helper 
(#70896)
    
    `AIRFLOW__API_AUTH__JWT_SECRET` was rendered from inside
    `standard_airflow_environment` behind an `IncludeJwtSecret` flag, so every
    component had to opt out of it explicitly with
    `(merge (dict "IncludeJwtSecret" false) .)`.
    
    Move the variable into its own `jwt_secret_environment` helper, following 
the
    shape of the existing `keda_airflow_environment` helper, and include it 
only in
    the API server and scheduler containers that need it. Every other caller of
    `standard_airflow_environment` passes a plain context again, and the
    `IncludeJwtSecret` context mutation is gone.
    
    No behavioural change: the same containers receive the same variable, still
    gated on `enableBuiltInSecretEnvVars.AIRFLOW__API_AUTH__JWT_SECRET`, 
preserving
    the least-privilege exposure introduced in #63204. Only the position of the
    variable within the rendered env list changes, so the ordered assertion in
    `test_have_all_variables` is updated to match.
    
    Closes: #70843
    
    Co-authored-by: rohan9446 <[email protected]>
---
 chart/templates/_helpers.yaml                      | 18 ++++---
 .../api-server/api-server-deployment.yaml          |  5 +-
 .../dag-processor/dag-processor-deployment.yaml    |  4 +-
 .../templates/scheduler/scheduler-deployment.yaml  |  5 +-
 .../templates/triggerer/triggerer-deployment.yaml  |  4 +-
 chart/templates/workers/worker-deployment.yaml     |  8 +--
 .../helm_tests/airflow_aux/test_airflow_common.py  | 61 +++++++++++++++++++++-
 7 files changed, 85 insertions(+), 20 deletions(-)

diff --git a/chart/templates/_helpers.yaml b/chart/templates/_helpers.yaml
index 1a1460293bc..54625dd01c4 100644
--- a/chart/templates/_helpers.yaml
+++ b/chart/templates/_helpers.yaml
@@ -91,13 +91,6 @@ If release name contains chart name it will be used as a 
full name.
         name: {{ template "api_secret_key_secret" . }}
         key: api-secret-key
   {{- end }}
-  {{- if and .IncludeJwtSecret 
.Values.enableBuiltInSecretEnvVars.AIRFLOW__API_AUTH__JWT_SECRET }}
-  - name: AIRFLOW__API_AUTH__JWT_SECRET
-    valueFrom:
-      secretKeyRef:
-        name: {{ template "jwt_secret" . }}
-        key: jwt-secret
-  {{- end }}
   {{- if contains "CeleryExecutor" .Values.executor }}
     {{- if and 
.Values.enableBuiltInSecretEnvVars.AIRFLOW__CELERY__RESULT_BACKEND (or 
.Values.data.resultBackendSecretName .Values.data.resultBackendConnection) }}
   - name: AIRFLOW__CELERY__RESULT_BACKEND
@@ -148,6 +141,17 @@ If release name contains chart name it will be used as a 
full name.
   {{- end }}
 {{- end }}
 
+{{/* JWT signing and validation secret, only needed by the API server and the 
scheduler */}}
+{{- define "jwt_secret_environment" }}
+  {{- if .Values.enableBuiltInSecretEnvVars.AIRFLOW__API_AUTH__JWT_SECRET }}
+  - name: AIRFLOW__API_AUTH__JWT_SECRET
+    valueFrom:
+      secretKeyRef:
+        name: {{ template "jwt_secret" . }}
+        key: jwt-secret
+  {{- end }}
+{{- end }}
+
 {{/* KEDA scaler database connection for the worker and triggerer autoscalers 
*/}}
 {{- define "keda_airflow_environment" }}
   {{- if or (eq .Values.data.metadataConnection.protocol "mysql") (and 
.Values.pgbouncer.enabled (not .UsePgbouncer)) }}
diff --git a/chart/templates/api-server/api-server-deployment.yaml 
b/chart/templates/api-server/api-server-deployment.yaml
index bc02135ae2b..5d6eeb3d507 100644
--- a/chart/templates/api-server/api-server-deployment.yaml
+++ b/chart/templates/api-server/api-server-deployment.yaml
@@ -151,7 +151,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if .Values.apiServer.waitForMigrations.env }}
               {{- tpl (toYaml .Values.apiServer.waitForMigrations.env) $ | 
nindent 12 }}
             {{- end }}
@@ -225,7 +225,8 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" true) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
+            {{- include "jwt_secret_environment" . | indent 10 }}
             {{- include "container_extra_envs" (list . .Values.apiServer.env) 
| indent 10 }}
         {{- if .Values.apiServer.extraContainers }}
           {{- tpl (toYaml .Values.apiServer.extraContainers) . | nindent 8 }}
diff --git a/chart/templates/dag-processor/dag-processor-deployment.yaml 
b/chart/templates/dag-processor/dag-processor-deployment.yaml
index d3596527c77..6a3865fc366 100644
--- a/chart/templates/dag-processor/dag-processor-deployment.yaml
+++ b/chart/templates/dag-processor/dag-processor-deployment.yaml
@@ -132,7 +132,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if .Values.dagProcessor.waitForMigrations.env }}
               {{- tpl (toYaml .Values.dagProcessor.waitForMigrations.env) $ | 
nindent 12 }}
             {{- end }}
@@ -177,7 +177,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- include "container_extra_envs" (list . 
.Values.dagProcessor.env) | indent 10 }}
           livenessProbe:
             initialDelaySeconds: {{ 
.Values.dagProcessor.livenessProbe.initialDelaySeconds }}
diff --git a/chart/templates/scheduler/scheduler-deployment.yaml 
b/chart/templates/scheduler/scheduler-deployment.yaml
index 35749fdbc5b..57c60f917c0 100644
--- a/chart/templates/scheduler/scheduler-deployment.yaml
+++ b/chart/templates/scheduler/scheduler-deployment.yaml
@@ -164,7 +164,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if .Values.scheduler.waitForMigrations.env }}
               {{- tpl (toYaml .Values.scheduler.waitForMigrations.env) $ | 
nindent 12 }}
             {{- end }}
@@ -192,7 +192,8 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" true) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
+            {{- include "jwt_secret_environment" . | indent 10 }}
             {{- include "container_extra_envs" (list . .Values.scheduler.env) 
| indent 10 }}
           livenessProbe:
             initialDelaySeconds: {{ 
.Values.scheduler.livenessProbe.initialDelaySeconds }}
diff --git a/chart/templates/triggerer/triggerer-deployment.yaml 
b/chart/templates/triggerer/triggerer-deployment.yaml
index 1c773d349ea..806a7a5df31 100644
--- a/chart/templates/triggerer/triggerer-deployment.yaml
+++ b/chart/templates/triggerer/triggerer-deployment.yaml
@@ -155,7 +155,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if .Values.triggerer.waitForMigrations.env }}
               {{- tpl (toYaml .Values.triggerer.waitForMigrations.env) $ | 
nindent 12 }}
             {{- end }}
@@ -200,7 +200,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if $keda }}
             {{- include "keda_airflow_environment" (merge (dict "UsePgbouncer" 
.Values.triggerer.keda.usePgbouncer) .) | indent 10 }}
             {{- end }}
diff --git a/chart/templates/workers/worker-deployment.yaml 
b/chart/templates/workers/worker-deployment.yaml
index 294b0d41010..536e73a216a 100644
--- a/chart/templates/workers/worker-deployment.yaml
+++ b/chart/templates/workers/worker-deployment.yaml
@@ -217,7 +217,7 @@ spec:
             - name: KRB5CCNAME
               value:  {{ include "kerberos_ccache_path" . | quote }}
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
         {{- end }}
         {{- if .Values.workers.celery.waitForMigrations.enabled }}
         - name: wait-for-airflow-migrations
@@ -242,7 +242,7 @@ spec:
           envFrom: {{- include "custom_airflow_environment_from" . | default 
"\n  []" | indent 10 }}
           env:
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if .Values.workers.celery.waitForMigrations.env }}
               {{- tpl (toYaml .Values.workers.celery.waitForMigrations.env) $ 
| nindent 12 }}
             {{- end }}
@@ -325,7 +325,7 @@ spec:
             - name: DUMB_INIT_SETSID
               value: "0"
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
             {{- if $keda }}
             {{- include "keda_airflow_environment" (merge (dict "UsePgbouncer" 
.Values.workers.celery.keda.usePgbouncer) .) | indent 10 }}
             {{- end }}
@@ -434,7 +434,7 @@ spec:
             - name: KRB5CCNAME
               value:  {{ include "kerberos_ccache_path" . | quote }}
             {{- include "custom_airflow_environment" . | indent 10 }}
-            {{- include "standard_airflow_environment" (merge (dict 
"IncludeJwtSecret" false) .) | indent 10 }}
+            {{- include "standard_airflow_environment" . | indent 10 }}
         {{- end }}
         {{- if .Values.workers.celery.extraContainers }}
           {{- tpl (toYaml .Values.workers.celery.extraContainers) . | nindent 
8 }}
diff --git a/chart/tests/helm_tests/airflow_aux/test_airflow_common.py 
b/chart/tests/helm_tests/airflow_aux/test_airflow_common.py
index ff82d4a29ca..0a5b1556279 100644
--- a/chart/tests/helm_tests/airflow_aux/test_airflow_common.py
+++ b/chart/tests/helm_tests/airflow_aux/test_airflow_common.py
@@ -376,8 +376,8 @@ class TestAirflowCommon:
             "AIRFLOW__DATABASE__SQL_ALCHEMY_CONN",
             "AIRFLOW_CONN_AIRFLOW_DB",
             "AIRFLOW__API__SECRET_KEY",
-            "AIRFLOW__API_AUTH__JWT_SECRET",
             "AIRFLOW__CELERY__BROKER_URL",
+            "AIRFLOW__API_AUTH__JWT_SECRET",
         ]
         expected_vars_no_jwt = [
             "AIRFLOW_HOME",
@@ -396,6 +396,65 @@ class TestAirflowCommon:
                 f"Wrong vars in {component}"
             )
 
+    def test_jwt_secret_injected_into_api_server_and_scheduler(self):
+        docs = render_chart(
+            show_only=[
+                "templates/api-server/api-server-deployment.yaml",
+                "templates/scheduler/scheduler-deployment.yaml",
+            ],
+        )
+
+        for doc in docs:
+            component = doc["metadata"]["labels"]["component"]
+            env_names = jmespath.search(
+                
f"spec.template.spec.containers[?name=='{component}'].env[].name", doc
+            )
+            assert env_names.count("AIRFLOW__API_AUTH__JWT_SECRET") == 1, (
+                f"JWT secret missing from {component}"
+            )
+
+            # it must not leak into the sidecars or init containers of those 
same pods
+            other_env_names = jmespath.search(
+                f"[spec.template.spec.containers[?name!='{component}'], "
+                "spec.template.spec.initContainers][][].env[].name",
+                doc,
+            )
+            assert "AIRFLOW__API_AUTH__JWT_SECRET" not in other_env_names, (
+                f"JWT secret leaked into a non-main container of {component}"
+            )
+
+    def test_jwt_secret_not_injected_into_other_components(self):
+        docs = render_chart(
+            show_only=[
+                "templates/workers/worker-deployment.yaml",
+                "templates/triggerer/triggerer-deployment.yaml",
+                "templates/dag-processor/dag-processor-deployment.yaml",
+            ],
+        )
+
+        for doc in docs:
+            component = doc["metadata"]["labels"]["component"]
+            env_names = jmespath.search(
+                "[spec.template.spec.containers, 
spec.template.spec.initContainers][][].env[].name", doc
+            )
+            assert "AIRFLOW__API_AUTH__JWT_SECRET" not in env_names, f"JWT 
secret leaked into {component}"
+
+    def test_jwt_secret_can_be_disabled(self):
+        docs = render_chart(
+            values={"enableBuiltInSecretEnvVars": 
{"AIRFLOW__API_AUTH__JWT_SECRET": False}},
+            show_only=[
+                "templates/api-server/api-server-deployment.yaml",
+                "templates/scheduler/scheduler-deployment.yaml",
+            ],
+        )
+
+        for doc in docs:
+            component = doc["metadata"]["labels"]["component"]
+            env_names = jmespath.search(
+                
f"spec.template.spec.containers[?name=='{component}'].env[].name", doc
+            )
+            assert "AIRFLOW__API_AUTH__JWT_SECRET" not in env_names, f"Wrong 
vars in {component}"
+
     def test_have_all_config_mounts_on_init_containers(self):
         docs = render_chart(
             show_only=[

Reply via email to