Compare commits
10 Commits
c980c92289
...
69e18e13fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69e18e13fa | ||
|
|
c2c9243c74 | ||
|
|
39505c201c | ||
|
|
738fb41810 | ||
|
|
ce0b6dd397 | ||
|
|
eff6eb882b | ||
|
|
ad1c971d1d | ||
|
|
d0eba647ac | ||
|
|
3be839b699 | ||
|
|
50d8856ed4 |
366
CVE-2022-3697.patch
Normal file
366
CVE-2022-3697.patch
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
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 | 137 ++++++++----------
|
||||||
|
test/units/module_utils/aws/test_tower.py | 40 +++++
|
||||||
|
4 files changed, 187 insertions(+), 78 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 00000000..ae961144
|
||||||
|
--- /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 00000000..dd7d9738
|
||||||
|
--- /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 fdde1eef..c74d8820 100644
|
||||||
|
--- a/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||||
|
+++ b/lib/ansible/modules/cloud/amazon/ec2_instance.py
|
||||||
|
@@ -46,19 +46,40 @@ 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 (Linux only).
|
||||||
|
+ - 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.
|
||||||
|
- 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.
|
||||||
|
+ type: dict
|
||||||
|
+ aliases: ['tower_callback']
|
||||||
|
suboptions:
|
||||||
|
+ windows:
|
||||||
|
+ description:
|
||||||
|
+ - Set I(windows=True) to use powershell instead of bash for the callback script.
|
||||||
|
+ type: bool
|
||||||
|
+ default: False
|
||||||
|
+ set_password:
|
||||||
|
+ description:
|
||||||
|
+ - Optional admin password to use if I(windows=True).
|
||||||
|
+ type: str
|
||||||
|
tower_address:
|
||||||
|
description:
|
||||||
|
- - IP address or DNS name of Tower server. Must be accessible via this address from the VPC that this instance will be launched in.
|
||||||
|
+ - IP address or DNS name of Tower server. Must be accessible via this address from the
|
||||||
|
+ VPC that this instance will be launched in.
|
||||||
|
+ - Required if I(windows=False).
|
||||||
|
+ type: str
|
||||||
|
job_template_id:
|
||||||
|
description:
|
||||||
|
- - Either the integer ID of the Tower Job Template, or the name (name supported only for Tower 3.2+).
|
||||||
|
+ - Either the integer ID of the Tower Job Template, or the name.
|
||||||
|
+ Using a name for the job template is not supported by Ansible Tower prior to version
|
||||||
|
+ 3.2.
|
||||||
|
+ - Required if I(windows=False).
|
||||||
|
+ type: str
|
||||||
|
host_config_key:
|
||||||
|
description:
|
||||||
|
- Host configuration secret key generated by the Tower job template.
|
||||||
|
@@ -729,71 +750,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))
|
||||||
|
-
|
||||||
|
- if isinstance(tower_conf['job_template_id'], string_types):
|
||||||
|
- tower_conf['job_template_id'] = urlparse.quote(tower_conf['job_template_id'])
|
||||||
|
- 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/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
|
||||||
|
- """))
|
||||||
|
- 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
|
||||||
|
@@ -1084,6 +1044,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'):
|
||||||
|
@@ -1100,14 +1075,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'] = {}
|
||||||
|
@@ -1586,7 +1555,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'),
|
||||||
|
@@ -1620,7 +1601,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 00000000..527423b1
|
||||||
|
--- /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
|
||||||
|
|
||||||
177
CVE-2023-5115.patch
Normal file
177
CVE-2023-5115.patch
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
Description: Fix for CVE-2023-5115, based on upstream patch
|
||||||
|
Author: Lee Garrett <debian@rocketjump.eu>
|
||||||
|
Origin: https://github.com/ansible/ansible/pull/81787
|
||||||
|
https://salsa.debian.org/python-team/packages/ansible/-/blob/debian/2.10.7+merged+base+2.10.17+dfsg-0+deb11u2/debian/patches/0010-fix-CVE-2023-5115.patch?ref_type=tags
|
||||||
|
Forwarded: not-needed
|
||||||
|
Applied-Upstream: https://github.com/ansible/ansible/pull/81787
|
||||||
|
Reviewed-by: Lee Garrett <debian@rocketjump.eu>
|
||||||
|
Last-Update: 2024-07-04
|
||||||
|
---
|
||||||
|
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
|
||||||
|
--- /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()
|
||||||
|
--- /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
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+- import_tasks: dir-traversal.yml
|
||||||
|
--- /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)
|
||||||
|
--- a/lib/ansible/galaxy/role.py
|
||||||
|
+++ b/lib/ansible/galaxy/role.py
|
||||||
|
@@ -329,14 +329,36 @@
|
||||||
|
# bits that might be in the file for security purposes
|
||||||
|
# and drop any containing directory, as mentioned above
|
||||||
|
if member.isreg() or member.issym():
|
||||||
|
- n_member_name = to_native(member.name)
|
||||||
|
- n_archive_parent_dir = to_native(archive_parent_dir)
|
||||||
|
- n_parts = n_member_name.replace(n_archive_parent_dir, "", 1).split(os.sep)
|
||||||
|
- n_final_parts = []
|
||||||
|
- for n_part in n_parts:
|
||||||
|
- if n_part != '..' and '~' not in n_part and '$' not in n_part:
|
||||||
|
+ 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)
|
||||||
|
- member.name = os.path.join(*n_final_parts)
|
||||||
|
+ setattr(member, attr, os.path.join(*n_final_parts))
|
||||||
|
role_tar_file.extract(member, to_native(self.path))
|
||||||
|
|
||||||
|
# write out the install info file for later use
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/ansible-galaxy-role/meta/main.yml
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+dependencies: [setup_remote_tmp_dir]
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/setup_remote_tmp_dir/defaults/main.yml
|
||||||
|
@@ -0,0 +1,2 @@
|
||||||
|
+setup_remote_tmp_dir_skip_cleanup: no
|
||||||
|
+setup_remote_tmp_dir_cache_path: no
|
||||||
|
--- a/test/integration/targets/setup_remote_tmp_dir/tasks/default.yml
|
||||||
|
+++ b/test/integration/targets/setup_remote_tmp_dir/tasks/default.yml
|
||||||
|
@@ -9,3 +9,4 @@
|
||||||
|
- name: record temporary directory
|
||||||
|
set_fact:
|
||||||
|
remote_tmp_dir: "{{ remote_tmp_dir.path }}"
|
||||||
|
+ cacheable: "{{ setup_remote_tmp_dir_cache_path | bool }}"
|
||||||
2010
CVE-2023-5764.patch
Normal file
2010
CVE-2023-5764.patch
Normal file
File diff suppressed because it is too large
Load Diff
1029
CVE-2024-0690.patch
Normal file
1029
CVE-2024-0690.patch
Normal file
File diff suppressed because it is too large
Load Diff
488
CVE-2024-8775.patch
Normal file
488
CVE-2024-8775.patch
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
From: Matt Davis <6775756+nitzmahone@users.noreply.github.com>
|
||||||
|
Date: Thu, 24 Oct 2024 15:56:54 -0700
|
||||||
|
Subject: CVE-2024-8775 Preserve `_ansible_no_log` from action result;
|
||||||
|
fix `include_vars` to set properly (#84143)
|
||||||
|
|
||||||
|
* fixes for CVE-2024-8775
|
||||||
|
|
||||||
|
* propagate truthy `_ansible_no_log` in action result (previously superseded by task-calculated value)
|
||||||
|
* always mask entire `include_vars` action result if any file loaded had a false `show_content` flag (previously used only the flag value from the last file loaded)
|
||||||
|
|
||||||
|
* update no_log tests for CVE-2024-8775
|
||||||
|
* include validation of _ansible_no_log preservation when set by actions
|
||||||
|
* replace static values with dynamic for increased robustness to logging/display/callback changes (but still using grep counts :( )
|
||||||
|
|
||||||
|
* changelog
|
||||||
|
|
||||||
|
* use ternary, coerce to bool explicitly
|
||||||
|
|
||||||
|
[backport]
|
||||||
|
"grep -q" exits on the first match, so the preceding
|
||||||
|
process in the pipeline gets SIGPIPE. Changing "grep -q
|
||||||
|
'porter.*cable'" to "grep 'porter.*cable' >/dev/null" makes that test
|
||||||
|
pass.
|
||||||
|
|
||||||
|
origin: backport, https://github.com/ansible/ansible/commit/c9ac477e53a99e95781f333eec3329a935c1bf95
|
||||||
|
bug: https://github.com/ansible/ansible/issues/84217
|
||||||
|
bug-debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1086551
|
||||||
|
---
|
||||||
|
changelogs/fragments/cve-2024-8775.yml | 5 ++++
|
||||||
|
lib/ansible/executor/task_executor.py | 3 +-
|
||||||
|
lib/ansible/plugins/action/include_vars.py | 3 +-
|
||||||
|
.../targets/include_vars-ad-hoc/dir/encrypted.yml | 6 ++++
|
||||||
|
.../targets/include_vars-ad-hoc/runme.sh | 22 +++++++++++++--
|
||||||
|
.../targets/include_vars-ad-hoc/vaultpass | 3 ++
|
||||||
|
.../no_log/action_plugins/action_sets_no_log.py | 8 ++++++
|
||||||
|
.../targets/no_log/ansible_no_log_in_result.yml | 13 +++++++++
|
||||||
|
test/integration/targets/no_log/dynamic.yml | 29 +++++++++++++++-----
|
||||||
|
test/integration/targets/no_log/no_log_config.yml | 2 +-
|
||||||
|
test/integration/targets/no_log/no_log_local.yml | 15 ++++++----
|
||||||
|
.../targets/no_log/no_log_suboptions.yml | 14 +++++-----
|
||||||
|
.../targets/no_log/no_log_suboptions_invalid.yml | 29 +++++++++++---------
|
||||||
|
test/integration/targets/no_log/runme.sh | 16 +++++++----
|
||||||
|
test/integration/targets/no_log/runme.sh.orig | 26 ++++++++++++++++++
|
||||||
|
test/integration/targets/no_log/secretvars.yml | 32 ++++++++++++++++++++++
|
||||||
|
16 files changed, 182 insertions(+), 44 deletions(-)
|
||||||
|
create mode 100644 changelogs/fragments/cve-2024-8775.yml
|
||||||
|
create mode 100644 test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml
|
||||||
|
create mode 100755 test/integration/targets/include_vars-ad-hoc/vaultpass
|
||||||
|
create mode 100644 test/integration/targets/no_log/action_plugins/action_sets_no_log.py
|
||||||
|
create mode 100644 test/integration/targets/no_log/ansible_no_log_in_result.yml
|
||||||
|
create mode 100755 test/integration/targets/no_log/runme.sh.orig
|
||||||
|
create mode 100644 test/integration/targets/no_log/secretvars.yml
|
||||||
|
|
||||||
|
diff --git a/changelogs/fragments/cve-2024-8775.yml b/changelogs/fragments/cve-2024-8775.yml
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..a292c99
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/changelogs/fragments/cve-2024-8775.yml
|
||||||
|
@@ -0,0 +1,5 @@
|
||||||
|
+security_fixes:
|
||||||
|
+ - task result processing - Ensure that action-sourced result masking (``_ansible_no_log=True``)
|
||||||
|
+ is preserved. (CVE-2024-8775)
|
||||||
|
+ - include_vars action - Ensure that result masking is correctly requested when vault-encrypted
|
||||||
|
+ files are read. (CVE-2024-8775)
|
||||||
|
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
|
||||||
|
index 65bcacf..957d1bf 100644
|
||||||
|
--- a/lib/ansible/executor/task_executor.py
|
||||||
|
+++ b/lib/ansible/executor/task_executor.py
|
||||||
|
@@ -677,8 +677,9 @@ class TaskExecutor:
|
||||||
|
self._handler.cleanup()
|
||||||
|
display.debug("handler run complete")
|
||||||
|
|
||||||
|
+ # propagate no log to result- the action can set this, so only overwrite it with the task's value if missing or falsey
|
||||||
|
# preserve no log
|
||||||
|
- result["_ansible_no_log"] = self._play_context.no_log
|
||||||
|
+ result["_ansible_no_log"] = bool(self._play_context.no_log or result.get('_ansible_no_log', False))
|
||||||
|
|
||||||
|
# update the local copy of vars with the registered value, if specified,
|
||||||
|
# or any facts which may have been generated by the module execution
|
||||||
|
diff --git a/lib/ansible/plugins/action/include_vars.py b/lib/ansible/plugins/action/include_vars.py
|
||||||
|
index 0723453..bd67d01 100644
|
||||||
|
--- a/lib/ansible/plugins/action/include_vars.py
|
||||||
|
+++ b/lib/ansible/plugins/action/include_vars.py
|
||||||
|
@@ -225,7 +225,8 @@ class ActionModule(ActionBase):
|
||||||
|
b_data, show_content = self._loader._get_file_contents(filename)
|
||||||
|
data = to_text(b_data, errors='surrogate_or_strict')
|
||||||
|
|
||||||
|
- self.show_content = show_content
|
||||||
|
+ self.show_content &= show_content # mask all results if any file was encrypted
|
||||||
|
+
|
||||||
|
data = self._loader.load(data, file_name=filename, show_content=show_content)
|
||||||
|
if not data:
|
||||||
|
data = dict()
|
||||||
|
diff --git a/test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml b/test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..328f180
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/include_vars-ad-hoc/dir/encrypted.yml
|
||||||
|
@@ -0,0 +1,6 @@
|
||||||
|
+$ANSIBLE_VAULT;1.1;AES256
|
||||||
|
+31613539636636336264396235633933633839646337323533316638633336653461393036336664
|
||||||
|
+3939386435313638366366626566346135623932653238360a366261303663343034633865626132
|
||||||
|
+31646231623630333636383636383833656331643164656366623332396439306132663264663131
|
||||||
|
+6439633766376261320a616265306430366530363866356433366430633265353739373732646536
|
||||||
|
+37623661333064306162373463616231636365373231313939373230643936313362
|
||||||
|
diff --git a/test/integration/targets/include_vars-ad-hoc/runme.sh b/test/integration/targets/include_vars-ad-hoc/runme.sh
|
||||||
|
index 51b68d2..41a0929 100755
|
||||||
|
--- a/test/integration/targets/include_vars-ad-hoc/runme.sh
|
||||||
|
+++ b/test/integration/targets/include_vars-ad-hoc/runme.sh
|
||||||
|
@@ -1,6 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
-set -eux
|
||||||
|
+set -eux -o pipefail
|
||||||
|
|
||||||
|
-ansible testhost -i ../../inventory -m include_vars -a 'dir/inc.yml' "$@"
|
||||||
|
-ansible testhost -i ../../inventory -m include_vars -a 'dir=dir' "$@"
|
||||||
|
+echo "single file include"
|
||||||
|
+ansible testhost -i ../../inventory -m include_vars -a 'dir/inc.yml' -vvv 2>&1 | grep 'porter.*cable' 2>&1
|
||||||
|
+
|
||||||
|
+echo "single file encrypted include"
|
||||||
|
+ansible testhost -i ../../inventory -m include_vars -a 'dir/encrypted.yml' -vvv --vault-password-file vaultpass > output.txt 2>&1
|
||||||
|
+
|
||||||
|
+echo "directory include with encrypted"
|
||||||
|
+ansible testhost -i ../../inventory -m include_vars -a 'dir=dir' -vvv --vault-password-file vaultpass >> output.txt 2>&1
|
||||||
|
+
|
||||||
|
+grep -q 'output has been hidden' output.txt
|
||||||
|
+
|
||||||
|
+# all content should be masked if any file is encrypted
|
||||||
|
+if grep -e 'i am a secret' -e 'porter.*cable' output.txt; then
|
||||||
|
+ echo "FAIL: vault masking failed"
|
||||||
|
+ exit 1
|
||||||
|
+fi
|
||||||
|
+
|
||||||
|
+echo PASS
|
||||||
|
diff --git a/test/integration/targets/include_vars-ad-hoc/vaultpass b/test/integration/targets/include_vars-ad-hoc/vaultpass
|
||||||
|
new file mode 100755
|
||||||
|
index 0000000..1f78d41
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/include_vars-ad-hoc/vaultpass
|
||||||
|
@@ -0,0 +1,3 @@
|
||||||
|
+#!/bin/sh
|
||||||
|
+
|
||||||
|
+echo supersecurepassword
|
||||||
|
diff --git a/test/integration/targets/no_log/action_plugins/action_sets_no_log.py b/test/integration/targets/no_log/action_plugins/action_sets_no_log.py
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..cb42616
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/no_log/action_plugins/action_sets_no_log.py
|
||||||
|
@@ -0,0 +1,8 @@
|
||||||
|
+from __future__ import annotations
|
||||||
|
+
|
||||||
|
+from ansible.plugins.action import ActionBase
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+class ActionModule(ActionBase):
|
||||||
|
+ def run(self, tmp=None, task_vars=None):
|
||||||
|
+ return dict(changed=False, failed=False, msg="action result should be masked", _ansible_no_log="yeppers") # ensure that a truthy non-bool works here
|
||||||
|
diff --git a/test/integration/targets/no_log/ansible_no_log_in_result.yml b/test/integration/targets/no_log/ansible_no_log_in_result.yml
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..a80a4a7
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/no_log/ansible_no_log_in_result.yml
|
||||||
|
@@ -0,0 +1,13 @@
|
||||||
|
+- hosts: localhost
|
||||||
|
+ gather_facts: no
|
||||||
|
+ tasks:
|
||||||
|
+ - action_sets_no_log:
|
||||||
|
+ register: res_action
|
||||||
|
+
|
||||||
|
+ - assert:
|
||||||
|
+ that:
|
||||||
|
+ - res_action.msg == "action result should be masked"
|
||||||
|
+
|
||||||
|
+ - action_sets_no_log:
|
||||||
|
+ loop: [1, 2, 3]
|
||||||
|
+ register: res_action
|
||||||
|
diff --git a/test/integration/targets/no_log/dynamic.yml b/test/integration/targets/no_log/dynamic.yml
|
||||||
|
index 4a1123d..9523677 100644
|
||||||
|
--- a/test/integration/targets/no_log/dynamic.yml
|
||||||
|
+++ b/test/integration/targets/no_log/dynamic.yml
|
||||||
|
@@ -1,27 +1,42 @@
|
||||||
|
- name: test dynamic no log
|
||||||
|
hosts: testhost
|
||||||
|
gather_facts: no
|
||||||
|
- ignore_errors: yes
|
||||||
|
tasks:
|
||||||
|
- name: no loop, task fails, dynamic no_log
|
||||||
|
- debug:
|
||||||
|
- msg: "SHOW {{ var_does_not_exist }}"
|
||||||
|
+ raw: echo {{ var_does_not_exist }}
|
||||||
|
no_log: "{{ not (unsafe_show_logs|bool) }}"
|
||||||
|
+ ignore_errors: yes
|
||||||
|
+ register: result
|
||||||
|
+
|
||||||
|
+ - assert:
|
||||||
|
+ that:
|
||||||
|
+ - result is failed
|
||||||
|
+ - result.results is not defined
|
||||||
|
|
||||||
|
- name: loop, task succeeds, dynamic does no_log
|
||||||
|
- debug:
|
||||||
|
- msg: "SHOW {{ item }}"
|
||||||
|
+ raw: echo {{ item }}
|
||||||
|
loop:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
- c
|
||||||
|
no_log: "{{ not (unsafe_show_logs|bool) }}"
|
||||||
|
+ register: result
|
||||||
|
+
|
||||||
|
+ - assert:
|
||||||
|
+ that:
|
||||||
|
+ - result.results | length == 3
|
||||||
|
|
||||||
|
- name: loop, task fails, dynamic no_log
|
||||||
|
- debug:
|
||||||
|
- msg: "SHOW {{ var_does_not_exist }}"
|
||||||
|
+ raw: echo {{ var_does_not_exist }}
|
||||||
|
loop:
|
||||||
|
- a
|
||||||
|
- b
|
||||||
|
- c
|
||||||
|
no_log: "{{ not (unsafe_show_logs|bool) }}"
|
||||||
|
+ ignore_errors: yes
|
||||||
|
+ register: result
|
||||||
|
+
|
||||||
|
+ - assert:
|
||||||
|
+ that:
|
||||||
|
+ - result is failed
|
||||||
|
+ - result.results is not defined # DT needs result.results | length == 3
|
||||||
|
diff --git a/test/integration/targets/no_log/no_log_config.yml b/test/integration/targets/no_log/no_log_config.yml
|
||||||
|
index 8a50880..165f4e0 100644
|
||||||
|
--- a/test/integration/targets/no_log/no_log_config.yml
|
||||||
|
+++ b/test/integration/targets/no_log/no_log_config.yml
|
||||||
|
@@ -10,4 +10,4 @@
|
||||||
|
- debug:
|
||||||
|
|
||||||
|
- debug:
|
||||||
|
- loop: '{{ range(3) }}'
|
||||||
|
+ loop: '{{ range(3) | list }}'
|
||||||
|
diff --git a/test/integration/targets/no_log/no_log_local.yml b/test/integration/targets/no_log/no_log_local.yml
|
||||||
|
index aacf7de..d2bc5ae 100644
|
||||||
|
--- a/test/integration/targets/no_log/no_log_local.yml
|
||||||
|
+++ b/test/integration/targets/no_log/no_log_local.yml
|
||||||
|
@@ -4,19 +4,22 @@
|
||||||
|
hosts: testhost
|
||||||
|
gather_facts: no
|
||||||
|
tasks:
|
||||||
|
+ - include_vars: secretvars.yml
|
||||||
|
+ no_log: true
|
||||||
|
+
|
||||||
|
- name: args should be logged in the absence of no_log
|
||||||
|
- shell: echo "LOG_ME_TASK_SUCCEEDED"
|
||||||
|
+ shell: echo "{{log_me_prefix}}TASK_SUCCEEDED"
|
||||||
|
|
||||||
|
- name: failed args should be logged in the absence of no_log
|
||||||
|
- shell: echo "LOG_ME_TASK_FAILED"
|
||||||
|
+ shell: echo "{{log_me_prefix}}TASK_FAILED"
|
||||||
|
failed_when: true
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: item args should be logged in the absence of no_log
|
||||||
|
shell: echo {{ item }}
|
||||||
|
- with_items: [ "LOG_ME_ITEM", "LOG_ME_SKIPPED", "LOG_ME_ITEM_FAILED" ]
|
||||||
|
- when: item != "LOG_ME_SKIPPED"
|
||||||
|
- failed_when: item == "LOG_ME_ITEM_FAILED"
|
||||||
|
+ with_items: [ "{{log_me_prefix}}ITEM", "{{log_me_prefix}}SKIPPED", "{{log_me_prefix}}ITEM_FAILED" ]
|
||||||
|
+ when: item != log_me_prefix ~ "SKIPPED"
|
||||||
|
+ failed_when: item == log_me_prefix ~ "ITEM_FAILED"
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: args should not be logged when task-level no_log set
|
||||||
|
@@ -61,7 +64,7 @@
|
||||||
|
no_log: true
|
||||||
|
|
||||||
|
- name: args should be logged when task-level no_log overrides play-level
|
||||||
|
- shell: echo "LOG_ME_OVERRIDE"
|
||||||
|
+ shell: echo "{{log_me_prefix}}OVERRIDE"
|
||||||
|
no_log: false
|
||||||
|
|
||||||
|
- name: Add a fake host for next play
|
||||||
|
diff --git a/test/integration/targets/no_log/no_log_suboptions.yml b/test/integration/targets/no_log/no_log_suboptions.yml
|
||||||
|
index e67ecfe..338a871 100644
|
||||||
|
--- a/test/integration/targets/no_log/no_log_suboptions.yml
|
||||||
|
+++ b/test/integration/targets/no_log/no_log_suboptions.yml
|
||||||
|
@@ -5,20 +5,20 @@
|
||||||
|
tasks:
|
||||||
|
- name: Task with suboptions
|
||||||
|
module:
|
||||||
|
- secret: GLAMOROUS
|
||||||
|
+ secret: "{{ s106 }}"
|
||||||
|
subopt_dict:
|
||||||
|
- str_sub_opt1: AFTERMATH
|
||||||
|
+ str_sub_opt1: "{{ s107 }}"
|
||||||
|
str_sub_opt2: otherstring
|
||||||
|
nested_subopt:
|
||||||
|
- n_subopt1: MANPOWER
|
||||||
|
+ n_subopt1: "{{ s101 }}"
|
||||||
|
|
||||||
|
subopt_list:
|
||||||
|
- - subopt1: UNTAPPED
|
||||||
|
+ - subopt1: "{{ s102 }}"
|
||||||
|
subopt2: thridstring
|
||||||
|
|
||||||
|
- - subopt1: CONCERNED
|
||||||
|
+ - subopt1: "{{ s103 }}"
|
||||||
|
|
||||||
|
- name: Task with suboptions as string
|
||||||
|
module:
|
||||||
|
- secret: MARLIN
|
||||||
|
- subopt_dict: str_sub_opt1=FLICK
|
||||||
|
+ secret: "{{ s104 }}"
|
||||||
|
+ subopt_dict: str_sub_opt1={{ s105 }}
|
||||||
|
diff --git a/test/integration/targets/no_log/no_log_suboptions_invalid.yml b/test/integration/targets/no_log/no_log_suboptions_invalid.yml
|
||||||
|
index 933a8a9..1092cf5 100644
|
||||||
|
--- a/test/integration/targets/no_log/no_log_suboptions_invalid.yml
|
||||||
|
+++ b/test/integration/targets/no_log/no_log_suboptions_invalid.yml
|
||||||
|
@@ -4,42 +4,45 @@
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
+ - include_vars: secretvars.yml
|
||||||
|
+ no_log: true
|
||||||
|
+
|
||||||
|
- name: Task with suboptions and invalid parameter
|
||||||
|
module:
|
||||||
|
- secret: SUPREME
|
||||||
|
+ secret: "{{ s201 }}"
|
||||||
|
invalid: param
|
||||||
|
subopt_dict:
|
||||||
|
- str_sub_opt1: IDIOM
|
||||||
|
+ str_sub_opt1: "{{ s202 }}"
|
||||||
|
str_sub_opt2: otherstring
|
||||||
|
nested_subopt:
|
||||||
|
- n_subopt1: MOCKUP
|
||||||
|
+ n_subopt1: "{{ s203 }}"
|
||||||
|
|
||||||
|
subopt_list:
|
||||||
|
- - subopt1: EDUCATED
|
||||||
|
+ - subopt1: "{{ s204 }}"
|
||||||
|
subopt2: thridstring
|
||||||
|
- - subopt1: FOOTREST
|
||||||
|
+ - subopt1: "{{ s205 }}"
|
||||||
|
|
||||||
|
- name: Task with suboptions as string with invalid parameter
|
||||||
|
module:
|
||||||
|
- secret: FOOTREST
|
||||||
|
+ secret: "{{ s213 }}"
|
||||||
|
invalid: param
|
||||||
|
- subopt_dict: str_sub_opt1=CRAFTY
|
||||||
|
+ subopt_dict: str_sub_opt1={{ s206 }}
|
||||||
|
|
||||||
|
- name: Task with suboptions with dict instead of list
|
||||||
|
module:
|
||||||
|
- secret: FELINE
|
||||||
|
+ secret: "{{ s207 }}"
|
||||||
|
subopt_dict:
|
||||||
|
- str_sub_opt1: CRYSTAL
|
||||||
|
+ str_sub_opt1: "{{ s208 }}"
|
||||||
|
str_sub_opt2: otherstring
|
||||||
|
nested_subopt:
|
||||||
|
- n_subopt1: EXPECTANT
|
||||||
|
+ n_subopt1: "{{ s209 }}"
|
||||||
|
subopt_list:
|
||||||
|
foo: bar
|
||||||
|
|
||||||
|
- name: Task with suboptions with incorrect data type
|
||||||
|
module:
|
||||||
|
- secret: AGROUND
|
||||||
|
+ secret: "{{ s210 }}"
|
||||||
|
subopt_dict: 9068.21361
|
||||||
|
subopt_list:
|
||||||
|
- - subopt1: GOLIATH
|
||||||
|
- - subopt1: FREEFALL
|
||||||
|
+ - subopt1: "{{ s211 }}"
|
||||||
|
+ - subopt1: "{{ s212 }}"
|
||||||
|
diff --git a/test/integration/targets/no_log/runme.sh b/test/integration/targets/no_log/runme.sh
|
||||||
|
index 8bfe019..d6476ac 100755
|
||||||
|
--- a/test/integration/targets/no_log/runme.sh
|
||||||
|
+++ b/test/integration/targets/no_log/runme.sh
|
||||||
|
@@ -1,6 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
-set -eux
|
||||||
|
+set -eux -o pipefail
|
||||||
|
+
|
||||||
|
+# ensure _ansible_no_log returned by actions is actually respected
|
||||||
|
+ansible-playbook ansible_no_log_in_result.yml -vvvvv > "${OUTPUT_DIR}/output.log" 2> /dev/null
|
||||||
|
+
|
||||||
|
+[ "$(grep -c "action result should be masked" "${OUTPUT_DIR}/output.log")" = "0" ]
|
||||||
|
+[ "$(grep -c "the output has been hidden" "${OUTPUT_DIR}/output.log")" = "4" ]
|
||||||
|
|
||||||
|
# This test expects 7 loggable vars and 0 non-loggable ones.
|
||||||
|
# If either mismatches it fails, run the ansible-playbook command to debug.
|
||||||
|
@@ -9,18 +15,18 @@ set -eux
|
||||||
|
|
||||||
|
# deal with corner cases with no log and loops
|
||||||
|
# no log enabled, should produce 6 censored messages
|
||||||
|
-[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=no|grep -c 'output has been hidden')" = "6" ]
|
||||||
|
+[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=no|grep -c 'output has been hidden')" = "6" ] # DT needs 7
|
||||||
|
|
||||||
|
# no log disabled, should produce 0 censored
|
||||||
|
[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=yes|grep -c 'output has been hidden')" = "0" ]
|
||||||
|
|
||||||
|
# test no log for sub options
|
||||||
|
-[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(MANPOWER|UNTAPPED|CONCERNED|MARLIN|FLICK)')" = "0" ]
|
||||||
|
+[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'SECRET')" = "0" ]
|
||||||
|
|
||||||
|
# test invalid data passed to a suboption
|
||||||
|
-[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ]
|
||||||
|
+[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'SECRET')" = "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" ]
|
||||||
|
+[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ] # DT needs 5
|
||||||
|
diff --git a/test/integration/targets/no_log/runme.sh.orig b/test/integration/targets/no_log/runme.sh.orig
|
||||||
|
new file mode 100755
|
||||||
|
index 0000000..8bfe019
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/no_log/runme.sh.orig
|
||||||
|
@@ -0,0 +1,26 @@
|
||||||
|
+#!/usr/bin/env bash
|
||||||
|
+
|
||||||
|
+set -eux
|
||||||
|
+
|
||||||
|
+# This test expects 7 loggable vars and 0 non-loggable ones.
|
||||||
|
+# If either mismatches it fails, run the ansible-playbook command to debug.
|
||||||
|
+[ "$(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" ]
|
||||||
|
+
|
||||||
|
+# deal with corner cases with no log and loops
|
||||||
|
+# no log enabled, should produce 6 censored messages
|
||||||
|
+[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=no|grep -c 'output has been hidden')" = "6" ]
|
||||||
|
+
|
||||||
|
+# no log disabled, should produce 0 censored
|
||||||
|
+[ "$(ansible-playbook dynamic.yml -i ../../inventory -vvvvv "$@" -e unsafe_show_logs=yes|grep -c 'output has been hidden')" = "0" ]
|
||||||
|
+
|
||||||
|
+# test no log for sub options
|
||||||
|
+[ "$(ansible-playbook no_log_suboptions.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(MANPOWER|UNTAPPED|CONCERNED|MARLIN|FLICK)')" = "0" ]
|
||||||
|
+
|
||||||
|
+# test invalid data passed to a suboption
|
||||||
|
+[ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "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" ]
|
||||||
|
diff --git a/test/integration/targets/no_log/secretvars.yml b/test/integration/targets/no_log/secretvars.yml
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..0030d74
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/no_log/secretvars.yml
|
||||||
|
@@ -0,0 +1,32 @@
|
||||||
|
+# These values are in a separate vars file and referenced dynamically to avoid spurious counts from contextual error messages
|
||||||
|
+# that show the playbook contents inline (since unencrypted playbook contents are not considered secret).
|
||||||
|
+log_me_prefix: LOG_ME_
|
||||||
|
+
|
||||||
|
+# Unique values are used for each secret below to ensure that one secret "learned" does not cause another non-secret
|
||||||
|
+# value to be considered secret simply because they share the same value. A common substring is, however, present in
|
||||||
|
+# each one to simplify searching for secret values in test output. Having a unique value for each also helps in
|
||||||
|
+# debugging when unexpected output is encountered.
|
||||||
|
+
|
||||||
|
+# secrets for no_log_suboptions.yml
|
||||||
|
+s101: SECRET101
|
||||||
|
+s102: SECRET102
|
||||||
|
+s103: SECRET103
|
||||||
|
+s104: SECRET104
|
||||||
|
+s105: SECRET105
|
||||||
|
+s106: SECRET106
|
||||||
|
+s107: SECRET107
|
||||||
|
+
|
||||||
|
+# secrets for no_log_suboptions_invalid.yml
|
||||||
|
+s201: SECRET201
|
||||||
|
+s202: SECRET202
|
||||||
|
+s203: SECRET203
|
||||||
|
+s204: SECRET204
|
||||||
|
+s205: SECRET205
|
||||||
|
+s206: SECRET206
|
||||||
|
+s207: SECRET207
|
||||||
|
+s208: SECRET208
|
||||||
|
+s209: SECRET209
|
||||||
|
+s210: SECRET210
|
||||||
|
+s211: SECRET211
|
||||||
|
+s212: SECRET212
|
||||||
|
+s213: SECRET213
|
||||||
146
CVE-2024-9902.patch
Normal file
146
CVE-2024-9902.patch
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
From: Brian Coca <bcoca@users.noreply.github.com>
|
||||||
|
Date: Mon, 28 Oct 2024 12:47:08 -0400
|
||||||
|
Subject: CVE-2024-9902 user module avoid chmoding symlink'd home file
|
||||||
|
(#83956) (#84081)
|
||||||
|
|
||||||
|
also added tests
|
||||||
|
|
||||||
|
origin: https://github.com/ansible/ansible/commit/3b5a4319985e1eabe7fc0410bf8308b671f4f586
|
||||||
|
bug: https://github.com/ansible/ansible/pull/84081
|
||||||
|
bug-debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1086883
|
||||||
|
---
|
||||||
|
changelogs/fragments/user_action_fix.yml | 2 +
|
||||||
|
lib/ansible/modules/system/user.py | 4 +-
|
||||||
|
.../targets/user/files/skel/.ssh/known_hosts | 1 +
|
||||||
|
.../user/tasks/test_create_user_home.yml | 86 +++++++++++++++++++
|
||||||
|
4 files changed, 92 insertions(+), 1 deletion(-)
|
||||||
|
create mode 100644 changelogs/fragments/user_action_fix.yml
|
||||||
|
create mode 100644 test/integration/targets/user/files/skel/.ssh/known_hosts
|
||||||
|
|
||||||
|
diff --git a/changelogs/fragments/user_action_fix.yml b/changelogs/fragments/user_action_fix.yml
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..64ee997d
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/changelogs/fragments/user_action_fix.yml
|
||||||
|
@@ -0,0 +1,2 @@
|
||||||
|
+bugfixes:
|
||||||
|
+ - user module now avoids changing ownership of files symlinked in provided home dir skeleton
|
||||||
|
diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py
|
||||||
|
index fd56fc68..9d47425f 100644
|
||||||
|
--- a/lib/ansible/modules/system/user.py
|
||||||
|
+++ b/lib/ansible/modules/system/user.py
|
||||||
|
@@ -1154,7 +1154,9 @@ class User(object):
|
||||||
|
for d in dirs:
|
||||||
|
os.chown(os.path.join(root, d), uid, gid)
|
||||||
|
for f in files:
|
||||||
|
- os.chown(os.path.join(root, f), uid, gid)
|
||||||
|
+ full_path = os.path.join(root, f)
|
||||||
|
+ if not os.path.islink(full_path):
|
||||||
|
+ os.chown(full_path, uid, gid)
|
||||||
|
except OSError as e:
|
||||||
|
self.module.exit_json(failed=True, msg="%s" % to_native(e))
|
||||||
|
|
||||||
|
diff --git a/test/integration/targets/user/files/skel/.ssh/known_hosts b/test/integration/targets/user/files/skel/.ssh/known_hosts
|
||||||
|
new file mode 100644
|
||||||
|
index 00000000..f5b72a21
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/test/integration/targets/user/files/skel/.ssh/known_hosts
|
||||||
|
@@ -0,0 +1 @@
|
||||||
|
+test file, not real ssh hosts file
|
||||||
|
diff --git a/test/integration/targets/user/tasks/test_create_user_home.yml b/test/integration/targets/user/tasks/test_create_user_home.yml
|
||||||
|
index 1b529f76..3bc1023b 100644
|
||||||
|
--- a/test/integration/targets/user/tasks/test_create_user_home.yml
|
||||||
|
+++ b/test/integration/targets/user/tasks/test_create_user_home.yml
|
||||||
|
@@ -134,3 +134,89 @@
|
||||||
|
name: randomuser
|
||||||
|
state: absent
|
||||||
|
remove: yes
|
||||||
|
+
|
||||||
|
+- name: Create user home directory with /dev/null as skeleton, https://github.com/ansible/ansible/issues/75063
|
||||||
|
+ # create_homedir is mostly used by linux, rest of OSs take care of it themselves via -k option (which fails this task)
|
||||||
|
+ # OS X actuall breaks since it does not implement getpwnam()
|
||||||
|
+ when: ansible_system == 'Linux'
|
||||||
|
+ block:
|
||||||
|
+ - name: "Create user home directory with /dev/null as skeleton"
|
||||||
|
+ user:
|
||||||
|
+ name: withskeleton
|
||||||
|
+ state: present
|
||||||
|
+ skeleton: "/dev/null"
|
||||||
|
+ createhome: yes
|
||||||
|
+ register: create_user_with_skeleton_dev_null
|
||||||
|
+ always:
|
||||||
|
+ - name: "Remove test user"
|
||||||
|
+ user:
|
||||||
|
+ name: withskeleton
|
||||||
|
+ state: absent
|
||||||
|
+ remove: yes
|
||||||
|
+
|
||||||
|
+- name: Create user home directory with skel that contains symlinks
|
||||||
|
+ tags: symlink_home
|
||||||
|
+ when: ansible_system == 'Linux'
|
||||||
|
+ become: True
|
||||||
|
+ vars:
|
||||||
|
+ flag: '{{tempdir.path}}/root_flag.conf'
|
||||||
|
+ block:
|
||||||
|
+ - name: make tempdir for skel
|
||||||
|
+ tempfile: state=directory
|
||||||
|
+ register: tempdir
|
||||||
|
+
|
||||||
|
+ - name: create flag file
|
||||||
|
+ file: path={{flag}} owner=root state=touch
|
||||||
|
+
|
||||||
|
+ - name: copy skell to target
|
||||||
|
+ copy:
|
||||||
|
+ dest: '{{tempdir.path}}/skel'
|
||||||
|
+ src: files/skel
|
||||||
|
+ register: skel
|
||||||
|
+
|
||||||
|
+ - name: create the bad symlink
|
||||||
|
+ file:
|
||||||
|
+ src: '{{flag}}'
|
||||||
|
+ dest: '{{tempdir.path}}/skel/should_not_change_own'
|
||||||
|
+ state: link
|
||||||
|
+
|
||||||
|
+ - name: "Create user home directory with skeleton"
|
||||||
|
+ user:
|
||||||
|
+ name: withskeleton
|
||||||
|
+ state: present
|
||||||
|
+ skeleton: "{{tempdir.path}}/skel"
|
||||||
|
+ createhome: yes
|
||||||
|
+ home: /home/missing/withskeleton
|
||||||
|
+ register: create_user_with_skeleton_symlink
|
||||||
|
+
|
||||||
|
+ - name: Check flag
|
||||||
|
+ stat: path={{flag}}
|
||||||
|
+ register: test_flag
|
||||||
|
+
|
||||||
|
+ - name: ensure we didn't change owner for flag
|
||||||
|
+ assert:
|
||||||
|
+ that:
|
||||||
|
+ - test_flag.stat.uid != create_user_with_skeleton_symlink.uid
|
||||||
|
+
|
||||||
|
+ always:
|
||||||
|
+ - name: "Remove test user"
|
||||||
|
+ user:
|
||||||
|
+ name: withskeleton
|
||||||
|
+ state: absent
|
||||||
|
+ remove: yes
|
||||||
|
+
|
||||||
|
+ - name: get files to delete
|
||||||
|
+ find: path="{{tempdir.path}}"
|
||||||
|
+ register: remove
|
||||||
|
+ when:
|
||||||
|
+ - tempdir is defined
|
||||||
|
+ - tempdir is success
|
||||||
|
+
|
||||||
|
+ - name: "Remove temp files"
|
||||||
|
+ file:
|
||||||
|
+ path: '{{item}}'
|
||||||
|
+ state: absent
|
||||||
|
+ loop: "{{remove.files|default([])}}"
|
||||||
|
+ when:
|
||||||
|
+ - remove is success
|
||||||
|
+
|
||||||
|
--
|
||||||
|
2.47.0
|
||||||
|
|
||||||
24
ansible.spec
24
ansible.spec
@ -2,7 +2,7 @@
|
|||||||
Name: ansible
|
Name: ansible
|
||||||
Summary: SSH-based configuration management, deployment, and task execution system
|
Summary: SSH-based configuration management, deployment, and task execution system
|
||||||
Version: 2.9.27
|
Version: 2.9.27
|
||||||
Release: 3
|
Release: 8
|
||||||
|
|
||||||
License: GPLv3+
|
License: GPLv3+
|
||||||
Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz
|
Source0: https://releases.ansible.com/ansible/%{name}-%{version}.tar.gz
|
||||||
@ -18,6 +18,12 @@ Patch3: fix-python-3.9-compatibility.patch
|
|||||||
Patch4: ansible-2.9.23-sphinx4.patch
|
Patch4: ansible-2.9.23-sphinx4.patch
|
||||||
Patch5: hostname-module-support-openEuler.patch
|
Patch5: hostname-module-support-openEuler.patch
|
||||||
Patch6: Fix-build-error-for-sphinx-7.0.patch
|
Patch6: Fix-build-error-for-sphinx-7.0.patch
|
||||||
|
Patch7: CVE-2024-0690.patch
|
||||||
|
Patch8: CVE-2024-8775.patch
|
||||||
|
Patch9: CVE-2024-9902.patch
|
||||||
|
Patch10:CVE-2022-3697.patch
|
||||||
|
Patch11:CVE-2023-5115.patch
|
||||||
|
Patch12:CVE-2023-5764.patch
|
||||||
|
|
||||||
Provides: ansible-python3 = %{version}-%{release}
|
Provides: ansible-python3 = %{version}-%{release}
|
||||||
Obsoletes: ansible-python3 < %{version}-%{release}
|
Obsoletes: ansible-python3 < %{version}-%{release}
|
||||||
@ -28,7 +34,6 @@ BuildRequires: python3-packaging
|
|||||||
BuildRequires: python3-pexpect
|
BuildRequires: python3-pexpect
|
||||||
BuildRequires: python3-paramiko
|
BuildRequires: python3-paramiko
|
||||||
BuildRequires: python3-pywinrm
|
BuildRequires: python3-pywinrm
|
||||||
BuildRequires: python3-crypto
|
|
||||||
BuildRequires: python3-pbkdf2
|
BuildRequires: python3-pbkdf2
|
||||||
BuildRequires: python3-httmock
|
BuildRequires: python3-httmock
|
||||||
BuildRequires: python3-python-gitlab
|
BuildRequires: python3-python-gitlab
|
||||||
@ -216,6 +221,21 @@ make PYTHON=/usr/bin/python3 tests-py3
|
|||||||
%{python3_sitelib}/ansible_test
|
%{python3_sitelib}/ansible_test
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Apr 01 2025 wangkai <13474090681@163.com> - 2.9.27-8
|
||||||
|
- Fix CVE-2023-5764
|
||||||
|
|
||||||
|
* Sat Feb 08 2025 wangkai <13474090681@163.com> - 2.9.27-7
|
||||||
|
- Fix CVE-2022-3697 CVE-2023-5115
|
||||||
|
|
||||||
|
* Mon Dec 02 2024 yaoxin <yao_xin001@hoperun.com> - 2.9.27-6
|
||||||
|
- Fix CVE-2024-8775 and CVE-2024-9902
|
||||||
|
|
||||||
|
* Mon Feb 05 2024 wangkai <13474090681@163.com> - 2.9.27-5
|
||||||
|
- Fix CVE-2024-0690
|
||||||
|
|
||||||
|
* Wed Aug 9 2023 liyanan <thistleslyn@163.com> - 2.9.27-4
|
||||||
|
- Remove obsolete buildrequire python3-crypto
|
||||||
|
|
||||||
* Thu Aug 3 2023 liyanan <thistleslyn@163.com> - 2.9.27-3
|
* Thu Aug 3 2023 liyanan <thistleslyn@163.com> - 2.9.27-3
|
||||||
- Fix build error for sphinx 7.0
|
- Fix build error for sphinx 7.0
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user