This is an automated email from the ASF dual-hosted git repository.
Miretpl pushed a commit to branch chart/v1-2x-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/chart/v1-2x-test by this push:
new fbc3c21eda1 [chart/v1-2x-test] Separate JWT secret env var from the
standard Airflow environment helper (#70896) (#71129)
fbc3c21eda1 is described below
commit fbc3c21eda1fe0bc8097d61ebef703c2a5f6b8a5
Author: Rohan Bandaru <[email protected]>
AuthorDate: Tue Aug 11 14:58:58 2026 -0700
[chart/v1-2x-test] Separate JWT secret env var from the standard Airflow
environment helper (#70896) (#71129)
`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
(cherry picked from commit 562cc3e243c756d25c1c1996c38ec7a480ce7b59)
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 04f5516567a..655374282ec 100644
--- a/chart/templates/_helpers.yaml
+++ b/chart/templates/_helpers.yaml
@@ -114,13 +114,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 (semverCompare ">=3.0.0"
.Values.airflowVersion)
.Values.enableBuiltInSecretEnvVars.AIRFLOW__API_AUTH__JWT_SECRET }}
- - name: AIRFLOW__API_AUTH__JWT_SECRET
- valueFrom:
- secretKeyRef:
- name: {{ template "jwt_secret" . }}
- key: jwt-secret
- {{- end }}
{{- if or (contains "CeleryExecutor" .Values.executor) (contains
"CeleryKubernetesExecutor" .Values.executor) }}
{{- if and
.Values.enableBuiltInSecretEnvVars.AIRFLOW__CELERY__RESULT_BACKEND (or
.Values.data.resultBackendSecretName .Values.data.resultBackendConnection) }}
- name: AIRFLOW__CELERY__RESULT_BACKEND
@@ -171,6 +164,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 and (semverCompare ">=3.0.0" .Values.airflowVersion)
.Values.enableBuiltInSecretEnvVars.AIRFLOW__API_AUTH__JWT_SECRET }}
+ - name: AIRFLOW__API_AUTH__JWT_SECRET
+ valueFrom:
+ secretKeyRef:
+ name: {{ template "jwt_secret" . }}
+ key: jwt-secret
+ {{- end }}
+{{- end }}
+
{{/* User defined Airflow environment variables */}}
{{- define "custom_airflow_environment" }}
# Dynamically created environment variables
diff --git a/chart/templates/api-server/api-server-deployment.yaml
b/chart/templates/api-server/api-server-deployment.yaml
index af9c38e15cd..dafe2a78eb8 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 9496ca96366..35c7215b479 100644
--- a/chart/templates/dag-processor/dag-processor-deployment.yaml
+++ b/chart/templates/dag-processor/dag-processor-deployment.yaml
@@ -136,7 +136,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 }}
@@ -181,7 +181,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 41e90859193..9b353fed724 100644
--- a/chart/templates/scheduler/scheduler-deployment.yaml
+++ b/chart/templates/scheduler/scheduler-deployment.yaml
@@ -173,7 +173,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 }}
@@ -201,7 +201,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 9f1b763f98b..24414a575f8 100644
--- a/chart/templates/triggerer/triggerer-deployment.yaml
+++ b/chart/templates/triggerer/triggerer-deployment.yaml
@@ -158,7 +158,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 }}
@@ -206,7 +206,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.triggerer.env)
| nindent 10 }}
livenessProbe:
initialDelaySeconds: {{
.Values.triggerer.livenessProbe.initialDelaySeconds }}
diff --git a/chart/templates/workers/worker-deployment.yaml
b/chart/templates/workers/worker-deployment.yaml
index a551152caa0..dfbc044bf51 100644
--- a/chart/templates/workers/worker-deployment.yaml
+++ b/chart/templates/workers/worker-deployment.yaml
@@ -225,7 +225,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.waitForMigrations.enabled }}
- name: wait-for-airflow-migrations
@@ -253,7 +253,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.waitForMigrations.env }}
{{- tpl (toYaml .Values.workers.waitForMigrations.env) $ |
nindent 12 }}
{{- end }}
@@ -339,7 +339,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 }}
{{- include "container_extra_envs" (list . .Values.workers.env) |
indent 10 }}
{{- if .Values.workers.kerberosSidecar.enabled }}
- name: KRB5_CONFIG
@@ -451,7 +451,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.extraContainers }}
{{- tpl (toYaml .Values.workers.extraContainers) . | nindent 8 }}
diff --git a/helm-tests/tests/helm_tests/airflow_aux/test_airflow_common.py
b/helm-tests/tests/helm_tests/airflow_aux/test_airflow_common.py
index 0f786a77fba..958c9cf3b02 100644
--- a/helm-tests/tests/helm_tests/airflow_aux/test_airflow_common.py
+++ b/helm-tests/tests/helm_tests/airflow_aux/test_airflow_common.py
@@ -448,8 +448,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",
@@ -468,6 +468,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(
values={