Compare commits
10 Commits
28e9842d1f
...
0f24a2a19d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f24a2a19d | ||
|
|
013a638b77 | ||
|
|
6c06026883 | ||
|
|
f8f97e411f | ||
|
|
41b73e864d | ||
|
|
cf75e9fa40 | ||
|
|
fe7ba89ed2 | ||
|
|
662529c2af | ||
|
|
513f66b5b8 | ||
|
|
78519b78ea |
150
CVE-2019-3828.patch
Normal file
150
CVE-2019-3828.patch
Normal file
@ -0,0 +1,150 @@
|
||||
From f3edc091523fbe301926b7a0db25fbbd96940d93 Mon Sep 17 00:00:00 2001
|
||||
From: Matt Martz <matt@sivel.net>
|
||||
Date: Mon, 18 Feb 2019 10:45:05 -0600
|
||||
Subject: [PATCH] [stable-2.5] Disallow use of remote home directories
|
||||
containing .. in their path (CVE-2019-3828) (#52133) (#52175)
|
||||
|
||||
* Disallow use of remote home directories containing .. in their path
|
||||
|
||||
* Add CVE to changelog
|
||||
(cherry picked from commit b34d141)
|
||||
|
||||
Co-authored-by: Matt Martz <matt@sivel.net>
|
||||
---
|
||||
.../fragments/disallow-relative-homedir.yaml | 3 +
|
||||
lib/ansible/plugins/action/__init__.py | 3 +
|
||||
test/units/plugins/action/test_action.py | 61 ++++++++++++-------
|
||||
3 files changed, 44 insertions(+), 23 deletions(-)
|
||||
create mode 100644 changelogs/fragments/disallow-relative-homedir.yaml
|
||||
|
||||
diff --git a/changelogs/fragments/disallow-relative-homedir.yaml b/changelogs/fragments/disallow-relative-homedir.yaml
|
||||
new file mode 100644
|
||||
index 00000000000000..0ae36ef94d27fd
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/disallow-relative-homedir.yaml
|
||||
@@ -0,0 +1,3 @@
|
||||
+bugfixes:
|
||||
+- remote home directory - Disallow use of remote home directories that include
|
||||
+ relative pathing by means of `..` (CVE-2019-3828) (https://github.com/ansible/ansible/pull/52133)
|
||||
diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py
|
||||
index ba8f32e52983f8..23d95ee444bc23 100644
|
||||
--- a/lib/ansible/plugins/action/__init__.py
|
||||
+++ b/lib/ansible/plugins/action/__init__.py
|
||||
@@ -609,6 +609,9 @@ def _remote_expand_user(self, path, sudoable=True, pathsep=None):
|
||||
else:
|
||||
expanded = initial_fragment
|
||||
|
||||
+ if '..' in os.path.dirname(expanded).split('/'):
|
||||
+ raise AnsibleError("'%s' returned an invalid relative home directory path containing '..'" % self._play_context.remote_addr)
|
||||
+
|
||||
return expanded
|
||||
|
||||
def _strip_success_message(self, data):
|
||||
diff --git a/test/units/plugins/action/test_action.py b/test/units/plugins/action/test_action.py
|
||||
index 52fa3bea1bece4..5221e54ed4fafb 100644
|
||||
--- a/test/units/plugins/action/test_action.py
|
||||
+++ b/test/units/plugins/action/test_action.py
|
||||
@@ -56,6 +56,28 @@
|
||||
"""
|
||||
|
||||
|
||||
+def _action_base():
|
||||
+ fake_loader = DictDataLoader({
|
||||
+ })
|
||||
+ mock_module_loader = MagicMock()
|
||||
+ mock_shared_loader_obj = MagicMock()
|
||||
+ mock_shared_loader_obj.module_loader = mock_module_loader
|
||||
+ mock_connection_loader = MagicMock()
|
||||
+
|
||||
+ mock_shared_loader_obj.connection_loader = mock_connection_loader
|
||||
+ mock_connection = MagicMock()
|
||||
+
|
||||
+ play_context = MagicMock()
|
||||
+
|
||||
+ action_base = DerivedActionBase(task=None,
|
||||
+ connection=mock_connection,
|
||||
+ play_context=play_context,
|
||||
+ loader=fake_loader,
|
||||
+ templar=None,
|
||||
+ shared_loader_obj=mock_shared_loader_obj)
|
||||
+ return action_base
|
||||
+
|
||||
+
|
||||
class DerivedActionBase(ActionBase):
|
||||
TRANSFERS_FILES = False
|
||||
|
||||
@@ -522,6 +544,18 @@ def test_action_base_sudo_only_if_user_differs(self):
|
||||
finally:
|
||||
C.BECOME_ALLOW_SAME_USER = become_allow_same_user
|
||||
|
||||
+ def test__remote_expand_user_relative_pathing(self):
|
||||
+ action_base = _action_base()
|
||||
+ action_base._play_context.remote_addr = 'bar'
|
||||
+ action_base._low_level_execute_command = MagicMock(return_value={'stdout': b'../home/user'})
|
||||
+ action_base._connection._shell.join_path.return_value = '../home/user/foo'
|
||||
+ with self.assertRaises(AnsibleError) as cm:
|
||||
+ action_base._remote_expand_user('~/foo')
|
||||
+ self.assertEqual(
|
||||
+ cm.exception.message,
|
||||
+ "'bar' returned an invalid relative home directory path containing '..'"
|
||||
+ )
|
||||
+
|
||||
|
||||
class TestActionBaseCleanReturnedData(unittest.TestCase):
|
||||
def test(self):
|
||||
@@ -573,27 +607,8 @@ def fake_all(path_only=None):
|
||||
|
||||
class TestActionBaseParseReturnedData(unittest.TestCase):
|
||||
|
||||
- def _action_base(self):
|
||||
- fake_loader = DictDataLoader({
|
||||
- })
|
||||
- mock_module_loader = MagicMock()
|
||||
- mock_shared_loader_obj = MagicMock()
|
||||
- mock_shared_loader_obj.module_loader = mock_module_loader
|
||||
- mock_connection_loader = MagicMock()
|
||||
-
|
||||
- mock_shared_loader_obj.connection_loader = mock_connection_loader
|
||||
- mock_connection = MagicMock()
|
||||
-
|
||||
- action_base = DerivedActionBase(task=None,
|
||||
- connection=mock_connection,
|
||||
- play_context=None,
|
||||
- loader=fake_loader,
|
||||
- templar=None,
|
||||
- shared_loader_obj=mock_shared_loader_obj)
|
||||
- return action_base
|
||||
-
|
||||
def test_fail_no_json(self):
|
||||
- action_base = self._action_base()
|
||||
+ action_base = _action_base()
|
||||
rc = 0
|
||||
stdout = 'foo\nbar\n'
|
||||
err = 'oopsy'
|
||||
@@ -607,7 +622,7 @@ def test_fail_no_json(self):
|
||||
self.assertEqual(res['module_stderr'], err)
|
||||
|
||||
def test_json_empty(self):
|
||||
- action_base = self._action_base()
|
||||
+ action_base = _action_base()
|
||||
rc = 0
|
||||
stdout = '{}\n'
|
||||
err = ''
|
||||
@@ -621,7 +636,7 @@ def test_json_empty(self):
|
||||
self.assertFalse(res)
|
||||
|
||||
def test_json_facts(self):
|
||||
- action_base = self._action_base()
|
||||
+ action_base = _action_base()
|
||||
rc = 0
|
||||
stdout = '{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"}}\n'
|
||||
err = ''
|
||||
@@ -637,7 +652,7 @@ def test_json_facts(self):
|
||||
# self.assertIsInstance(res['ansible_facts'], AnsibleUnsafe)
|
||||
|
||||
def test_json_facts_add_host(self):
|
||||
- action_base = self._action_base()
|
||||
+ action_base = _action_base()
|
||||
rc = 0
|
||||
stdout = '''{"ansible_facts": {"foo": "bar", "ansible_blip": "blip_value"},
|
||||
"add_host": {"host_vars": {"some_key": ["whatever the add_host object is"]}
|
||||
@ -51,10 +51,10 @@ index c83709d..0a4fe07 100644
|
||||
- # exceptions to 'deprefixing'
|
||||
- deprefixed[k] = deepcopy(facts[k])
|
||||
+ if k.startswith('ansible_') and k not in ('ansible_local',):
|
||||
+ deprefixed[k[8:]] = module_response_deepcopy(facts[k])
|
||||
+ deprefixed[k[8:]] = deepcopy(facts[k])
|
||||
else:
|
||||
- deprefixed[k.replace('ansible_', '', 1)] = deepcopy(facts[k])
|
||||
+ deprefixed[k] = module_response_deepcopy(facts[k])
|
||||
+ deprefixed[k] = deepcopy(facts[k])
|
||||
|
||||
return {'ansible_facts': deprefixed}
|
||||
diff --git a/test/integration/targets/gathering_facts/library/bogus_facts b/test/integration/targets/gathering_facts/library/bogus_facts
|
||||
|
||||
@ -1,53 +1,100 @@
|
||||
From 5292482553dc409081f7f4368398358cbf9f8672 Mon Sep 17 00:00:00 2001
|
||||
From: Brian Coca <bcoca@users.noreply.github.com>
|
||||
Date: Wed, 8 Apr 2020 14:28:51 -0400
|
||||
From c1dedd38cb6a3ad215cf077c533c93bd978acb93 Mon Sep 17 00:00:00 2001
|
||||
From: caodongxia <315816521@qq.com>
|
||||
Date: Tue, 2 Nov 2021 17:26:43 +0800
|
||||
Subject: [PATCH] fixed fetch traversal from slurp (#68720)
|
||||
Reference:https://github.com/ansible/ansible/commit/290bfa820d533dc224e0c3fa7dd7c6b907ed0189.patch
|
||||
|
||||
* fixed fetch traversal from slurp
|
||||
|
||||
* ignore slurp result for dest
|
||||
* fixed naming when source is relative
|
||||
* fixed bug in local connection plugin
|
||||
* added tests with fake slurp
|
||||
* moved existing role tests into runme.sh
|
||||
* normalized on action excepts
|
||||
* moved dest transform down to when needed
|
||||
* added is_subpath check
|
||||
│ * fixed bug in local connection
|
||||
fixes #67793
|
||||
|
||||
CVE-2019-3828
|
||||
|
||||
(cherry picked from commit ba87c225cd13343c35075fe7fc15b4cf1343fed6)
|
||||
---
|
||||
changelogs/fragments/fetch_no_slurp.yml | 2 ++
|
||||
lib/ansible/plugins/action/fetch.py | 23 ++++++---------
|
||||
.../fetch/injection/avoid_slurp_return.yml | 26 +++++++++++++++++
|
||||
changelogs/fragments/fetch_no_slurp.yml | 2 +
|
||||
lib/ansible/plugins/action/fetch.py | 44 ++++++++-----------
|
||||
lib/ansible/utils/path.py | 22 ++++++++++
|
||||
test/integration/targets/fetch/aliases | 1 +
|
||||
.../fetch/injection/avoid_slurp_return.yml | 26 +++++++++++
|
||||
.../targets/fetch/injection/here.txt | 1 +
|
||||
.../targets/fetch/injection/library/slurp.py | 29 +++++++++++++++++++
|
||||
.../targets/fetch/run_fetch_tests.yml | 5 ++++
|
||||
test/integration/targets/fetch/runme.sh | 12 ++++++++
|
||||
7 files changed, 84 insertions(+), 14 deletions(-)
|
||||
.../targets/fetch/injection/library/slurp.py | 28 ++++++++++++
|
||||
.../targets/fetch/run_fetch_tests.yml | 5 +++
|
||||
test/integration/targets/fetch/runme.sh | 12 +++++
|
||||
9 files changed, 113 insertions(+), 26 deletions(-)
|
||||
create mode 100644 changelogs/fragments/fetch_no_slurp.yml
|
||||
create mode 100644 test/integration/targets/fetch/injection/avoid_slurp_return.yml
|
||||
create mode 100644 test/integration/targets/fetch/injection/here.txt
|
||||
create mode 100644 test/integration/targets/fetch/injection/library/slurp.py
|
||||
create mode 100644 test/integration/targets/fetch/run_fetch_tests.yml
|
||||
create mode 100755 test/integration/targets/fetch/runme.sh
|
||||
create mode 100644 test/integration/targets/fetch/runme.sh
|
||||
|
||||
diff --git a/changelogs/fragments/fetch_no_slurp.yml b/changelogs/fragments/fetch_no_slurp.yml
|
||||
new file mode 100644
|
||||
index 0000000..c742d40
|
||||
--- /dev/null
|
||||
index 0000000000000..c742d40c3ba82
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/fetch_no_slurp.yml
|
||||
@@ -0,0 +1,2 @@
|
||||
+bugfixes:
|
||||
+ - In fetch action, avoid using slurp return to set up dest, also ensure no dir traversal CVE-2019-3828.
|
||||
diff --git a/lib/ansible/plugins/action/fetch.py b/lib/ansible/plugins/action/fetch.py
|
||||
index fbaf992..471732c 100644
|
||||
index fbaf992..7db5b43 100644
|
||||
--- a/lib/ansible/plugins/action/fetch.py
|
||||
+++ b/lib/ansible/plugins/action/fetch.py
|
||||
@@ -106,12 +106,6 @@ class ActionModule(ActionBase):
|
||||
@@ -20,13 +20,13 @@ __metaclass__ = type
|
||||
import os
|
||||
import base64
|
||||
|
||||
-from ansible.errors import AnsibleError
|
||||
+from ansible.errors import AnsibleActionFail, AnsibleActionSkip
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.utils.hashing import checksum, checksum_s, md5, secure_hash
|
||||
-from ansible.utils.path import makedirs_safe
|
||||
+from ansible.utils.path import makedirs_safe, is_subpath
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
@@ -47,24 +47,23 @@ class ActionModule(ActionBase):
|
||||
|
||||
try:
|
||||
if self._play_context.check_mode:
|
||||
- result['skipped'] = True
|
||||
- result['msg'] = 'check mode not (yet) supported for this module'
|
||||
- return result
|
||||
+ raise AnsibleActionSkip('check mode not (yet) supported for this module')
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
- dest = self._task.args.get('dest', None)
|
||||
+ original_dest = dest = self._task.args.get('dest', None)
|
||||
flat = boolean(self._task.args.get('flat'), strict=False)
|
||||
fail_on_missing = boolean(self._task.args.get('fail_on_missing', True), strict=False)
|
||||
validate_checksum = boolean(self._task.args.get('validate_checksum',
|
||||
self._task.args.get('validate_md5', True)),
|
||||
strict=False)
|
||||
|
||||
+ msg = ''
|
||||
# validate source and dest are strings FIXME: use basic.py and module specs
|
||||
if not isinstance(source, string_types):
|
||||
- result['msg'] = "Invalid type supplied for source option, it must be a string"
|
||||
+ msg = "Invalid type supplied for source option, it must be a string"
|
||||
|
||||
if not isinstance(dest, string_types):
|
||||
- result['msg'] = "Invalid type supplied for dest option, it must be a string"
|
||||
+ msg = "Invalid type supplied for dest option, it must be a string"
|
||||
|
||||
# validate_md5 is the deprecated way to specify validate_checksum
|
||||
if 'validate_md5' in self._task.args and 'validate_checksum' in self._task.args:
|
||||
@@ -74,11 +73,10 @@ class ActionModule(ActionBase):
|
||||
display.deprecated('Use validate_checksum instead of validate_md5', version='2.8')
|
||||
|
||||
if source is None or dest is None:
|
||||
- result['msg'] = "src and dest are required"
|
||||
+ msg = "src and dest are required"
|
||||
|
||||
- if result.get('msg'):
|
||||
- result['failed'] = True
|
||||
- return result
|
||||
+ if msg:
|
||||
+ raise AnsibleActionFail(msg)
|
||||
|
||||
source = self._connection._shell.join_path(source)
|
||||
source = self._remote_expand_user(source)
|
||||
@@ -106,12 +104,6 @@ class ActionModule(ActionBase):
|
||||
remote_data = base64.b64decode(slurpres['content'])
|
||||
if remote_data is not None:
|
||||
remote_checksum = checksum_s(remote_data)
|
||||
@ -60,7 +107,7 @@ index fbaf992..471732c 100644
|
||||
|
||||
# calculate the destination name
|
||||
if os.path.sep not in self._connection._shell.join_path('a', ''):
|
||||
@@ -120,13 +114,14 @@ class ActionModule(ActionBase):
|
||||
@@ -120,13 +112,13 @@ class ActionModule(ActionBase):
|
||||
else:
|
||||
source_local = source
|
||||
|
||||
@ -68,8 +115,7 @@ index fbaf992..471732c 100644
|
||||
+ # ensure we only use file name, avoid relative paths
|
||||
+ if not is_subpath(dest, original_dest):
|
||||
+ # TODO: ? dest = os.path.expanduser(dest.replace(('../','')))
|
||||
+ raise AnsibleActionFail("Detected directory traversal, expected to be contained in '%s' but got '%s'" % (original_dest, dest))
|
||||
+
|
||||
+ raise AnsibleActionFail("Detected directory traversal, expected to be contained in '%s' but got '%s'" %(original_dest, dest))
|
||||
if flat:
|
||||
if os.path.isdir(to_bytes(dest, errors='surrogate_or_strict')) and not dest.endswith(os.sep):
|
||||
- result['msg'] = "dest is an existing directory, use a trailing slash if you want to fetch src into that directory"
|
||||
@ -80,7 +126,7 @@ index fbaf992..471732c 100644
|
||||
if dest.endswith(os.sep):
|
||||
# if the path ends with "/", we'll use the source filename as the
|
||||
# destination filename
|
||||
@@ -143,8 +138,6 @@ class ActionModule(ActionBase):
|
||||
@@ -143,8 +135,6 @@ class ActionModule(ActionBase):
|
||||
target_name = self._play_context.remote_addr
|
||||
dest = "%s/%s/%s" % (self._loader.path_dwim(dest), target_name, source_local)
|
||||
|
||||
@ -89,16 +135,16 @@ index fbaf992..471732c 100644
|
||||
if remote_checksum in ('0', '1', '2', '3', '4', '5'):
|
||||
result['changed'] = False
|
||||
result['file'] = source
|
||||
@@ -172,6 +165,8 @@ class ActionModule(ActionBase):
|
||||
@@ -172,6 +162,8 @@ class ActionModule(ActionBase):
|
||||
result['msg'] += ", not transferring, ignored"
|
||||
return result
|
||||
|
||||
+ dest = os.path.normpath(dest)
|
||||
+ dest = os.path.normpath(dest)
|
||||
+
|
||||
# calculate checksum for the local file
|
||||
local_checksum = checksum(dest)
|
||||
|
||||
@@ -188,7 +183,7 @@ class ActionModule(ActionBase):
|
||||
@@ -188,7 +180,7 @@ class ActionModule(ActionBase):
|
||||
f.write(remote_data)
|
||||
f.close()
|
||||
except (IOError, OSError) as e:
|
||||
@ -107,9 +153,46 @@ index fbaf992..471732c 100644
|
||||
new_checksum = secure_hash(dest)
|
||||
# For backwards compatibility. We'll return None on FIPS enabled systems
|
||||
try:
|
||||
diff --git a/lib/ansible/utils/path.py b/lib/ansible/utils/path.py
|
||||
index 717cef6..2ef1316 100644
|
||||
--- a/lib/ansible/utils/path.py
|
||||
+++ b/lib/ansible/utils/path.py
|
||||
@@ -97,3 +97,25 @@ def basedir(source):
|
||||
dname = os.path.abspath(dname)
|
||||
|
||||
return to_text(dname, errors='surrogate_or_strict')
|
||||
+
|
||||
+def is_subpath(child, parent):
|
||||
+ """
|
||||
+ Compares paths to check if one is contained in the other
|
||||
+ :arg: child: Path to test
|
||||
+ :arg parent; Path to test against
|
||||
+ """
|
||||
+ test = False
|
||||
+
|
||||
+ abs_child = unfrackpath(child, follow=False)
|
||||
+ abs_parent = unfrackpath(parent, follow=False)
|
||||
+
|
||||
+ c = abs_child.split(os.path.sep)
|
||||
+ p = abs_parent.split(os.path.sep)
|
||||
+
|
||||
+ try:
|
||||
+ test = c[:len(p)] == p
|
||||
+ except IndexError:
|
||||
+ # child is shorter than parent so cannot be subpath
|
||||
+ pass
|
||||
+
|
||||
+ return test
|
||||
diff --git a/test/integration/targets/fetch/aliases b/test/integration/targets/fetch/aliases
|
||||
index 7af8b7f..197794b 100644
|
||||
--- a/test/integration/targets/fetch/aliases
|
||||
+++ b/test/integration/targets/fetch/aliases
|
||||
@@ -1 +1,2 @@
|
||||
posix/ci/group2
|
||||
+needs/target/setup_remote_tmp_dir
|
||||
diff --git a/test/integration/targets/fetch/injection/avoid_slurp_return.yml b/test/integration/targets/fetch/injection/avoid_slurp_return.yml
|
||||
new file mode 100644
|
||||
index 0000000..af62dcf
|
||||
index 0000000..c44b85e
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/fetch/injection/avoid_slurp_return.yml
|
||||
@@ -0,0 +1,26 @@
|
||||
@ -148,10 +231,10 @@ index 0000000..493021b
|
||||
+this is a test file
|
||||
diff --git a/test/integration/targets/fetch/injection/library/slurp.py b/test/integration/targets/fetch/injection/library/slurp.py
|
||||
new file mode 100644
|
||||
index 0000000..7b78ba1
|
||||
index 0000000..3916ae8
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/fetch/injection/library/slurp.py
|
||||
@@ -0,0 +1,29 @@
|
||||
@@ -0,0 +1,28 @@
|
||||
+#!/usr/bin/python
|
||||
+from __future__ import (absolute_import, division, print_function)
|
||||
+__metaclass__ = type
|
||||
@ -171,7 +254,6 @@ index 0000000..7b78ba1
|
||||
+
|
||||
+import json
|
||||
+import random
|
||||
+
|
||||
+bad_responses = ['../foo', '../../foo', '../../../foo', '/../../../foo', '/../foo', '//..//foo', '..//..//foo']
|
||||
+
|
||||
+
|
||||
@ -193,8 +275,8 @@ index 0000000..f2ff1df
|
||||
+ roles:
|
||||
+ - fetch_tests
|
||||
diff --git a/test/integration/targets/fetch/runme.sh b/test/integration/targets/fetch/runme.sh
|
||||
new file mode 100755
|
||||
index 0000000..7e909dd
|
||||
new file mode 100644
|
||||
index 0000000..3d5b75c
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/fetch/runme.sh
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
From a2ef19e48a53cc83b3a6f433013d8ff4e8f5d618 Mon Sep 17 00:00:00 2001
|
||||
From: Brian Coca <brian.coca+git@gmail.com>
|
||||
Date: Thu, 2 Apr 2020 11:07:51 -0400
|
||||
Subject: [PATCH] stricter permissions on atomic_move when creating new file
|
||||
|
||||
---
|
||||
test/units/module_utils/basic/test_atomic_move.py | 13 ++++++++-----
|
||||
1 file changed, 8 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/test/units/module_utils/basic/test_atomic_move.py b/test/units/module_utils/basic/test_atomic_move.py
|
||||
index d1dc4d7..a44ebc5 100644
|
||||
--- a/test/units/module_utils/basic/test_atomic_move.py
|
||||
+++ b/test/units/module_utils/basic/test_atomic_move.py
|
||||
@@ -59,7 +59,7 @@ def atomic_mocks(mocker):
|
||||
@pytest.fixture
|
||||
def fake_stat(mocker):
|
||||
stat1 = mocker.MagicMock()
|
||||
- stat1.st_mode = 0o0644
|
||||
+ stat1.st_mode = 0o0640
|
||||
stat1.st_uid = 0
|
||||
stat1.st_gid = 0
|
||||
yield stat1
|
||||
@@ -75,7 +75,8 @@ def test_new_file(atomic_am, atomic_mocks, mocker, selinux):
|
||||
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
|
||||
|
||||
atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
|
||||
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', basic.DEFAULT_PERM & ~18)]
|
||||
+ # 416 is what we expect with default perms set to 0640
|
||||
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', 416)]
|
||||
|
||||
if selinux:
|
||||
assert atomic_am.selinux_default_context.call_args_list == [mocker.call('/path/to/dest')]
|
||||
@@ -96,7 +97,7 @@ def test_existing_file(atomic_am, atomic_mocks, fake_stat, mocker, selinux):
|
||||
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
|
||||
|
||||
atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
|
||||
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
|
||||
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', 416)]
|
||||
|
||||
if selinux:
|
||||
assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
|
||||
@@ -119,10 +120,10 @@ def test_no_tty_fallback(atomic_am, atomic_mocks, fake_stat, mocker):
|
||||
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
|
||||
|
||||
atomic_mocks['rename'].assert_called_with(b'/path/to/src', b'/path/to/dest')
|
||||
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
|
||||
|
||||
assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
|
||||
assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
|
||||
+ atomic_am.atomic_move('/path/to/src', '/path/to/dest')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])
|
||||
@@ -150,6 +151,8 @@ def test_existing_file_stat_perms_failure(atomic_am, atomic_mocks, mocker):
|
||||
# FIXME: Should atomic_move() set a default permission value when it cannot retrieve the
|
||||
# existing file's permissions? (Right now it's up to the calling code.
|
||||
# assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/src', basic.DEFAULT_PERM & ~18)]
|
||||
+ # atomic_move() will set a default permission value whenit cannot retrieve the
|
||||
+ # existing file's permissions.
|
||||
assert atomic_am.set_context_if_different.call_args_list == [mocker.call('/path/to/dest', mock_context, False)]
|
||||
assert atomic_am.selinux_context.call_args_list == [mocker.call('/path/to/dest')]
|
||||
|
||||
@@ -206,7 +209,7 @@ def test_rename_perms_fail_temp_succeeds(atomic_am, atomic_mocks, fake_stat, moc
|
||||
atomic_am.atomic_move('/path/to/src', '/path/to/dest')
|
||||
assert atomic_mocks['rename'].call_args_list == [mocker.call(b'/path/to/src', b'/path/to/dest'),
|
||||
mocker.call(b'/path/to/tempfile', b'/path/to/dest')]
|
||||
- assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', basic.DEFAULT_PERM & ~18)]
|
||||
+ assert atomic_mocks['chmod'].call_args_list == [mocker.call(b'/path/to/dest', 416)]
|
||||
|
||||
if selinux:
|
||||
assert atomic_am.selinux_default_context.call_args_list == [mocker.call('/path/to/dest')]
|
||||
--
|
||||
2.23.0
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
From b1fd71de03ae3843ac556d9b726b5f3b2441c3ed Mon Sep 17 00:00:00 2001
|
||||
From: Abhijeet Kasurde <akasurde@redhat.com>
|
||||
Date: Thu, 27 Feb 2020 11:42:12 +0530
|
||||
Subject: [PATCH] Add whitelisting for package and service module
|
||||
|
||||
**security issue** (CVE-2020-1738)
|
||||
When 'use' parameter is not used in package and service module,
|
||||
ansible relies on ansible facts such as 'pkg_mgr' and 'service_mgr'.
|
||||
|
||||
This would allow arbitrary code execution on the managed node.
|
||||
|
||||
Fix is added by adding a whitelist of allowed package manager modules and
|
||||
service manager modules to avoid arbitrary code execution on the managed node.
|
||||
|
||||
Fixes: #67796
|
||||
|
||||
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
|
||||
---
|
||||
changelogs/fragments/67796-package-service-fact_fix.yml | 4 ++++
|
||||
lib/ansible/plugins/action/package.py | 8 ++++++++
|
||||
lib/ansible/plugins/action/service.py | 5 +++++
|
||||
3 files changed, 17 insertions(+)
|
||||
create mode 100644 changelogs/fragments/67796-package-service-fact_fix.yml
|
||||
|
||||
diff --git a/changelogs/fragments/67796-package-service-fact_fix.yml b/changelogs/fragments/67796-package-service-fact_fix.yml
|
||||
new file mode 100644
|
||||
index 0000000000000..ce1ee71da08e0
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/67796-package-service-fact_fix.yml
|
||||
@@ -0,0 +1,4 @@
|
||||
+bugfixes:
|
||||
+ - >
|
||||
+ **security issue** Add a whitelist of modules for package and service module
|
||||
+ when 'use' is not used and engine relies on pkg_mgr and service_mgr facts (CVE-2020-1738).
|
||||
diff --git a/lib/ansible/plugins/action/package.py b/lib/ansible/plugins/action/package.py
|
||||
index 932acccb04b66..8884086d8d6c5 100644
|
||||
--- a/lib/ansible/plugins/action/package.py
|
||||
+++ b/lib/ansible/plugins/action/package.py
|
||||
@@ -56,6 +56,14 @@ def run(self, tmp=None, task_vars=None):
|
||||
module = facts.get('ansible_facts', {}).get('ansible_pkg_mgr', 'auto')
|
||||
|
||||
if module != 'auto':
|
||||
+ if module not in ['apk', 'apt_rpm', 'apt', 'dnf', 'homebrew_cask',
|
||||
+ 'homebrew_tap', 'homebrew', 'installp', 'macports', 'mas',
|
||||
+ 'openbsd_pkg', 'opkg', 'pacman', 'pkg5', 'pkgin',
|
||||
+ 'pkgng', 'pkgutil', 'portage', 'portinstall', 'slackpkg',
|
||||
+ 'snap', 'sorcery', 'svr4pkg', 'swdepot', 'swupd',
|
||||
+ 'urpmi', 'xbps', 'yum', 'zypper']:
|
||||
+ raise AnsibleActionFail('Could not find a module for package manager %s.'
|
||||
+ 'Try setting the "use" option.' % module)
|
||||
|
||||
if module not in self._shared_loader_obj.module_loader:
|
||||
raise AnsibleActionFail('Could not find a module for %s.' % module)
|
||||
diff --git a/lib/ansible/plugins/action/service.py b/lib/ansible/plugins/action/service.py
|
||||
index 3ebd0ae17dc90..e11ab1e287164 100644
|
||||
--- a/lib/ansible/plugins/action/service.py
|
||||
+++ b/lib/ansible/plugins/action/service.py
|
||||
@@ -61,6 +61,11 @@ def run(self, tmp=None, task_vars=None):
|
||||
module = 'service'
|
||||
|
||||
if module != 'auto':
|
||||
+ # Check if auto detected module is valid module name or not
|
||||
+ if module not in ['nosh', 'openwrt_init', 'runit',
|
||||
+ 'svc', 'systemd', 'sysvinit', 'service']:
|
||||
+ raise AnsibleActionFail('Could not find module for "%s" service manager. '
|
||||
+ 'Try setting the "use" option.' % module)
|
||||
# run the 'service' module
|
||||
new_module_args = self._task.args.copy()
|
||||
if 'use' in new_module_args:
|
||||
329
CVE-2022-3697.patch
Normal file
329
CVE-2022-3697.patch
Normal file
@ -0,0 +1,329 @@
|
||||
From: =?utf-8?q?Bastien_Roucari=C3=A8s?= <rouca@debian.org>
|
||||
Date: Sun, 24 Dec 2023 11:59:07 +0000
|
||||
Subject: CVE-2022-3697: ec2_instance - validate options on tower_callback
|
||||
|
||||
A flaw was found in Ansible in the amazon.aws collection when using the
|
||||
tower_callback parameter from the amazon.aws.ec2_instance module.
|
||||
This flaw allows an attacker to take advantage of this issue as
|
||||
the module is handling the parameter insecurely, leading to the password leaking in the logs
|
||||
|
||||
https://salsa.debian.org/python-team/packages/ansible/-/blob/debian/2.7.7+dfsg-1+deb10u2/debian/patches/0029-CVE-2022-3697-ec2_instance-validate-options-on-tower.patch?ref_type=tags
|
||||
origin: https://patch-diff.githubusercontent.com/raw/ansible-collections/amazon.aws/pull/1207.patch
|
||||
bug: https://github.com/ansible-collections/amazon.aws/pull/1199
|
||||
bug-redhat: https://bugzilla.redhat.com/show_bug.cgi?id=2137664
|
||||
|
||||
---
|
||||
.../20221021-ec2_instance-tower_callback.yml | 5 +
|
||||
lib/ansible/module_utils/aws/tower.py | 83 ++++++++++++++
|
||||
.../modules/cloud/amazon/ec2_instance.py | 104 +++++++-----------
|
||||
test/units/module_utils/aws/test_tower.py | 40 +++++++
|
||||
4 files changed, 167 insertions(+), 65 deletions(-)
|
||||
create mode 100644 changelogs/fragments/20221021-ec2_instance-tower_callback.yml
|
||||
create mode 100644 lib/ansible/module_utils/aws/tower.py
|
||||
create mode 100644 test/units/module_utils/aws/test_tower.py
|
||||
|
||||
diff --git a/changelogs/fragments/20221021-ec2_instance-tower_callback.yml b/changelogs/fragments/20221021-ec2_instance-tower_callback.yml
|
||||
new file mode 100644
|
||||
index 0000000..ae96114
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/20221021-ec2_instance-tower_callback.yml
|
||||
@@ -0,0 +1,5 @@
|
||||
+minor_changes:
|
||||
+- ec2_instance - refacter ``tower_callback`` code to handle parameter validation as part of the argument specification (https://github.com/ansible-collections/amazon.aws/pull/1199).
|
||||
+- ec2_instance - the ``tower_callback`` parameter has been renamed to ``aap_callback``, ``tower_callback`` remains as an alias. This change should have no observable effect for users outside the module documentation (https://github.com/ansible-collections/amazon.aws/pull/1199).
|
||||
+security_fixes:
|
||||
+- ec2_instance - fixes leak of password into logs when using ``tower_callback.windows=True`` and ``tower_callback.set_password`` (https://github.com/ansible-collections/amazon.aws/pull/1199).
|
||||
diff --git a/lib/ansible/module_utils/aws/tower.py b/lib/ansible/module_utils/aws/tower.py
|
||||
new file mode 100644
|
||||
index 0000000..dd7d973
|
||||
--- /dev/null
|
||||
+++ b/lib/ansible/module_utils/aws/tower.py
|
||||
@@ -0,0 +1,83 @@
|
||||
+# Copyright: Ansible Project
|
||||
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
+
|
||||
+from __future__ import absolute_import, division, print_function
|
||||
+__metaclass__ = type
|
||||
+
|
||||
+import string
|
||||
+import textwrap
|
||||
+
|
||||
+from ansible.module_utils._text import to_native
|
||||
+from ansible.module_utils.six.moves.urllib import parse as urlparse
|
||||
+
|
||||
+
|
||||
+def _windows_callback_script(passwd=None):
|
||||
+ script_url = 'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'
|
||||
+ if passwd is not None:
|
||||
+ passwd = passwd.replace("'", "''")
|
||||
+ script_tpl = """\
|
||||
+ <powershell>
|
||||
+ $admin = [adsi]('WinNT://./administrator, user')
|
||||
+ $admin.PSBase.Invoke('SetPassword', '${PASS}')
|
||||
+ Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('${SCRIPT}'))
|
||||
+ </powershell>
|
||||
+ """
|
||||
+ else:
|
||||
+ script_tpl = """\
|
||||
+ <powershell>
|
||||
+ $admin = [adsi]('WinNT://./administrator, user')
|
||||
+ Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('${SCRIPT}'))
|
||||
+ </powershell>
|
||||
+ """
|
||||
+
|
||||
+ tpl = string.Template(textwrap.dedent(script_tpl))
|
||||
+ return tpl.safe_substitute(PASS=passwd, SCRIPT=script_url)
|
||||
+
|
||||
+
|
||||
+def _linux_callback_script(tower_address, template_id, host_config_key):
|
||||
+ template_id = urlparse.quote(template_id)
|
||||
+ tower_address = urlparse.quote(tower_address)
|
||||
+ host_config_key = host_config_key.replace("'", "'\"'\"'")
|
||||
+
|
||||
+ script_tpl = """\
|
||||
+ #!/bin/bash
|
||||
+ set -x
|
||||
+
|
||||
+ retry_attempts=10
|
||||
+ attempt=0
|
||||
+ while [[ $attempt -lt $retry_attempts ]]
|
||||
+ do
|
||||
+ status_code=$(curl --max-time 10 -v -k -s -i \
|
||||
+ --data 'host_config_key=${host_config_key}' \
|
||||
+ 'https://${tower_address}/api/v2/job_templates/${template_id}/callback/' \
|
||||
+ | head -n 1 \
|
||||
+ | awk '{print $2}')
|
||||
+ if [[ $status_code == 404 ]]
|
||||
+ then
|
||||
+ status_code=$(curl --max-time 10 -v -k -s -i \
|
||||
+ --data 'host_config_key=${host_config_key}' \
|
||||
+ 'https://${tower_address}/api/v1/job_templates/${template_id}/callback/' \
|
||||
+ | head -n 1 \
|
||||
+ | awk '{print $2}')
|
||||
+ # fall back to using V1 API for Tower 3.1 and below, since v2 API will always 404
|
||||
+ fi
|
||||
+ if [[ $status_code == 201 ]]
|
||||
+ then
|
||||
+ exit 0
|
||||
+ fi
|
||||
+ attempt=$(( attempt + 1 ))
|
||||
+ echo "$${status_code} received... retrying in 1 minute. (Attempt $${attempt})"
|
||||
+ sleep 60
|
||||
+ done
|
||||
+ exit 1
|
||||
+ """
|
||||
+ tpl = string.Template(textwrap.dedent(script_tpl))
|
||||
+ return tpl.safe_substitute(tower_address=tower_address,
|
||||
+ template_id=template_id,
|
||||
+ host_config_key=host_config_key)
|
||||
+
|
||||
+
|
||||
+def tower_callback_script(tower_address, job_template_id, host_config_key, windows, passwd):
|
||||
+ if windows:
|
||||
+ return to_native(_windows_callback_script(passwd=passwd))
|
||||
+ return _linux_callback_script(tower_address, job_template_id, host_config_key)
|
||||
diff --git a/lib/ansible/modules/cloud/amazon/ec2_instance.py b/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||
index 3d376bc..68aaced 100644
|
||||
--- a/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||
+++ b/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||
@@ -45,13 +45,16 @@ options:
|
||||
user_data:
|
||||
description:
|
||||
- Opaque blob of data which is made available to the ec2 instance
|
||||
- tower_callback:
|
||||
+ aap_callback:
|
||||
description:
|
||||
- - Preconfigured user-data to enable an instance to perform a Tower callback.
|
||||
+ - Preconfigured user-data to enable an instance to perform an Ansible Automation Platform
|
||||
+ callback (Linux only).
|
||||
+ - For Windows instances, to enable remote access via Ansible set I(windows) to C(true), and
|
||||
+ optionally set an admin password.
|
||||
+ - If using I(windows) and I(set_password), callback ton Ansible Automation Platform will not
|
||||
+ be performed but the instance will be ready to receive winrm connections from Ansible.
|
||||
- Requires parameters I(tower_callback.tower_address), I(tower_callback.job_template_id), and I(tower_callback.host_config_key).
|
||||
- Mutually exclusive with I(user_data).
|
||||
- - For Windows instances, to enable remote access via Ansible set I(tower_callback.windows) to true, and optionally set an admin password.
|
||||
- - If using 'windows' and 'set_password', callback to Tower will not be performed but the instance will be ready to receive winrm connections from Ansible.
|
||||
tags:
|
||||
description:
|
||||
- A hash/dictionary of tags to add to the new instance or to add/remove from an existing one.
|
||||
@@ -613,60 +616,10 @@ from ansible.module_utils.ec2 import (boto3_conn,
|
||||
camel_dict_to_snake_dict)
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
+from ansible.module_utils.aws.tower import tower_callback_script
|
||||
|
||||
module = None
|
||||
|
||||
-
|
||||
-def tower_callback_script(tower_conf, windows=False, passwd=None):
|
||||
- script_url = 'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'
|
||||
- if windows and passwd is not None:
|
||||
- script_tpl = """<powershell>
|
||||
- $admin = [adsi]("WinNT://./administrator, user")
|
||||
- $admin.PSBase.Invoke("SetPassword", "{PASS}")
|
||||
- Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('{SCRIPT}'))
|
||||
- </powershell>
|
||||
- """
|
||||
- return to_native(textwrap.dedent(script_tpl).format(PASS=passwd, SCRIPT=script_url))
|
||||
- elif windows and passwd is None:
|
||||
- script_tpl = """<powershell>
|
||||
- $admin = [adsi]("WinNT://./administrator, user")
|
||||
- Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('{SCRIPT}'))
|
||||
- </powershell>
|
||||
- """
|
||||
- return to_native(textwrap.dedent(script_tpl).format(PASS=passwd, SCRIPT=script_url))
|
||||
- elif not windows:
|
||||
- for p in ['tower_address', 'job_template_id', 'host_config_key']:
|
||||
- if p not in tower_conf:
|
||||
- module.fail_json(msg="Incomplete tower_callback configuration. tower_callback.{0} not set.".format(p))
|
||||
-
|
||||
- tpl = string.Template(textwrap.dedent("""#!/bin/bash
|
||||
- set -x
|
||||
-
|
||||
- retry_attempts=10
|
||||
- attempt=0
|
||||
- while [[ $attempt -lt $retry_attempts ]]
|
||||
- do
|
||||
- status_code=`curl --max-time 10 -v -k -s -i \
|
||||
- --data "host_config_key=${host_config_key}" \
|
||||
- https://${tower_address}/api/v1/job_templates/${template_id}/callback/ \
|
||||
- | head -n 1 \
|
||||
- | awk '{print $2}'`
|
||||
- if [[ $status_code == 201 ]]
|
||||
- then
|
||||
- exit 0
|
||||
- fi
|
||||
- attempt=$(( attempt + 1 ))
|
||||
- echo "$${status_code} received... retrying in 1 minute. (Attempt $${attempt})"
|
||||
- sleep 60
|
||||
- done
|
||||
- exit 1
|
||||
- """))
|
||||
- return tpl.safe_substitute(tower_address=tower_conf['tower_address'],
|
||||
- template_id=tower_conf['job_template_id'],
|
||||
- host_config_key=tower_conf['host_config_key'])
|
||||
- raise NotImplementedError("Only windows with remote-prep or non-windows with tower job callback supported so far.")
|
||||
-
|
||||
-
|
||||
@AWSRetry.jittered_backoff()
|
||||
def manage_tags(match, new_tags, purge_tags, ec2):
|
||||
changed = False
|
||||
@@ -885,6 +838,21 @@ def discover_security_groups(group, groups, parent_vpc_id=None, subnet_id=None,
|
||||
return list(dict((g['GroupId'], g) for g in found_groups).values())
|
||||
|
||||
|
||||
+def build_userdata(params):
|
||||
+ if params.get('user_data') is not None:
|
||||
+ return {'UserData': to_native(params.get('user_data'))}
|
||||
+ if params.get('aap_callback'):
|
||||
+ userdata = tower_callback_script(
|
||||
+ tower_address=params.get('aap_callback').get('tower_address'),
|
||||
+ job_template_id=params.get('aap_callback').get('job_template_id'),
|
||||
+ host_config_key=params.get('aap_callback').get('host_config_key'),
|
||||
+ windows=params.get('aap_callback').get('windows'),
|
||||
+ passwd=params.get('aap_callback').get('set_passwd'),
|
||||
+ )
|
||||
+ return {'UserData': userdata}
|
||||
+ return {}
|
||||
+
|
||||
+
|
||||
def build_top_level_options(params):
|
||||
spec = {}
|
||||
if params.get('image_id'):
|
||||
@@ -901,14 +869,8 @@ def build_top_level_options(params):
|
||||
|
||||
if params.get('key_name') is not None:
|
||||
spec['KeyName'] = params.get('key_name')
|
||||
- if params.get('user_data') is not None:
|
||||
- spec['UserData'] = to_native(params.get('user_data'))
|
||||
- elif params.get('tower_callback') is not None:
|
||||
- spec['UserData'] = tower_callback_script(
|
||||
- tower_conf=params.get('tower_callback'),
|
||||
- windows=params.get('tower_callback').get('windows', False),
|
||||
- passwd=params.get('tower_callback').get('set_password'),
|
||||
- )
|
||||
+
|
||||
+ spec.update(build_userdata(params))
|
||||
|
||||
if params.get('launch_template') is not None:
|
||||
spec['LaunchTemplate'] = {}
|
||||
@@ -1331,7 +1293,19 @@ def main():
|
||||
image_id=dict(type='str'),
|
||||
instance_type=dict(default='t2.micro', type='str'),
|
||||
user_data=dict(type='str'),
|
||||
- tower_callback=dict(type='dict'),
|
||||
+ aap_callback=dict(
|
||||
+ type='dict', aliases=['tower_callback'],
|
||||
+ required_if=[
|
||||
+ ('windows', False, ('tower_address', 'job_template_id', 'host_config_key',), False),
|
||||
+ ],
|
||||
+ options=dict(
|
||||
+ windows=dict(type='bool', default=False),
|
||||
+ set_password=dict(type='str', no_log=True),
|
||||
+ tower_address=dict(type='str'),
|
||||
+ job_template_id=dict(type='str'),
|
||||
+ host_config_key=dict(type='str', no_log=True),
|
||||
+ ),
|
||||
+ ),
|
||||
ebs_optimized=dict(type='bool'),
|
||||
vpc_subnet_id=dict(type='str', aliases=['subnet_id']),
|
||||
availability_zone=dict(type='str'),
|
||||
@@ -1360,7 +1334,7 @@ def main():
|
||||
mutually_exclusive=[
|
||||
['security_groups', 'security_group'],
|
||||
['availability_zone', 'vpc_subnet_id'],
|
||||
- ['tower_callback', 'user_data'],
|
||||
+ ['aap_callback', 'user_data'],
|
||||
['image_id', 'image'],
|
||||
],
|
||||
supports_check_mode=True
|
||||
diff --git a/test/units/module_utils/aws/test_tower.py b/test/units/module_utils/aws/test_tower.py
|
||||
new file mode 100644
|
||||
index 0000000..527423b
|
||||
--- /dev/null
|
||||
+++ b/test/units/module_utils/aws/test_tower.py
|
||||
@@ -0,0 +1,40 @@
|
||||
+# (c) 2022 Red Hat Inc.
|
||||
+#
|
||||
+# This file is part of Ansible
|
||||
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
+
|
||||
+from __future__ import (absolute_import, division, print_function)
|
||||
+__metaclass__ = type
|
||||
+
|
||||
+# import pytest
|
||||
+
|
||||
+import ansible.module_utils.aws.tower as utils_tower
|
||||
+
|
||||
+WINDOWS_DOWNLOAD = "Invoke-Expression ((New-Object System.Net.Webclient).DownloadString(" \
|
||||
+ "'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'))"
|
||||
+EXAMPLE_PASSWORD = 'MY_EXAMPLE_PASSWORD'
|
||||
+WINDOWS_INVOKE = "$admin.PSBase.Invoke('SetPassword', 'MY_EXAMPLE_PASSWORD'"
|
||||
+
|
||||
+EXAMPLE_TOWER = "tower.example.com"
|
||||
+EXAMPLE_TEMPLATE = 'My Template'
|
||||
+EXAMPLE_KEY = '123EXAMPLE123'
|
||||
+LINUX_TRIGGER_V1 = 'https://tower.example.com/api/v1/job_templates/My%20Template/callback/'
|
||||
+LINUX_TRIGGER_V2 = 'https://tower.example.com/api/v2/job_templates/My%20Template/callback/'
|
||||
+
|
||||
+
|
||||
+def test_windows_callback_no_password():
|
||||
+ user_data = utils_tower._windows_callback_script()
|
||||
+ assert WINDOWS_DOWNLOAD in user_data
|
||||
+ assert 'SetPassword' not in user_data
|
||||
+
|
||||
+
|
||||
+def test_windows_callback_password():
|
||||
+ user_data = utils_tower._windows_callback_script(EXAMPLE_PASSWORD)
|
||||
+ assert WINDOWS_DOWNLOAD in user_data
|
||||
+ assert WINDOWS_INVOKE in user_data
|
||||
+
|
||||
+
|
||||
+def test_linux_callback_with_name():
|
||||
+ user_data = utils_tower._linux_callback_script(EXAMPLE_TOWER, EXAMPLE_TEMPLATE, EXAMPLE_KEY)
|
||||
+ assert LINUX_TRIGGER_V1 in user_data
|
||||
+ assert LINUX_TRIGGER_V2 in user_data
|
||||
--
|
||||
2.33.0
|
||||
|
||||
219
CVE-2023-5115.patch
Normal file
219
CVE-2023-5115.patch
Normal file
@ -0,0 +1,219 @@
|
||||
From: Matt Martz <matt@sivel.net>
|
||||
Date: Tue, 26 Sep 2023 11:32:19 -0500
|
||||
Subject: CVE-2023-5115 Prevent roles from using symlinks to overwrite files
|
||||
outside of the installation directory (#81780)
|
||||
|
||||
* Sanitize linkname during role installs
|
||||
|
||||
* Add tests
|
||||
|
||||
* add clog frag.
|
||||
(cherry picked from commit ddf0311c63287e2d5334770377350c1e0cbfff28)
|
||||
origin: https://github.com/sivel/ansible/commit/638f5988f32eb9ec3f931d5544c2294e0631154d.patch
|
||||
https://salsa.debian.org/python-team/packages/ansible/-/blob/debian/2.7.7+dfsg-1+deb10u2/debian/patches/0030-CVE-2023-5115-Prevent-roles-from-using-symlinks-to-o.patch?ref_type=tags
|
||||
|
||||
[debian backport] add to no_native import
|
||||
---
|
||||
changelogs/fragments/cve-2023-5115.yml | 3 ++
|
||||
lib/ansible/galaxy/role.py | 37 +++++++++++++++---
|
||||
.../targets/ansible-galaxy-role/aliases | 3 ++
|
||||
.../files/create-role-archive.py | 45 ++++++++++++++++++++++
|
||||
.../targets/ansible-galaxy-role/meta/main.yml | 0
|
||||
.../ansible-galaxy-role/tasks/dir-traversal.yml | 44 +++++++++++++++++++++
|
||||
.../targets/ansible-galaxy-role/tasks/main.yml | 4 ++
|
||||
7 files changed, 130 insertions(+), 6 deletions(-)
|
||||
create mode 100644 changelogs/fragments/cve-2023-5115.yml
|
||||
create mode 100644 test/integration/targets/ansible-galaxy-role/aliases
|
||||
create mode 100755 test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
|
||||
create mode 100644 test/integration/targets/ansible-galaxy-role/meta/main.yml
|
||||
create mode 100644 test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
|
||||
create mode 100644 test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
|
||||
diff --git a/changelogs/fragments/cve-2023-5115.yml b/changelogs/fragments/cve-2023-5115.yml
|
||||
new file mode 100644
|
||||
index 0000000..69e0ddb
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/cve-2023-5115.yml
|
||||
@@ -0,0 +1,3 @@
|
||||
+security_fixes:
|
||||
+- ansible-galaxy - Prevent roles from using symlinks to overwrite
|
||||
+ files outside of the installation directory (CVE-2023-5115)
|
||||
diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py
|
||||
index 24075bf..2cca4c4 100644
|
||||
--- a/lib/ansible/galaxy/role.py
|
||||
+++ b/lib/ansible/galaxy/role.py
|
||||
@@ -33,6 +33,7 @@ from shutil import rmtree
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.urls import open_url
|
||||
+from ansible.module_utils._text import to_native
|
||||
from ansible.playbook.role.requirement import RoleRequirement
|
||||
from ansible.galaxy.api import GalaxyAPI
|
||||
|
||||
@@ -316,12 +317,36 @@ class GalaxyRole(object):
|
||||
# bits that might be in the file for security purposes
|
||||
# and drop any containing directory, as mentioned above
|
||||
if member.isreg() or member.issym():
|
||||
- parts = member.name.replace(archive_parent_dir, "", 1).split(os.sep)
|
||||
- final_parts = []
|
||||
- for part in parts:
|
||||
- if part != '..' and '~' not in part and '$' not in part:
|
||||
- final_parts.append(part)
|
||||
- member.name = os.path.join(*final_parts)
|
||||
+ for attr in ('name', 'linkname'):
|
||||
+ attr_value = getattr(member, attr, None)
|
||||
+ if not attr_value:
|
||||
+ continue
|
||||
+ n_attr_value = to_native(attr_value)
|
||||
+ n_archive_parent_dir = to_native(archive_parent_dir)
|
||||
+ n_parts = n_attr_value.replace(n_archive_parent_dir, "", 1).split(os.sep)
|
||||
+ n_final_parts = []
|
||||
+ for n_part in n_parts:
|
||||
+ # TODO if the condition triggers it produces a broken installation.
|
||||
+ # It will create the parent directory as an empty file and will
|
||||
+ # explode if the directory contains valid files.
|
||||
+ # Leaving this as is since the whole module needs a rewrite.
|
||||
+ #
|
||||
+ # Check if we have any files with illegal names,
|
||||
+ # and display a warning if so. This could help users
|
||||
+ # to debug a broken installation.
|
||||
+ if not n_part:
|
||||
+ continue
|
||||
+ if n_part == '..':
|
||||
+ display.warning(f"Illegal filename '{n_part}': '..' is not allowed")
|
||||
+ continue
|
||||
+ if n_part.startswith('~'):
|
||||
+ display.warning(f"Illegal filename '{n_part}': names cannot start with '~'")
|
||||
+ continue
|
||||
+ if '$' in n_part:
|
||||
+ display.warning(f"Illegal filename '{n_part}': names cannot contain '$'")
|
||||
+ continue
|
||||
+ n_final_parts.append(n_part)
|
||||
+ setattr(member, attr, os.path.join(*n_final_parts))
|
||||
role_tar_file.extract(member, self.path)
|
||||
|
||||
# write out the install info file for later use
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/aliases b/test/integration/targets/ansible-galaxy-role/aliases
|
||||
new file mode 100644
|
||||
index 0000000..bf63a1d
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/aliases
|
||||
@@ -0,0 +1,3 @@
|
||||
+destructive
|
||||
+shippable/posix/group3
|
||||
+needs/target/setup_remote_tmp_dir
|
||||
\ No newline at end of file
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
|
||||
new file mode 100755
|
||||
index 0000000..cfd908c
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py
|
||||
@@ -0,0 +1,45 @@
|
||||
+#!/usr/bin/env python
|
||||
+"""Create a role archive which overwrites an arbitrary file."""
|
||||
+
|
||||
+import argparse
|
||||
+import pathlib
|
||||
+import tarfile
|
||||
+import tempfile
|
||||
+
|
||||
+
|
||||
+def main() -> None:
|
||||
+ parser = argparse.ArgumentParser(description=__doc__)
|
||||
+ parser.add_argument('archive', type=pathlib.Path, help='archive to create')
|
||||
+ parser.add_argument('content', type=pathlib.Path, help='content to write')
|
||||
+ parser.add_argument('target', type=pathlib.Path, help='file to overwrite')
|
||||
+
|
||||
+ args = parser.parse_args()
|
||||
+
|
||||
+ create_archive(args.archive, args.content, args.target)
|
||||
+
|
||||
+
|
||||
+def create_archive(archive_path: pathlib.Path, content_path: pathlib.Path, target_path: pathlib.Path) -> None:
|
||||
+ with (
|
||||
+ tarfile.open(name=archive_path, mode='w') as role_archive,
|
||||
+ tempfile.TemporaryDirectory() as temp_dir_name,
|
||||
+ ):
|
||||
+ temp_dir_path = pathlib.Path(temp_dir_name)
|
||||
+
|
||||
+ meta_main_path = temp_dir_path / 'meta' / 'main.yml'
|
||||
+ meta_main_path.parent.mkdir()
|
||||
+ meta_main_path.write_text('')
|
||||
+
|
||||
+ symlink_path = temp_dir_path / 'symlink'
|
||||
+ symlink_path.symlink_to(target_path)
|
||||
+
|
||||
+ role_archive.add(meta_main_path)
|
||||
+ role_archive.add(symlink_path)
|
||||
+
|
||||
+ content_tarinfo = role_archive.gettarinfo(content_path, str(symlink_path))
|
||||
+
|
||||
+ with content_path.open('rb') as content_file:
|
||||
+ role_archive.addfile(content_tarinfo, content_file)
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ main()
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/meta/main.yml b/test/integration/targets/ansible-galaxy-role/meta/main.yml
|
||||
new file mode 100644
|
||||
index 0000000..e69de29
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
|
||||
new file mode 100644
|
||||
index 0000000..c70e899
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml
|
||||
@@ -0,0 +1,44 @@
|
||||
+- name: create test directories
|
||||
+ file:
|
||||
+ path: '{{ remote_tmp_dir }}/dir-traversal/{{ item }}'
|
||||
+ state: directory
|
||||
+ loop:
|
||||
+ - source
|
||||
+ - target
|
||||
+ - roles
|
||||
+
|
||||
+- name: create test content
|
||||
+ copy:
|
||||
+ dest: '{{ remote_tmp_dir }}/dir-traversal/source/content.txt'
|
||||
+ content: |
|
||||
+ some content to write
|
||||
+
|
||||
+- name: build dangerous dir traversal role
|
||||
+ script:
|
||||
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
|
||||
+ cmd: create-role-archive.py dangerous.tar content.txt {{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt
|
||||
+ executable: '{{ ansible_playbook_python }}'
|
||||
+
|
||||
+- name: install dangerous role
|
||||
+ command:
|
||||
+ cmd: ansible-galaxy role install --roles-path '{{ remote_tmp_dir }}/dir-traversal/roles' dangerous.tar
|
||||
+ chdir: '{{ remote_tmp_dir }}/dir-traversal/source'
|
||||
+ ignore_errors: true
|
||||
+ register: galaxy_install_dangerous
|
||||
+
|
||||
+- name: check for overwritten file
|
||||
+ stat:
|
||||
+ path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt'
|
||||
+ register: dangerous_overwrite_stat
|
||||
+
|
||||
+- name: get overwritten content
|
||||
+ slurp:
|
||||
+ path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt'
|
||||
+ register: dangerous_overwrite_content
|
||||
+ when: dangerous_overwrite_stat.stat.exists
|
||||
+
|
||||
+- assert:
|
||||
+ that:
|
||||
+ - dangerous_overwrite_content.content|default('')|b64decode == ''
|
||||
+ - not dangerous_overwrite_stat.stat.exists
|
||||
+ - galaxy_install_dangerous is failed
|
||||
diff --git a/test/integration/targets/ansible-galaxy-role/tasks/main.yml b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
new file mode 100644
|
||||
index 0000000..3deb8b5
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||
@@ -0,0 +1,4 @@
|
||||
+- name: Install role from Galaxy (should not fail with AttributeError)
|
||||
+ command: ansible-galaxy role install ansible.nope -vvvv --ignore-errors
|
||||
+
|
||||
+- import_tasks: dir-traversal.yml
|
||||
91
CVE-2024-0690.patch
Normal file
91
CVE-2024-0690.patch
Normal file
@ -0,0 +1,91 @@
|
||||
From beb04bc2642c208447c5a936f94310528a1946b1 Mon Sep 17 00:00:00 2001
|
||||
From: Matt Martz <matt@sivel.net>
|
||||
Date: Thu, 18 Jan 2024 17:17:23 -0600
|
||||
Subject: [PATCH] [stable-2.14] Ensure ANSIBLE_NO_LOG is respected
|
||||
(CVE-2024-0690) (#82565) (#82568)
|
||||
|
||||
Origin: https://github.com/ansible/ansible/commit/beb04bc2642c208447c5a936f94310528a1946b1
|
||||
|
||||
(cherry picked from commit 6935c8e)
|
||||
|
||||
---
|
||||
changelogs/fragments/cve-2024-0690.yml | 2 ++
|
||||
lib/ansible/playbook/base.py | 2 +-
|
||||
lib/ansible/playbook/play_context.py | 4 ----
|
||||
test/integration/targets/no_log/no_log_config.yml | 13 +++++++++++++
|
||||
test/integration/targets/no_log/runme.sh | 5 +++++
|
||||
5 files changed, 21 insertions(+), 5 deletions(-)
|
||||
create mode 100644 changelogs/fragments/cve-2024-0690.yml
|
||||
create mode 100644 test/integration/targets/no_log/no_log_config.yml
|
||||
|
||||
diff --git a/changelogs/fragments/cve-2024-0690.yml b/changelogs/fragments/cve-2024-0690.yml
|
||||
new file mode 100644
|
||||
index 0000000..0e030d8
|
||||
--- /dev/null
|
||||
+++ b/changelogs/fragments/cve-2024-0690.yml
|
||||
@@ -0,0 +1,2 @@
|
||||
+security_fixes:
|
||||
+- ANSIBLE_NO_LOG - Address issue where ANSIBLE_NO_LOG was ignored (CVE-2024-0690)
|
||||
diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py
|
||||
index 5fae511..733fa83 100644
|
||||
--- a/lib/ansible/playbook/base.py
|
||||
+++ b/lib/ansible/playbook/base.py
|
||||
@@ -154,7 +154,7 @@ class Base(with_metaclass(BaseMeta, object)):
|
||||
|
||||
# flags and misc. settings
|
||||
_environment = FieldAttribute(isa='list', extend=True, prepend=True)
|
||||
- _no_log = FieldAttribute(isa='bool')
|
||||
+ _no_log = FieldAttribute(isa='bool', default=C.DEFAULT_NO_LOG)
|
||||
_always_run = FieldAttribute(isa='bool')
|
||||
_run_once = FieldAttribute(isa='bool')
|
||||
_ignore_errors = FieldAttribute(isa='bool')
|
||||
diff --git a/lib/ansible/playbook/play_context.py b/lib/ansible/playbook/play_context.py
|
||||
index ea8bb87..8ca03cd 100644
|
||||
--- a/lib/ansible/playbook/play_context.py
|
||||
+++ b/lib/ansible/playbook/play_context.py
|
||||
@@ -432,10 +432,6 @@ class PlayContext(Base):
|
||||
new_info.connection_user = new_info.remote_user
|
||||
new_info.remote_user = pwd.getpwuid(os.getuid()).pw_name
|
||||
|
||||
- # set no_log to default if it was not previously set
|
||||
- if new_info.no_log is None:
|
||||
- new_info.no_log = C.DEFAULT_NO_LOG
|
||||
-
|
||||
if task.always_run:
|
||||
display.deprecated("always_run is deprecated. Use check_mode = no instead.", version="2.4", removed=False)
|
||||
new_info.check_mode = False
|
||||
diff --git a/test/integration/targets/no_log/no_log_config.yml b/test/integration/targets/no_log/no_log_config.yml
|
||||
new file mode 100644
|
||||
index 0000000..8a50880
|
||||
--- /dev/null
|
||||
+++ b/test/integration/targets/no_log/no_log_config.yml
|
||||
@@ -0,0 +1,13 @@
|
||||
+- hosts: testhost
|
||||
+ gather_facts: false
|
||||
+ tasks:
|
||||
+ - debug:
|
||||
+ no_log: true
|
||||
+
|
||||
+ - debug:
|
||||
+ no_log: false
|
||||
+
|
||||
+ - debug:
|
||||
+
|
||||
+ - debug:
|
||||
+ loop: '{{ range(3) }}'
|
||||
diff --git a/test/integration/targets/no_log/runme.sh b/test/integration/targets/no_log/runme.sh
|
||||
index e20bb08..b78dc94 100755
|
||||
--- a/test/integration/targets/no_log/runme.sh
|
||||
+++ b/test/integration/targets/no_log/runme.sh
|
||||
@@ -7,3 +7,8 @@ set -eux
|
||||
|
||||
[ "$(ansible-playbook no_log_local.yml -i ../../inventory -vvvvv "$@" | awk \
|
||||
'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "26/0" ]
|
||||
+
|
||||
+# test variations on ANSIBLE_NO_LOG
|
||||
+[ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
|
||||
+[ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ]
|
||||
+[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ]
|
||||
--
|
||||
2.33.0
|
||||
|
||||
38
ansible-2.5.5-openEuler-hostname.patch
Normal file
38
ansible-2.5.5-openEuler-hostname.patch
Normal file
@ -0,0 +1,38 @@
|
||||
diff --color -Nur ansible-2.5.5.orig/lib/ansible/modules/system/hostname.py ansible-2.5.5/lib/ansible/modules/system/hostname.py
|
||||
--- ansible-2.5.5.orig/lib/ansible/modules/system/hostname.py 2018-06-15 05:20:41.000000000 +0800
|
||||
+++ ansible-2.5.5/lib/ansible/modules/system/hostname.py 2021-12-16 16:28:49.652405482 +0800
|
||||
@@ -762,6 +762,10 @@
|
||||
distribution = 'Neon'
|
||||
strategy_class = DebianStrategy
|
||||
|
||||
+class OpenEulerHostname(Hostname):
|
||||
+ platform = 'Linux'
|
||||
+ distribution = 'Openeuler'
|
||||
+ strategy_class = SystemdStrategy
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
diff --color -Nur ansible-2.5.5.orig/lib/ansible/module_utils/facts/system/distribution.py ansible-2.5.5/lib/ansible/module_utils/facts/system/distribution.py
|
||||
--- ansible-2.5.5.orig/lib/ansible/module_utils/facts/system/distribution.py 2018-06-15 05:20:41.000000000 +0800
|
||||
+++ ansible-2.5.5/lib/ansible/module_utils/facts/system/distribution.py 2021-12-16 14:16:19.859943412 +0800
|
||||
@@ -408,7 +408,7 @@
|
||||
# keep keys in sync with Conditionals page of docs
|
||||
OS_FAMILY_MAP = {'RedHat': ['RedHat', 'Fedora', 'CentOS', 'Scientific', 'SLC',
|
||||
'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS',
|
||||
- 'OEL', 'Amazon', 'Virtuozzo', 'XenServer'],
|
||||
+ 'OEL', 'Amazon', 'Virtuozzo', 'XenServer', 'openEuler'],
|
||||
'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon',
|
||||
'Linux Mint'],
|
||||
'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed',
|
||||
diff --color -Nur ansible-2.5.5.orig/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py ansible-2.5.5/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py
|
||||
--- ansible-2.5.5.orig/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py 2018-06-15 05:20:41.000000000 +0800
|
||||
+++ ansible-2.5.5/test/sanity/import/lib/ansible/module_utils/facts/system/distribution.py 2021-12-16 14:16:19.859943412 +0800
|
||||
@@ -408,7 +408,7 @@
|
||||
# keep keys in sync with Conditionals page of docs
|
||||
OS_FAMILY_MAP = {'RedHat': ['RedHat', 'Fedora', 'CentOS', 'Scientific', 'SLC',
|
||||
'Ascendos', 'CloudLinux', 'PSBM', 'OracleLinux', 'OVS',
|
||||
- 'OEL', 'Amazon', 'Virtuozzo', 'XenServer'],
|
||||
+ 'OEL', 'Amazon', 'Virtuozzo', 'XenServer', 'openEuler'],
|
||||
'Debian': ['Debian', 'Ubuntu', 'Raspbian', 'Neon', 'KDE neon',
|
||||
'Linux Mint'],
|
||||
'Suse': ['SuSE', 'SLES', 'SLED', 'openSUSE', 'openSUSE Tumbleweed',
|
||||
39
ansible.spec
39
ansible.spec
@ -3,7 +3,7 @@
|
||||
Name: ansible
|
||||
Summary: SSH-based configuration management, deployment, and task execution system
|
||||
Version: 2.5.5
|
||||
Release: 3
|
||||
Release: 8
|
||||
License: Python-2.0 and MIT and GPL+
|
||||
Url: http://ansible.com
|
||||
Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz
|
||||
@ -12,16 +12,19 @@ Patch100: ansible-newer-jinja.patch
|
||||
Patch101: CVE-2019-14904.patch
|
||||
Patch102: CVE-2020-10684.patch
|
||||
Patch103: CVE-2020-10729.patch
|
||||
Patch104: CVE-2020-1735.patch
|
||||
Patch105: CVE-2020-1736.patch
|
||||
Patch106: CVE-2020-1737.patch
|
||||
Patch107: CVE-2020-1738.patch
|
||||
Patch108: CVE-2020-1739.patch
|
||||
Patch109: CVE-2020-1740.patch
|
||||
Patch110: CVE-2020-1753.patch
|
||||
Patch111: CVE-2021-20191.patch
|
||||
Patch112: CVE-2019-10156-1.patch
|
||||
Patch113: CVE-2019-10156-2.patch
|
||||
Patch114: CVE-2020-1735.patch
|
||||
Patch115: CVE-2019-3828.patch
|
||||
Patch116: ansible-2.5.5-openEuler-hostname.patch
|
||||
Patch117: CVE-2024-0690.patch
|
||||
Patch118: CVE-2022-3697.patch
|
||||
Patch119: CVE-2023-5115.patch
|
||||
BuildArch: noarch
|
||||
Provides: ansible-fireball = %{version}-%{release}
|
||||
Obsoletes: ansible-fireball < 1.2.4
|
||||
@ -41,7 +44,7 @@ Provides: ansible-python3 = %{version}-%{release}
|
||||
Obsoletes: ansible-python3 < %{version}-%{release}
|
||||
BuildRequires: python3-devel python3-setuptools
|
||||
BuildRequires: python3-PyYAML python3-paramiko python3-crypto python3-packaging
|
||||
BuildRequires: python3-pexpect python3-winrm
|
||||
BuildRequires: python3-pexpect python3-pywinrm
|
||||
BuildRequires: git-core
|
||||
%if %with_docs
|
||||
BuildRequires: python3-sphinx python3-sphinx-theme-alabaster asciidoc
|
||||
@ -79,17 +82,19 @@ This package installs extensive documentation for ansible
|
||||
%patch101 -p1
|
||||
%patch102 -p1
|
||||
%patch103 -p1
|
||||
%patch104 -p1
|
||||
%patch105 -p1
|
||||
%patch106 -p1
|
||||
%patch107 -p1
|
||||
%patch108 -p1
|
||||
%patch109 -p1
|
||||
%patch110 -p1
|
||||
%patch111 -p1
|
||||
%patch112 -p1
|
||||
%patch113 -p1
|
||||
|
||||
%patch114 -p1
|
||||
%patch115 -p1
|
||||
%patch116 -p1
|
||||
%patch117 -p1
|
||||
%patch118 -p1
|
||||
%patch119 -p1
|
||||
%if 0%{?with_python3}
|
||||
rm -rf %{py3dir}
|
||||
cp -a . %{py3dir}
|
||||
@ -150,6 +155,22 @@ cp -pr docs/docsite/rst .
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Sat Feb 08 2025 wangkai <13474090681@163.com> - 2.5.5-8
|
||||
- Fix CVE-2022-3697 CVE-2023-5115
|
||||
|
||||
* Mon Feb 05 2024 wangkai <13474090681@163.com> - 2.5.5-7
|
||||
- Fix CVE-2024-0690
|
||||
|
||||
* Fri Oct 13 2023 Ge Wang <wang__ge@126.com> - 2.5.5-6
|
||||
- Change BuildRequire python3-winrm to python3-pywinrm
|
||||
|
||||
* Mon Feb 28 2022 wangkai <wangkai385@huawei.com> - 2.5.5-5
|
||||
- Fix CVE-2019-3828 and hostname module support openEuler
|
||||
- and add defination of is_subpath and remove module_response_deepcopy
|
||||
|
||||
* Thu Oct 28 2021 liwu <liwu13@huawei.com> - 2.5.5-4
|
||||
- The upstream community rolls back the patch
|
||||
|
||||
* Wed Oct 13 2021 yaoxin <yaoxin30@huawei.com> - 2.5.5-3
|
||||
- Fix CVE-2019-10156
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user