Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 17.8k
Redact secrets in rendered templates properly when truncating it#59566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
6906236593c9f696379df4159517File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -784,17 +784,87 @@ def startup() -> tuple[RuntimeTaskInstance, Context, Logger]: | ||
| return ti, ti.get_template_context(), log | ||
| def _serialize_template_field(template_field: Any, name: str) -> str | dict | list | int | float: | ||
| """ | ||
| Return a serializable representation of the templated field. | ||
| If ``templated_field`` contains a class or instance that requires recursive | ||
| templating, store them as strings. Otherwise simply return the field as-is. | ||
| Used sdk secrets masker to redact secrets in the serialized output. | ||
| """ | ||
| import json | ||
| from airflow.sdk._shared.secrets_masker import redact | ||
| def is_jsonable(x): | ||
| try: | ||
| json.dumps(x) | ||
| except (TypeError, OverflowError): | ||
| return False | ||
| else: | ||
| return True | ||
amoghrajesh marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| def translate_tuples_to_lists(obj: Any): | ||
amoghrajesh marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| """Recursively convert tuples to lists.""" | ||
| if isinstance(obj, tuple): | ||
| return [translate_tuples_to_lists(item) for item in obj] | ||
| if isinstance(obj, list): | ||
| return [translate_tuples_to_lists(item) for item in obj] | ||
amoghrajesh marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if isinstance(obj, dict): | ||
| return {key: translate_tuples_to_lists(value) for key, value in obj.items()} | ||
| return obj | ||
| def sort_dict_recursively(obj: Any) -> Any: | ||
| """Recursively sort dictionaries to ensure consistent ordering.""" | ||
| if isinstance(obj, dict): | ||
| return {k: sort_dict_recursively(v) for k, v in sorted(obj.items())} | ||
| if isinstance(obj, list): | ||
| return [sort_dict_recursively(item) for item in obj] | ||
| if isinstance(obj, tuple): | ||
| return tuple(sort_dict_recursively(item) for item in obj) | ||
| return obj | ||
| max_length = conf.getint("core", "max_templated_field_length") | ||
| if not is_jsonable(template_field): | ||
| try: | ||
| serialized = template_field.serialize() | ||
| except AttributeError: | ||
| serialized = str(template_field) | ||
| if len(serialized) > max_length: | ||
| rendered = redact(serialized, name) | ||
| return ( | ||
| "Truncated. You can change this behaviour in [core]max_templated_field_length. " | ||
| f"{rendered[: max_length - 79]!r}... " | ||
| ) | ||
| return serialized | ||
| if not template_field and not isinstance(template_field, tuple): | ||
amoghrajesh marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| # Avoid unnecessary serialization steps for empty fields unless they are tuples | ||
| # and need to be converted to lists | ||
| return template_field | ||
| template_field = translate_tuples_to_lists(template_field) | ||
| # Sort dictionaries recursively to ensure consistent string representation | ||
| # This prevents hash inconsistencies when dict ordering varies | ||
| if isinstance(template_field, dict): | ||
| template_field = sort_dict_recursively(template_field) | ||
| serialized = str(template_field) | ||
| if len(serialized) > max_length: | ||
| rendered = redact(serialized, name) | ||
| return ( | ||
| "Truncated. You can change this behaviour in [core]max_templated_field_length. " | ||
| f"{rendered[: max_length - 79]!r}... " | ||
| ) | ||
| return template_field | ||
| def _serialize_rendered_fields(task: AbstractOperator) -> dict[str, JsonValue]: | ||
| # TODO: Port one of the following to Task SDK | ||
| # airflow.serialization.helpers.serialize_template_field or | ||
| # airflow.models.renderedtifields.get_serialized_template_fields | ||
| from airflow.sdk._shared.secrets_masker import redact | ||
| from airflow.serialization.helpers import serialize_template_field | ||
| rendered_fields = {} | ||
| for field in task.template_fields: | ||
| value = getattr(task, field) | ||
| serialized = serialize_template_field(value, field) | ||
| serialized = _serialize_template_field(value, field) | ||
| # Redact secrets in the task process itself before sending to API server | ||
| # This ensures that the secrets those are registered via mask_secret() on workers / dag processor are properly masked | ||
| # on the UI. | ||
Uh oh!
There was an error while loading. Please reload this page.