import Oracle_OSS ansible-core-2.14.18-3.el9_8.1

This commit is contained in:
AlmaLinux RelEng Bot 2026-08-20 02:42:21 -04:00
parent 9197e34292
commit 768cdbdcc0
4 changed files with 408 additions and 1 deletions

View 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
View 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
View 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()

View File

@ -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