Added _TEMPLAR_SANDBOX_MODE config (#85222)

* Added _TEMPLAR_SANDBOX_MODE config

* allows unsafe attribute checks to be disabled in Jinja sandbox

* Update lib/ansible/_internal/_templating/_jinja_bits.py

Co-authored-by: Matt Clay <matt@mystile.com>

---------

Co-authored-by: Matt Clay <matt@mystile.com>
(cherry picked from commit 91453e30af)
pull/85255/head
Matt Davis 6 months ago committed by Matt Davis
parent 8b0b54de38
commit 5fd78b07fb

@ -0,0 +1,2 @@
minor_changes:
- templating - Added ``_ANSIBLE_TEMPLAR_SANDBOX_MODE=allow_unsafe_attributes`` environment variable to disable Jinja template attribute sandbox. (https://github.com/ansible/ansible/issues/85202)

@ -49,6 +49,7 @@ from ._jinja_common import (
TruncationMarker,
validate_arg_type,
JinjaCallContext,
_SandboxMode,
)
from ._jinja_plugins import JinjaPluginIntercept, _query, _lookup, _now, _wrap_plugin_output, get_first_marker_arg, _DirectCall, _jinja_const_template_warning
from ._lazy_containers import (
@ -588,6 +589,13 @@ class AnsibleEnvironment(ImmutableSandboxedEnvironment):
return template_obj
def is_safe_attribute(self, obj: t.Any, attr: str, value: t.Any) -> bool:
# deprecated: description="remove relaxed template sandbox mode support" core_version="2.23"
if _TemplateConfig.sandbox_mode == _SandboxMode.ALLOW_UNSAFE_ATTRIBUTES:
return True
return super().is_safe_attribute(obj, attr, value)
@property
def lexer(self) -> AnsibleLexer:
"""Return/cache an AnsibleLexer with settings from the current AnsibleEnvironment"""

@ -2,6 +2,7 @@ from __future__ import annotations
import abc
import collections.abc as c
import enum
import inspect
import itertools
import typing as t
@ -24,10 +25,16 @@ from ...module_utils.datatag import native_type_name
_patch_jinja() # apply Jinja2 patches before types are declared that are dependent on the changes
class _SandboxMode(enum.Enum):
DEFAULT = enum.auto()
ALLOW_UNSAFE_ATTRIBUTES = enum.auto()
class _TemplateConfig:
allow_embedded_templates: bool = config.get_config_value("ALLOW_EMBEDDED_TEMPLATES")
allow_broken_conditionals: bool = config.get_config_value('ALLOW_BROKEN_CONDITIONALS')
jinja_extensions: list[str] = config.get_config_value('DEFAULT_JINJA2_EXTENSIONS')
sandbox_mode: _SandboxMode = _SandboxMode.__members__[config.get_config_value('_TEMPLAR_SANDBOX_MODE').upper()]
unknown_type_encountered_handler = ErrorHandler.from_config('_TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED')
unknown_type_conversion_handler = ErrorHandler.from_config('_TEMPLAR_UNKNOWN_TYPE_CONVERSION')

@ -2036,6 +2036,19 @@ TASK_TIMEOUT:
- {key: task_timeout, section: defaults}
type: integer
version_added: '2.10'
_TEMPLAR_SANDBOX_MODE:
name: Control Jinja template sandbox behavior
default: default
description:
- The default Jinja sandbox behavior blocks template access to all `_` prefixed object attributes and known collection mutation methods (e.g., `dict.clear()`, `list.append()`).
type: choices
choices:
- default
- allow_unsafe_attributes
env: [{name: _ANSIBLE_TEMPLAR_SANDBOX_MODE}]
deprecated:
why: controlling sandbox behavior is a temporary workaround
version: '2.23'
_TEMPLAR_UNKNOWN_TYPE_CONVERSION:
name: Templar unknown type conversion behavior
default: warning

@ -43,7 +43,7 @@ from ansible._internal._templating import _transform
from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionFinder
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
from ansible.plugins.loader import init_plugin_loader
from ansible._internal._templating._jinja_common import _TemplateConfig
from ansible._internal._templating._jinja_common import _TemplateConfig, _SandboxMode
from ansible._internal._templating._jinja_plugins import _lookup
from ansible._internal._templating import _jinja_plugins
from ansible._internal._templating._engine import TemplateEngine, TemplateOptions
@ -1058,3 +1058,19 @@ def test_jinja_const_template_finalized() -> None:
with _DeferredWarningContext(variables={}): # suppress warning from usage of embedded template
with unittest.mock.patch.object(_TemplateConfig, 'allow_embedded_templates', True):
assert not _JinjaConstTemplate.is_tagged_on(TemplateEngine().template(TRUST.tag("{{ '{{ 1 }}' }}")))
@pytest.mark.parametrize("template,expected", (
("{% set x=[] %}{% set _=x.append(42) %}{{ x }}", [42]),
("{{ (32).__or__(64) }}", 96),
("{% set x={'foo': 42} %}{% set _=x.clear() %}{{ x }}", {}),
))
def test_unsafe_attr_access(template: str, expected: object) -> None:
"""Verify that unsafe attribute access fails by default and works when explicitly configured."""
assert _TemplateConfig.sandbox_mode == _SandboxMode.DEFAULT
with pytest.raises(AnsibleUndefinedVariable):
TemplateEngine().template(TRUST.tag(template))
with unittest.mock.patch.object(_TemplateConfig, 'sandbox_mode', _SandboxMode.ALLOW_UNSAFE_ATTRIBUTES):
assert TemplateEngine().template(TRUST.tag(template)) == expected

Loading…
Cancel
Save