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