import Oracle_OSS ansible-core-2.14.18-3.el9_8.1
This commit is contained in:
parent
9197e34292
commit
768cdbdcc0
96
SOURCES/ansible-core-2.14.18-CVE-2026-11332.patch
Normal file
96
SOURCES/ansible-core-2.14.18-CVE-2026-11332.patch
Normal file
@ -0,0 +1,96 @@
|
||||
From b4ddf456ecbe8256881585e4e668f27d634532fd Mon Sep 17 00:00:00 2001
|
||||
From: Sloane Hertel <19572925+s-hertel@users.noreply.github.com>
|
||||
Date: Fri, 5 Jun 2026 18:38:56 -0400
|
||||
Subject: [PATCH] Fix CVE-2026-11332 - prevent role requirements from
|
||||
configuring git (#87070)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
* Pass malformed role requirements as positional arguments to prevent arbitrary git configuration
|
||||
|
||||
* Add test coverage, checking for specific errors and that git clone is always followed by --
|
||||
|
||||
Co-authored-by: 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua>
|
||||
---
|
||||
lib/ansible/utils/galaxy.py | 2 +-
|
||||
.../tasks/git-config-injection.yml | 47 +++++++++++++++++++
|
||||
.../ansible-galaxy-role/tasks/main.yml | 1 +
|
||||
3 files changed, 49 insertions(+), 1 deletion(-)
|
||||
create mode 100644 test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml
|
||||
|
||||
diff --git a/lib/ansible/utils/galaxy.py b/lib/ansible/utils/galaxy.py
|
||||
index bbb26fb111..b463ab0f08 100644
|
||||
--- a/lib/ansible/utils/galaxy.py
|
||||
+++ b/lib/ansible/utils/galaxy.py
|
||||
@@ -74,7 +74,7 @@ def scm_archive_resource(src, scm='git', name=None, version='HEAD', keep_scm_met
|
||||
elif scm == 'hg':
|
||||
clone_cmd.append('--insecure')
|
||||
|
||||
- clone_cmd.extend([src, name])
|
||||
+ clone_cmd.extend(['--', src, name])
|
||||
|
||||
run_scm_cmd(clone_cmd, tempdir)
|
||||
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml b/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml
|
||||
new file mode 100644
|
||||
index 0000000000..32ed15c3e6
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml
|
||||
@@ -0,0 +1,47 @@
|
||||
+- vars:
|
||||
+ invalid_git_opts: '-ccore.sshCommand=sh -c "id > {{ remote_tmp_dir }}/role_exe"'
|
||||
+ # use SSH protocol to test core.sshCommand is not configured
|
||||
+ dummy_repo: git@github.com:ansible/nosuchrepo.git
|
||||
+ block:
|
||||
+ - name: Ensure git is installed
|
||||
+ package:
|
||||
+ name: git
|
||||
+ when: ansible_distribution not in ["MacOSX", "Alpine"]
|
||||
+ register: git_install
|
||||
+
|
||||
+ - name: Create invalid requirements file
|
||||
+ copy:
|
||||
+ dest: "{{ remote_tmp_dir }}/invalid-requirements.yml"
|
||||
+ content: |
|
||||
+ - src: {{ invalid_git_opts }}
|
||||
+ scm: git
|
||||
+ name: {{ dummy_repo }}
|
||||
+ - src: {{ dummy_repo }}
|
||||
+ scm: git
|
||||
+ name: {{ invalid_git_opts }}
|
||||
+
|
||||
+ - name: Attempt to install invalid role requirements
|
||||
+ command: ansible-galaxy install -r {{ remote_tmp_dir }}/invalid-requirements.yml --ignore-errors
|
||||
+ register: result
|
||||
+
|
||||
+ - name: Validate git core.sshCommand did not run
|
||||
+ stat:
|
||||
+ path: "{{ remote_tmp_dir }}/role_exe"
|
||||
+ failed_when: _task.result.stat.exists
|
||||
+
|
||||
+ - name: Verify the invalid field is treated as a single positional argument (repo or dest)
|
||||
+ assert:
|
||||
+ that:
|
||||
+ - result.stderr is search(error1)
|
||||
+ - result.stderr is search(error2)
|
||||
+ - (result.stderr | regex_findall("git clone") | length) == (result.stderr | regex_findall("git clone --") | length) == 2
|
||||
+ vars:
|
||||
+ error1: "repository '{{ invalid_git_opts }}' does not exist"
|
||||
+ error2: "Cloning into '{{ invalid_git_opts }}'"
|
||||
+
|
||||
+ always:
|
||||
+ - name: Uninstall git if it was installed
|
||||
+ package:
|
||||
+ name: git
|
||||
+ state: absent
|
||||
+ when: git_install is changed | default(false)
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/tasks/main.yml b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
index 5f88a55765..f41273b488 100644
|
||||
--- a/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
@@ -70,3 +70,4 @@
|
||||
|
||||
- import_tasks: dir-traversal.yml
|
||||
- import_tasks: valid-role-symlinks.yml
|
||||
+- import_tasks: git-config-injection.yml
|
||||
13
SOURCES/telemetry.patch
Normal file
13
SOURCES/telemetry.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py
|
||||
index 2e8a77e398..fcd941e14e 100644
|
||||
--- a/lib/ansible/plugins/loader.py
|
||||
+++ b/lib/ansible/plugins/loader.py
|
||||
@@ -1483,7 +1483,7 @@ cache_loader = PluginLoader(
|
||||
callback_loader = PluginLoader(
|
||||
'CallbackModule',
|
||||
'ansible.plugins.callback',
|
||||
- C.DEFAULT_CALLBACK_PLUGIN_PATH,
|
||||
+ C.DEFAULT_CALLBACK_PLUGIN_PATH + ['/usr/share/ansible/telemetry'],
|
||||
'callback_plugins',
|
||||
)
|
||||
|
||||
268
SOURCES/telemetry.py
Executable file
268
SOURCES/telemetry.py
Executable file
@ -0,0 +1,268 @@
|
||||
#!/usr/bin/python3
|
||||
# Copyright: Contributors to the Ansible project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: telemetry
|
||||
short_description: Track various telemetry related data and store in journald
|
||||
description:
|
||||
- Track various telemetry related data and store in journald
|
||||
type: aggregate
|
||||
options:
|
||||
bypass_insights_check:
|
||||
name: Bypass Insights Check
|
||||
description: Bypass check to see if insights-client is active
|
||||
default: false
|
||||
type: bool
|
||||
env:
|
||||
- name: TELEMETRY_BYPASS_INSIGHTS_CHECK
|
||||
notes:
|
||||
- The file can also be executed directly from the CLI to retrieve
|
||||
the stored telemetry data from journald
|
||||
'''
|
||||
|
||||
import contextlib
|
||||
import ctypes
|
||||
import datetime
|
||||
import functools
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
||||
if __name__ == '__main__':
|
||||
# When run as a script via insights-client, these are not needed
|
||||
CallbackBase = object
|
||||
Display = None
|
||||
else:
|
||||
from ansible.executor.task_result import TaskResult
|
||||
from ansible.module_utils.common.yaml import yaml_load
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.plugins.loader import connection_loader
|
||||
from ansible.release import __version__ as ansible_version
|
||||
from ansible.utils.display import Display
|
||||
|
||||
journal_sendv = None
|
||||
try:
|
||||
from systemd import journal
|
||||
journal_sendv = journal.sendv
|
||||
except ImportError:
|
||||
# When invoked using the non-system python
|
||||
# the systemd python bindings are not available
|
||||
with contextlib.suppress(AttributeError, OSError):
|
||||
_libsysetmd = ctypes.CDLL('libsystemd.so.0')
|
||||
sd_journal_send = _libsysetmd.sd_journal_send
|
||||
|
||||
def journal_sendv(*args):
|
||||
b_args = [ctypes.c_char_p(a.encode()) for a in args] + [None]
|
||||
sd_journal_send(*b_args)
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from importlib.resources import files
|
||||
else:
|
||||
# importlib.resources below Python 3.10 doesn't support
|
||||
# custom loaders, this is not a full featured replacement
|
||||
# but suffices for the needs of this plugin
|
||||
def files(name):
|
||||
spec = importlib.util.find_spec(name)
|
||||
if spec is None:
|
||||
raise ImportError(name)
|
||||
origin = pathlib.Path(spec.origin)
|
||||
if origin.name == '__synthetic__':
|
||||
origin = origin.parent
|
||||
return origin
|
||||
|
||||
if Display:
|
||||
display = Display()
|
||||
|
||||
_CORE_SYN_COLLECTIONS = frozenset((
|
||||
'ansible.builtin',
|
||||
'ansible.legacy',
|
||||
))
|
||||
|
||||
_ANSIBLE_TELEMETRY_MESSAGE_ID = uuid.UUID('73df9e03d8c6473eb811065c4ebc370a')
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _get_connection_fqcn(task, host):
|
||||
connection = host.vars.get('ansible_connection')
|
||||
if not connection:
|
||||
connection = task.connection
|
||||
is_connection_fqcn = '.' in connection
|
||||
if is_connection_fqcn:
|
||||
return connection
|
||||
|
||||
return connection_loader.get_with_context(
|
||||
connection,
|
||||
class_only=True,
|
||||
collection_list=task.collections,
|
||||
).plugin_load_context.resolved_fqcn
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'telemetry'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.wants_implicit_tasks = False
|
||||
self.disabled = False
|
||||
|
||||
self._telemetry = {
|
||||
'collections': defaultdict(
|
||||
lambda: {
|
||||
'resources': defaultdict(
|
||||
lambda: defaultdict(lambda: {
|
||||
'count': 0,
|
||||
})
|
||||
),
|
||||
'version': '*',
|
||||
},
|
||||
),
|
||||
}
|
||||
self._hosts = set()
|
||||
self._conn_cache = {}
|
||||
|
||||
def _is_insights_client_active(self):
|
||||
if self.get_option('bypass_insights_check'):
|
||||
return True
|
||||
|
||||
if not os.path.exists('/etc/insights-client/.registered'):
|
||||
return False
|
||||
|
||||
with contextlib.suppress(subprocess.CalledProcessError):
|
||||
subprocess.run(
|
||||
['systemctl', 'is-active', '--quiet', 'insights-client.timer'],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _set_disabled(self):
|
||||
if not self._is_insights_client_active():
|
||||
return True
|
||||
|
||||
if journal_sendv is None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
self.disabled = self._set_disabled()
|
||||
|
||||
def _handle_result(self, *args, **kwargs):
|
||||
if not args:
|
||||
return
|
||||
|
||||
result = args[0]
|
||||
if not isinstance(result, TaskResult):
|
||||
return
|
||||
|
||||
host = result._host
|
||||
task = result._task
|
||||
role = task._role
|
||||
|
||||
self._hosts.add(host.get_name())
|
||||
|
||||
action = task.resolved_action
|
||||
action_collection = None
|
||||
if action and '.' in action:
|
||||
action_collection = '.'.join(action.split('.', 2)[:2])
|
||||
|
||||
role_collection = None
|
||||
role_name = None if not role else role.get_name()
|
||||
if role and '.' in role_name:
|
||||
role_collection = '.'.join(role_name.split('.', 2)[:2])
|
||||
|
||||
connection_collection = None
|
||||
connection = _get_connection_fqcn(task, host)
|
||||
if '.' in connection:
|
||||
connection_collection = '.'.join(connection.split('.')[:2])
|
||||
|
||||
collections = self._telemetry['collections']
|
||||
for resource, collection, name in (('action', action_collection, action),
|
||||
('role', role_collection, role_name),
|
||||
('connection', connection_collection, connection)):
|
||||
if not collection:
|
||||
continue
|
||||
|
||||
collections[collection]['resources'][resource][name]['count'] += 1
|
||||
|
||||
# v2_on_file_diff = _handle_result
|
||||
v2_runner_item_on_failed = _handle_result
|
||||
v2_runner_item_on_ok = _handle_result
|
||||
v2_runner_item_on_skipped = _handle_result
|
||||
v2_runner_on_async_failed = _handle_result
|
||||
v2_runner_on_async_ok = _handle_result
|
||||
v2_runner_on_async_poll = _handle_result
|
||||
v2_runner_on_failed = _handle_result
|
||||
v2_runner_on_ok = _handle_result
|
||||
v2_runner_on_skipped = _handle_result
|
||||
v2_runner_on_unreachable = _handle_result
|
||||
v2_runner_retry = _handle_result
|
||||
|
||||
def v2_playbook_on_stats(self, stats):
|
||||
self._telemetry['ansible_core'] = {
|
||||
'version': ansible_version,
|
||||
}
|
||||
self._telemetry['hosts'] = {
|
||||
'count': len(self._hosts),
|
||||
}
|
||||
|
||||
for collection in self._telemetry['collections']:
|
||||
if collection in _CORE_SYN_COLLECTIONS:
|
||||
continue
|
||||
cpath = files('ansible_collections.%s' % collection)
|
||||
for candidate in ('METADATA.json', 'galaxy.yml'):
|
||||
mdfile = cpath.joinpath(candidate)
|
||||
if mdfile.exists():
|
||||
self._telemetry['collections'][collection]['version'] = yaml_load(
|
||||
mdfile.read_text()
|
||||
).get('version', '*')
|
||||
break
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self._write_journal()
|
||||
|
||||
def _write_journal(self):
|
||||
journal_sendv(
|
||||
'MESSAGE=%s' % json.dumps(self._telemetry, separators=(',', ':')),
|
||||
'MESSAGE_ID=%s' % _ANSIBLE_TELEMETRY_MESSAGE_ID.hex,
|
||||
'ANSIBLE_TELEMETRY=core',
|
||||
'ANSIBLE_TELEMETRY_ID=%s' % uuid.uuid4().hex,
|
||||
)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _get_journal_reader(seek=None):
|
||||
with journal.Reader() as j:
|
||||
if seek:
|
||||
j.seek_realtime(seek)
|
||||
j.add_match('MESSAGE_ID=%s' % _ANSIBLE_TELEMETRY_MESSAGE_ID.hex)
|
||||
j.add_match('ANSIBLE_TELEMETRY=core')
|
||||
yield j
|
||||
|
||||
|
||||
def main():
|
||||
lastupload_file = pathlib.Path('/etc/insights-client/.lastupload')
|
||||
lastupload = None
|
||||
with contextlib.suppress(ValueError, OSError):
|
||||
lastupload = datetime.datetime.strptime(
|
||||
lastupload_file.read_text(),
|
||||
'%Y-%m-%dT%H:%M:%S.%f'
|
||||
)
|
||||
|
||||
with _get_journal_reader(lastupload) as j:
|
||||
for entry in j:
|
||||
print(entry['MESSAGE'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -29,7 +29,7 @@ Name: ansible-core
|
||||
Summary: SSH-based configuration management, deployment, and task execution system
|
||||
Epoch: 1
|
||||
Version: 2.14.18
|
||||
Release: 1%{?dist}
|
||||
Release: 3%{?dist}.1
|
||||
|
||||
Group: Development/Libraries
|
||||
License: GPLv3+
|
||||
@ -43,6 +43,14 @@ Source4: https://files.pythonhosted.org/packages/source/M/MarkupSafe/MarkupSafe-
|
||||
|
||||
Patch0: remove-bundled-deps-from-requirements.patch
|
||||
|
||||
# https://github.com/ansible/ansible/commit/edee59aa15abcc74d920bb3e9c3835ab8db05a2f
|
||||
Patch1: ansible-core-2.14.18-CVE-2026-11332.patch
|
||||
|
||||
%if 0%{!?centos:1} && 0%{?rhel}
|
||||
Source99: telemetry.py
|
||||
Patch99: telemetry.patch
|
||||
%endif
|
||||
|
||||
URL: http://ansible.com
|
||||
|
||||
# We obsolete old ansible, and any version of ansible-base.
|
||||
@ -104,10 +112,16 @@ developed for ansible.
|
||||
%prep
|
||||
%setup -q -b1 -b3 -b4 -n ansible_core-%{version}
|
||||
%patch0 -p1
|
||||
%patch1 -p1
|
||||
|
||||
# Fix all Python shebangs recursively in ansible-test
|
||||
%{py3_shebang_fix} test/lib/ansible_test
|
||||
|
||||
%if 0%{!?centos:1} && 0%{?rhel}
|
||||
%patch99 -p1
|
||||
%{py3_shebang_fix} %{SOURCE99}
|
||||
%endif
|
||||
|
||||
%build
|
||||
%{py3_build}
|
||||
|
||||
@ -159,6 +173,12 @@ mkdir -p %{buildroot}%{_sysconfdir}/ansible/roles/
|
||||
cp ../ansible-documentation-%{doc_version}/examples/hosts %{buildroot}%{_sysconfdir}/ansible/
|
||||
cp ../ansible-documentation-%{doc_version}/examples/ansible.cfg %{buildroot}%{_sysconfdir}/ansible/
|
||||
|
||||
%if 0%{!?centos:1} && 0%{?rhel}
|
||||
mkdir -p %{buildroot}%{_datadir}/ansible/telemetry
|
||||
cp %{SOURCE99} %{buildroot}%{_datadir}/ansible/telemetry/
|
||||
%py_byte_compile %{__python3} %{buildroot}%{_datadir}/ansible/telemetry/telemetry.py
|
||||
%endif
|
||||
|
||||
mkdir -p %{buildroot}/%{_mandir}/man1/
|
||||
|
||||
mkdir -p docs/man/man1
|
||||
@ -191,6 +211,16 @@ strip --strip-unneeded %{vendor_path}/markupsafe/_speedups%{python3_ext_suffix}
|
||||
|
||||
|
||||
%changelog
|
||||
* Sat Jul 11 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 1:2.14.18-3.1
|
||||
- Fix CVE-2026-11332 (prevent arbitrary git configuration via role
|
||||
requirements) (RHEL-194134)
|
||||
|
||||
* Tue Feb 10 2026 Dimitri Savineau <dsavinea@redhat.com> - 1:2.14.18-3
|
||||
- Fix selinux AVC denial when telemetry is enabled (RHEL-148293)
|
||||
|
||||
* Thu Oct 30 2025 Dimitri Savineau <dsavinea@redhat.com> - 1:2.14.18-2
|
||||
- Add telemetry for RHEL (RHEL-123003)
|
||||
|
||||
* Fri Jan 03 2025 Dimitri Savineau <dsavinea@redhat.com> - 1:2.14.18-1
|
||||
- ansible-core 2.14.18 release (RHEL-69086)
|
||||
- Fix license file path
|
||||
|
||||
Loading…
Reference in New Issue
Block a user