Skip to content

Fix secrets not masked in Rendered Templates view with KubernetesPodOperator - #68975

Merged
shahar1 merged 6 commits into
apache:mainfrom
juan-pablo-guereca:fix/k8s-pod-operator-secrets-masking
Jul 22, 2026
Merged

Fix secrets not masked in Rendered Templates view with KubernetesPodOperator#68975
shahar1 merged 6 commits into
apache:mainfrom
juan-pablo-guereca:fix/k8s-pod-operator-secrets-masking

Conversation

@juan-pablo-guereca

@juan-pablo-guereca juan-pablo-guereca commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Regression fix, restores Airflow 2 behavior, adds nothing new.

When var.json is used in a Jinja template (e.g. to pass secrets as env vars to a KubernetesPodOperator), _get_variable deserialised the JSON before masking. The isinstance(var_val, str) guard evaluated to False for the resulting dict, so mask_secret was never called. The secret was stored unredacted in the rendered_task_instance_fields table and shown as plain text in the Rendered Templates view.

Both the SecretCache hit path and the secrets-backend path receive the same fix.


Before the fix:

image

After the fix:

image

DAG to reproduce it:

from airflow import DAG
from airflow.operators.bash import BashOperator
from airflow.providers.cncf.kubernetes.operators.pod import (
    KubernetesPodOperator,
)

DAG_ID = "test_secrets_masking"

ENVS = {
    "some_password": "{{ var.value.some_password }}",
    "some_password_json": "{{ var.json.some_password }}",
    "some_password_json.secret": "{{ var.json.some_password.secret }}",
    "some_password_json.foo": "{{ var.json.some_password.foo }}",
}

with DAG(
    dag_id=DAG_ID,
    schedule=None,
    description="just a dummy dag to test secrets masking",
    max_active_runs=1,
):
    command = "echo hi there; env; echo bye"

    BashOperator(task_id="some_bash_pod_operator", bash_command=command, env=ENVS)

    KubernetesPodOperator(
        task_id="some_k8s_pod_operator",
        cmds=["/bin/bash", "-c", command],
        env_vars=ENVS,
        is_delete_operator_pod=True,
        in_cluster=True,
        get_logs=True,
    )

Fixes the issue #69021


Was generative AI tooling used to co-author this PR?
  • Yes, claude

@boring-cyborg

boring-cyborg Bot commented Jun 25, 2026

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example Dag that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@juan-pablo-guereca
juan-pablo-guereca marked this pull request as ready for review June 25, 2026 12:28
…perator

When var.json is used in a Jinja template (e.g. to pass secrets as env
vars to a KubernetesPodOperator), _get_variable deserialised the JSON
before masking. The isinstance(var_val, str) guard evaluated to False
for the resulting dict, so mask_secret was never called. The secret was
stored unredacted in the rendered_task_instance_fields table and shown
as plain text in the Rendered Templates view.

Both the SecretCache hit path and the secrets-backend path receive the
same fix.

@SameerMesiah97 SameerMesiah97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just 2 missing test cases. CI needs to be triggered. Looks good otherwise.

Comment thread task-sdk/tests/task_sdk/execution_time/test_context.py
@potiuk potiuk added the ready for maintainer review Set after triaging when all criteria pass. label Jul 2, 2026

@SameerMesiah97 SameerMesiah97 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolve the comment please. Looks good.

edit: Just saw CI needs to be triggered. Approved pending green CI.

@potiuk

potiuk commented Jul 7, 2026

Copy link
Copy Markdown
Member

It's an interesting one - I am not sure if we want to merge this one. This gives false sense of security - you know you are not supposed to pass secrets in K8S Pod Operator Env Vars ?

Never use environment variables to pass secrets (for example connection authentication information) to Kubernetes Pod Operator. Such environment variables will be visible to anyone who has access to see and describe PODs in Kubernetes. Instead, pass your secrets via native Kubernetes Secrets or use Connections and Variables from Airflow. For the latter, you need to have apache-airflow package installed in your image in the same version as Airflow you run your Kubernetes Pod Operator from).

Reference

I will psss that to the security team for consideration - but I think this one - especially that it addresses something we explicitly told is a bad practice is not a good idea

@shahar1 shahar1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking until figuring out the above

@juan-pablo-guereca

Copy link
Copy Markdown
Contributor Author

@potiuk totally agree passing secrets via pod env vars isn't safe practice, and this doesn't change that guidance at all, k8s level visibility stays the same either way.

The reason I'd still love to see this merged: this masking already works fine in Airflow 2. In AF3 it quietly stopped working for var.json values in some cases, so this is really just restoring existing behavior, not adding anything new. I worry that teams who tested and trusted this on AF2 might not realize it's silently different on AF3.

One more bit of context in case it's useful: I'm working on an AF2 to AF3 migration with a lot of existing DAGs, some of which already use this pattern (not by choice, just the reality of a large existing codebase). Losing masking silently on upgrade is a real regression for that kind of migration, not just a hypothetical, but happy to defer to whatever the security team decides either way.

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I think if it eases the migration we can release it . @shahar1 ?

Comment thread task-sdk/src/airflow/sdk/execution_time/context.py Outdated
Addressed two bugs in _mask_and_deserialize_variable flagged in review:
- String scalars (e.g. var.json storing '"s3cr3t"'): only the quoted raw
  was registered as a mask pattern, leaving the unquoted value that lands
  in rendered_task_instance_fields unmasked. Fix: call mask_secret(val, key)
  for str values so both forms are registered.
- List variables: mask_secret(val) without a name is unconditional mode,
  causing all elements of non-sensitive lists to be redacted from logs.
  Fix: restrict the dict-walk call to dict values only.
@shahar1
shahar1 merged commit cd28fbd into apache:main Jul 22, 2026
107 checks passed
@boring-cyborg

boring-cyborg Bot commented Jul 22, 2026

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request! You are invited to check our Issue Tracker for additional contributions.

@potiuk potiuk added the backport-to-v3-3-test Backport to v3-3-test label Jul 30, 2026
potiuk added a commit that referenced this pull request Jul 30, 2026
…perator (#68975) (#70756)

(cherry picked from commit cd28fbd)

Co-authored-by: juan-pablo-guereca <juan.guereca@happening.xyz>
vatsrahul1001 pushed a commit that referenced this pull request Aug 5, 2026
…perator (#68975) (#70756)

(cherry picked from commit cd28fbd)

Co-authored-by: juan-pablo-guereca <juan.guereca@happening.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:task-sdk backport-to-v3-3-test Backport to v3-3-test ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants