diff --git a/docker/keepalived/Dockerfile.j2 b/docker/keepalived/Dockerfile.j2 index 006ac9d85e..73219912f9 100644 --- a/docker/keepalived/Dockerfile.j2 +++ b/docker/keepalived/Dockerfile.j2 @@ -7,13 +7,17 @@ LABEL maintainer="{{ maintainer }}" name="{{ image_name }}" build-date="{{ build {% import "macros.j2" as macros with context %} +{{ macros.enable_extra_repos(['mariadb']) }} + {% if base_package_type == 'rpm' %} {% set keepalived_packages = [ - 'keepalived' + 'keepalived', + 'MariaDB-client' ] %} {% elif base_package_type == 'deb' %} {% set keepalived_packages = [ - 'keepalived' + 'keepalived', + 'mariadb-client' ] %} {% endif %} {{ macros.install_packages(keepalived_packages | customizable("packages")) }} diff --git a/kolla/image/kolla_worker.py b/kolla/image/kolla_worker.py index 680595f866..b60f0558f9 100644 --- a/kolla/image/kolla_worker.py +++ b/kolla/image/kolla_worker.py @@ -23,6 +23,7 @@ import time import jinja2 +import jinja2.sandbox from kolla.common import config as common_config from kolla.common import utils from kolla.engine_adapter import engine @@ -392,7 +393,7 @@ def create_dockerfiles(self): 'build_date': build_date, 'clean_package_cache': self.clean_package_cache, 'patches_path': self.patches_path} - env = jinja2.Environment( # nosec: not used to render HTML + env = jinja2.sandbox.SandboxedEnvironment( loader=jinja2.FileSystemLoader(self.working_dir)) env.filters.update(self._get_filters()) env.globals.update(self._get_methods()) @@ -405,7 +406,7 @@ def create_dockerfiles(self): tpl_dict = self._merge_overrides(self.conf.template_override) template_name = os.path.basename(list(tpl_dict.keys())[0]) values['parent_template'] = template - env = jinja2.Environment( # nosec: not used to render HTML + env = jinja2.sandbox.SandboxedEnvironment( loader=jinja2.DictLoader(tpl_dict)) env.filters.update(self._get_filters()) env.globals.update(self._get_methods()) diff --git a/kolla/tests/test_build.py b/kolla/tests/test_build.py index deb2dfa103..82bcb7d6d5 100644 --- a/kolla/tests/test_build.py +++ b/kolla/tests/test_build.py @@ -11,6 +11,7 @@ # limitations under the License. import fixtures +import jinja2 import os import requests import sys @@ -579,6 +580,72 @@ def test_set_time(self): kolla.setup_working_dir() kolla.set_time() + @mock.patch.dict(os.environ, {'http_proxy': 'http://test-proxy:8080'}, + clear=False) + @mock.patch(engine_client) + def test_create_dockerfiles_env_http_proxy(self, mock_client): + tmpdir = self.useFixture(fixtures.TempDir()).path + base_dir = os.path.join(tmpdir, 'base') + os.makedirs(base_dir) + with open(os.path.join(base_dir, 'Dockerfile.j2'), 'w') as f: + f.write( + '{% if env.http_proxy %}' + 'ARG http_proxy={{ env.http_proxy }}' + '{% endif %}\n' + ) + with mock.patch.object(build.KollaWorker, '_get_images_dir', + return_value=tmpdir): + kolla = build.KollaWorker(self.conf) + kolla.setup_working_dir() + kolla.find_dockerfiles() + kolla.create_dockerfiles() + dockerfile = os.path.join(kolla.working_dir, 'base', 'Dockerfile') + with open(dockerfile) as f: + content = f.read() + self.assertIn('http://test-proxy:8080', content) + + @mock.patch(engine_client) + def test_create_dockerfiles_ssti_blocked(self, mock_client): + tmpdir = self.useFixture(fixtures.TempDir()).path + base_dir = os.path.join(tmpdir, 'base') + os.makedirs(base_dir) + with open(os.path.join(base_dir, 'Dockerfile.j2'), 'w') as f: + f.write( + "{{ self.__init__.__globals__['__builtins__']" + "['__import__']('os').popen('id').read() }}\n" + ) + with mock.patch.object(build.KollaWorker, '_get_images_dir', + return_value=tmpdir): + kolla = build.KollaWorker(self.conf) + kolla.setup_working_dir() + kolla.find_dockerfiles() + self.assertRaises(jinja2.exceptions.SecurityError, + kolla.create_dockerfiles) + + @mock.patch(engine_client) + def test_create_dockerfiles_template_override_ssti_blocked( + self, mock_client): + tmpdir = self.useFixture(fixtures.TempDir()).path + base_dir = os.path.join(tmpdir, 'base') + os.makedirs(base_dir) + with open(os.path.join(base_dir, 'Dockerfile.j2'), 'w') as f: + f.write('FROM {{ base_distro }}:{{ base_distro_tag }}\n') + override_file = os.path.join( + self.useFixture(fixtures.TempDir()).path, 'template_override.j2') + with open(override_file, 'w') as f: + f.write( + "{{ self.__init__.__globals__['__builtins__']" + "['__import__']('os').popen('id').read() }}\n" + ) + self.conf.set_override('template_override', [override_file]) + with mock.patch.object(build.KollaWorker, '_get_images_dir', + return_value=tmpdir): + kolla = build.KollaWorker(self.conf) + kolla.setup_working_dir() + kolla.find_dockerfiles() + self.assertRaises(jinja2.exceptions.SecurityError, + kolla.create_dockerfiles) + def _get_matched_images(self, images): return [image for image in images if image.status == utils.Status.MATCHED] diff --git a/kolla/tests/test_validate_all_file.py b/kolla/tests/test_validate_all_file.py new file mode 100644 index 0000000000..04dab530ee --- /dev/null +++ b/kolla/tests/test_validate_all_file.py @@ -0,0 +1,43 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +import os +import tempfile +from unittest import mock + +from kolla.tests import base + +_VALIDATE_SCRIPT = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', 'tools', + 'validate-all-file.py')) +_spec = importlib.util.spec_from_file_location('validate_all_file', + _VALIDATE_SCRIPT) +validate_all_file = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(validate_all_file) + +_SSTI_PAYLOAD = ( + "{{ self.__init__.__globals__['__builtins__']" + "['__import__']('os').popen('id').read() }}" +) + + +class ValidateAllFileTest(base.TestCase): + + def test_check_json_j2_ssti_blocked(self): + with tempfile.TemporaryDirectory() as tmpdir: + malicious = os.path.join(tmpdir, 'test.json.j2') + with open(malicious, 'w') as f: + f.write(_SSTI_PAYLOAD + '\n') + with mock.patch.object(validate_all_file, 'PROJECT_ROOT', tmpdir): + result = validate_all_file.check_json_j2() + self.assertEqual(1, result) diff --git a/tools/validate-all-file.py b/tools/validate-all-file.py index 67f03218ad..12a50a70c9 100755 --- a/tools/validate-all-file.py +++ b/tools/validate-all-file.py @@ -21,6 +21,7 @@ import sys import jinja2 +import jinja2.sandbox PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) @@ -75,7 +76,7 @@ def hostvars(): return collections.defaultdict(hostvars) def validate_json_j2(root, filename): - env = jinja2.Environment( # nosec: not used to render HTML + env = jinja2.sandbox.SandboxedEnvironment( loader=jinja2.FileSystemLoader(root)) env.filters['bool'] = bool_filter template = env.get_template(filename) diff --git a/zuul.d/scenarios/aio.yaml b/zuul.d/scenarios/aio.yaml index cc896b84df..f7ed01d31d 100644 --- a/zuul.d/scenarios/aio.yaml +++ b/zuul.d/scenarios/aio.yaml @@ -9,7 +9,7 @@ files: ^docker/ - kolla-ansible-debian-trixie: files: ^docker/ - - kolla-ansible-debian-bookworm-upgrade: + - kolla-ansible-debian-trixie-upgrade: files: ^docker/ - kolla-ansible-rocky-10: files: ^docker/ @@ -29,7 +29,7 @@ jobs: - kolla-ansible-debian-trixie: files: ^docker/ - - kolla-ansible-debian-bookworm-upgrade: + - kolla-ansible-debian-trixie-upgrade: files: ^docker/ - kolla-ansible-rocky-10: files: ^docker/