diff --git a/.ansible.metadata b/.ansible.metadata index 1ca5e23..1f7a337 100644 --- a/.ansible.metadata +++ b/.ansible.metadata @@ -1 +1 @@ -abea37a4ba664b55af3d0d9c6017d45daa9816cd SOURCES/ansible-2.3.2.0.tar.gz +bcbdc1511ec85ea1779f24df430bf90987683b8d SOURCES/ansible-2.4.0.0.tar.gz diff --git a/.gitignore b/.gitignore index 485007a..d5be5ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/ansible-2.3.2.0.tar.gz +SOURCES/ansible-2.4.0.0.tar.gz diff --git a/SOURCES/ansible-pr-30875.patch b/SOURCES/ansible-pr-30875.patch new file mode 100644 index 0000000..7192fe4 --- /dev/null +++ b/SOURCES/ansible-pr-30875.patch @@ -0,0 +1,50 @@ +diff --git a/lib/ansible/modules/web_infrastructure/jenkins_plugin.py b/lib/ansible/modules/web_infrastructure/jenkins_plugin.py +index 9aba61a..6d2bad6 100644 +--- a/lib/ansible/modules/web_infrastructure/jenkins_plugin.py ++++ b/lib/ansible/modules/web_infrastructure/jenkins_plugin.py +@@ -52,6 +52,8 @@ options: + description: + - Option used to allow the user to overwrite any of the other options. To + remove an option, set the value of the option to C(null). ++ - Changed in 2.5.0, 2.4.1, 2.3.3 to raise an error if C(url_password) is specified in params. ++ Use the actual C(url_password) argument instead. + state: + required: false + choices: [absent, present, pinned, unpinned, enabled, disabled, latest] +@@ -166,20 +168,18 @@ EXAMPLES = ''' + state: absent + + # +-# Example of how to use the params +-# +-# Define a variable and specify all default parameters you want to use across +-# all jenkins_plugin calls: ++# Example of how to authenticate + # + # my_jenkins_params: + # url_username: admin +-# url_password: p4ssw0rd +-# url: http://localhost:8888 + # + - name: Install plugin + jenkins_plugin: + name: build-pipeline-plugin + params: "{{ my_jenkins_params }}" ++ url_password: p4ssw0rd ++ url: http://localhost:8888 ++# Note that url_password **can not** be placed in params as params could end up in a log file + + # + # Example of a Play which handles Jenkins restarts during the state changes +@@ -764,6 +764,11 @@ def main(): + + # Update module parameters by user's parameters if defined + if 'params' in module.params and isinstance(module.params['params'], dict): ++ if 'url_password' in module.params['params']: ++ # The params argument should be removed eventually. Until then, raise an error if ++ # url_password is specified there as it can lead to the password being logged ++ module.fail_json(msg='Do not specify url_password in params as it may get logged') ++ + module.params.update(module.params['params']) + # Remove the params + module.params.pop('params', None) diff --git a/SOURCES/ansible-selinux-issue-30618.patch b/SOURCES/ansible-selinux-issue-30618.patch new file mode 100644 index 0000000..4110412 --- /dev/null +++ b/SOURCES/ansible-selinux-issue-30618.patch @@ -0,0 +1,440 @@ +diff --git a/lib/ansible/module_utils/facts/utils.py b/lib/ansible/module_utils/facts/utils.py +index 2446ae6..728934c 100644 +--- a/lib/ansible/module_utils/facts/utils.py ++++ b/lib/ansible/module_utils/facts/utils.py +@@ -36,9 +36,9 @@ def get_file_content(path, default=None, strip=True): + return data + + +-def get_file_lines(path): ++def get_file_lines(path, strip=True): + '''get list of lines from file''' +- data = get_file_content(path) ++ data = get_file_content(path, strip=strip) + if data: + ret = data.splitlines() + else: +diff --git a/lib/ansible/modules/system/selinux.py b/lib/ansible/modules/system/selinux.py +index 7cd4011..7d1cb8d 100644 +--- a/lib/ansible/modules/system/selinux.py ++++ b/lib/ansible/modules/system/selinux.py +@@ -8,9 +8,11 @@ from __future__ import absolute_import, division, print_function + __metaclass__ = type + + +-ANSIBLE_METADATA = {'metadata_version': '1.1', +- 'status': ['stableinterface'], +- 'supported_by': 'core'} ++ANSIBLE_METADATA = { ++ 'metadata_version': '1.1', ++ 'status': ['stableinterface'], ++ 'supported_by': 'core' ++} + + + DOCUMENTATION = ''' +@@ -59,21 +61,51 @@ EXAMPLES = ''' + state: disabled + ''' + ++RETURN = ''' ++msg: ++ description: Messages that describe changes that were made ++ returned: always ++ type: string ++ sample: Config SELinux state changed from 'disabled' to 'permissive' ++configfile: ++ description: Path to SELinux configuration file ++ returned: always ++ type: string ++ sample: /etc/selinux/config ++policy: ++ description: Name of the SELinux policy ++ returned: always ++ type: string ++ sample: targeted ++state: ++ description: SELinux mode ++ returned: always ++ type: string ++ sample: enforcing ++reboot_required: ++ description: Whether or not an reboot is required for the changes to take effect ++ returned: always ++ type: bool ++ sample: true ++''' ++ + import os + import re ++import tempfile + + try: + import selinux + HAS_SELINUX = True + except ImportError: + HAS_SELINUX = False ++ + from ansible.module_utils.basic import AnsibleModule + from ansible.module_utils.facts.utils import get_file_lines + + + # getter subroutines + def get_config_state(configfile): +- lines = get_file_lines(configfile) ++ lines = get_file_lines(configfile, strip=False) + + for line in lines: + stateline = re.match(r'^SELINUX=.*$', line) +@@ -82,7 +114,7 @@ def get_config_state(configfile): + + + def get_config_policy(configfile): +- lines = get_file_lines(configfile) ++ lines = get_file_lines(configfile, strip=False) + + for line in lines: + stateline = re.match(r'^SELINUXTYPE=.*$', line) +@@ -91,16 +123,19 @@ def get_config_policy(configfile): + + + # setter subroutines +-def set_config_state(state, configfile): ++def set_config_state(module, state, configfile): + # SELINUX=permissive + # edit config file with state value + stateline = 'SELINUX=%s' % state ++ lines = get_file_lines(configfile, strip=False) + +- lines = get_file_lines(configfile) ++ tmpfd, tmpfile = tempfile.mkstemp() + +- with open(configfile, "w") as write_file: ++ with open(tmpfile, "w") as write_file: + for line in lines: +- write_file.write(re.sub(r'^SELINUX=.*', stateline, line)) ++ write_file.write(re.sub(r'^SELINUX=.*', stateline, line) + '\n') ++ ++ module.atomic_move(tmpfile, configfile) + + + def set_state(module, state): +@@ -115,15 +150,19 @@ def set_state(module, state): + module.fail_json(msg=msg) + + +-def set_config_policy(policy, configfile): ++def set_config_policy(module, policy, configfile): + # edit config file with state value + # SELINUXTYPE=targeted + policyline = 'SELINUXTYPE=%s' % policy +- lines = get_file_lines(configfile) ++ lines = get_file_lines(configfile, strip=False) + +- with open(configfile, "w") as write_file: ++ tmpfd, tmpfile = tempfile.mkstemp() ++ ++ with open(tmpfile, "w") as write_file: + for line in lines: +- write_file.write(re.sub(r'^SELINUXTYPE=.*', policyline, line)) ++ write_file.write(re.sub(r'^SELINUXTYPE=.*', policyline, line) + '\n') ++ ++ module.atomic_move(tmpfile, configfile) + + + def main(): +@@ -148,6 +187,7 @@ def main(): + runtime_enabled = selinux.is_selinux_enabled() + runtime_policy = selinux.selinux_getpolicytype()[1] + runtime_state = 'disabled' ++ reboot_required = False + + if runtime_enabled: + # enabled means 'enforcing' or 'permissive' +@@ -167,7 +207,7 @@ def main(): + # check to see if policy is set if state is not 'disabled' + if state != 'disabled': + if not policy: +- module.fail_json(msg='policy is required if state is not \'disabled\'') ++ module.fail_json(msg='Policy is required if state is not \'disabled\'') + else: + if not policy: + policy = config_policy +@@ -177,14 +217,14 @@ def main(): + if module.check_mode: + module.exit_json(changed=True) + # cannot change runtime policy +- msgs.append('reboot to change the loaded policy') ++ msgs.append('Running SELinux policy changed from \'%s\' to \'%s\'' % (runtime_policy, policy)) + changed = True + + if policy != config_policy: + if module.check_mode: + module.exit_json(changed=True) +- msgs.append('config policy changed from \'%s\' to \'%s\'' % (config_policy, policy)) +- set_config_policy(policy, configfile) ++ set_config_policy(module, policy, configfile) ++ msgs.append('SELinux policy configuration in \'%s\' changed from \'%s\' to \'%s\'' % (configfile, config_policy, policy)) + changed = True + + if state != runtime_state: +@@ -195,26 +235,30 @@ def main(): + if runtime_state != 'permissive': + # Temporarily set state to permissive + set_state(module, 'permissive') +- msgs.append('runtime state temporarily changed from \'%s\' to \'permissive\', state change will take effect next reboot' % (runtime_state)) ++ module.warn('SELinux state temporarily changed from \'%s\' to \'permissive\'. State change will take effect next reboot.' % (runtime_state)) + else: +- msgs.append('state change will take effect next reboot') ++ module.warn('SELinux state change will take effect next reboot') ++ reboot_required = True + else: + set_state(module, state) +- msgs.append('runtime state changed from \'%s\' to \'%s\'' % (runtime_state, state)) ++ msgs.append('SELinux state changed from \'%s\' to \'%s\'' % (runtime_state, state)) ++ ++ # Only report changes if the file is changed. ++ # This prevents the task from reporting changes every time the task is run. ++ changed = True + else: +- msgs.append('state change will take effect next reboot') +- changed = True ++ module.warn("Reboot is required to set SELinux state to %s" % state) ++ reboot_required = True + + if state != config_state: + if module.check_mode: + module.exit_json(changed=True) +- msgs.append('config state changed from \'%s\' to \'%s\'' % (config_state, state)) +- set_config_state(state, configfile) ++ msgs.append('Config SELinux state changed from \'%s\' to \'%s\'' % (config_state, state)) ++ set_config_state(module, state, configfile) + changed = True + +- module.exit_json(changed=changed, msg=', '.join(msgs), configfile=configfile, policy=policy, state=state) ++ module.exit_json(changed=changed, msg=', '.join(msgs), configfile=configfile, policy=policy, state=state, reboot_required=reboot_required) + +-################################################# + + if __name__ == '__main__': + main() +diff --git a/test/integration/targets/selinux/aliases b/test/integration/targets/selinux/aliases +new file mode 100644 +index 0000000..53b3251 +--- /dev/null ++++ b/test/integration/targets/selinux/aliases +@@ -0,0 +1,2 @@ ++needs/root ++posix/ci/group2 +diff --git a/test/integration/targets/selinux/tasks/main.yml b/test/integration/targets/selinux/tasks/main.yml +new file mode 100644 +index 0000000..dc3e678 +--- /dev/null ++++ b/test/integration/targets/selinux/tasks/main.yml +@@ -0,0 +1,30 @@ ++# (c) 2017, Sam Doran ++ ++# This file is part of Ansible ++# ++# Ansible is free software: you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# Ansible is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with Ansible. If not, see . ++ ++- debug: ++ msg: SELinux is disabled ++ when: ansible_selinux is defined and ansible_selinux == False ++ ++- debug: ++ msg: SELinux is {{ ansible_selinux.status }} ++ when: ansible_selinux is defined and ansible_selinux != False ++ ++- include: selinux.yml ++ when: ++ - ansible_selinux is defined ++ - ansible_selinux != False ++ - ansible_selinux.status == 'enabled' +diff --git a/test/integration/targets/selinux/tasks/selinux.yml b/test/integration/targets/selinux/tasks/selinux.yml +new file mode 100644 +index 0000000..ff8b2fa +--- /dev/null ++++ b/test/integration/targets/selinux/tasks/selinux.yml +@@ -0,0 +1,170 @@ ++# (c) 2017, Sam Doran ++ ++# This file is part of Ansible ++# ++# Ansible is free software: you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation, either version 3 of the License, or ++# (at your option) any later version. ++# ++# Ansible is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with Ansible. If not, see . ++ ++ ++# First Test ++# ############################################################################## ++# Test changing the state, which requires a reboot ++ ++- name: TEST 1 | Get current SELinux config file contents ++ set_fact: ++ selinux_config_original: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}" ++ before_test_sestatus: "{{ ansible_selinux }}" ++ ++- debug: ++ var: "{{ item }}" ++ verbosity: 1 ++ with_items: ++ - selinux_config_original ++ - before_test_sestatus ++ - ansible_selinux ++ ++- name: TEST 1 | Setup SELinux configuration for tests ++ selinux: ++ state: enforcing ++ policy: targeted ++ ++- name: TEST 1 | Disable SELinux ++ selinux: ++ state: disabled ++ policy: targeted ++ register: _disable_test1 ++ ++- debug: ++ var: _disable_test1 ++ verbosity: 1 ++ ++- name: TEST 1 | Re-gather facts ++ setup: ++ ++- name: TEST 1 | Assert that status was changed, reboot_required is True, a warning was displayed, and SELinux is configured properly ++ assert: ++ that: ++ - _disable_test1 | changed ++ - _disable_test1.reboot_required ++ - (_disable_test1.warnings | length ) >= 1 ++ - ansible_selinux.config_mode == 'disabled' ++ - ansible_selinux.type == 'targeted' ++ ++- debug: ++ var: ansible_selinux ++ verbosity: 1 ++ ++- name: TEST 1 | Disable SELinux again ++ selinux: ++ state: disabled ++ policy: targeted ++ register: _disable_test2 ++ ++- debug: ++ var: _disable_test2 ++ verbosity: 1 ++ ++- name: TEST 1 | Assert that no change is reported, a warnking was dispalyed, and reboot_required is True ++ assert: ++ that: ++ - not _disable_test2 | changed ++ - (_disable_test1.warnings | length ) >= 1 ++ - _disable_test2.reboot_required ++ ++- name: TEST 1 | Get modified config file ++ set_fact: ++ selinux_config_after: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}" ++ ++- debug: ++ var: selinux_config_after ++ verbosity: 1 ++ ++- name: TEST 1 | Ensure SELinux config file is properly formatted ++ assert: ++ that: ++ - selinux_config_original | length == selinux_config_after | length ++ - selinux_config_after[selinux_config_after.index('SELINUX=disabled')] | search("^SELINUX=\w+$") ++ - selinux_config_after[selinux_config_after.index('SELINUXTYPE=targeted')] | search("^SELINUXTYPE=\w+$") ++ ++- name: TEST 1 | Reset SELinux configuration for next test ++ selinux: ++ state: enforcing ++ policy: targeted ++ ++ ++# Second Test ++# ############################################################################## ++# Test changing only the policy, which does not require a reboot ++ ++- name: TEST 2 | Set SELinux policy ++ selinux: ++ state: enforcing ++ policy: mls ++ register: _state_test1 ++ ++- debug: ++ var: _state_test1 ++ verbosity: 1 ++ ++- name: TEST 2 | Re-gather facts ++ setup: ++ ++- debug: ++ var: ansible_selinux ++ tags: debug ++ ++- name: TEST 2 | Assert that status was changed, reboot_required is False, no warnings were displayed, and SELinux is configured properly ++ assert: ++ that: ++ - _state_test1 | changed ++ - not _state_test1.reboot_required ++ - _state_test1.warnings is not defined ++ - ansible_selinux.config_mode == 'enforcing' ++ - ansible_selinux.type == 'mls' ++ ++- name: TEST 2 | Set SELinux policy again ++ selinux: ++ state: enforcing ++ policy: mls ++ register: _state_test2 ++ ++- debug: ++ var: _state_test2 ++ verbosity: 1 ++ ++- name: TEST 2 | Assert that no change was reported, no warnings were dispalyed, and reboot_required is False ++ assert: ++ that: ++ - not _state_test2 | changed ++ - _state_test2.warnings is not defined ++ - not _state_test2.reboot_required ++ ++- name: TEST 2 | Get modified config file ++ set_fact: ++ selinux_config_after: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}" ++ ++- debug: ++ var: selinux_config_after ++ verbosity: 1 ++ ++- name: TEST 2 | Ensure SELinux config file is properly formatted ++ assert: ++ that: ++ - selinux_config_original | length == selinux_config_after | length ++ - selinux_config_after[selinux_config_after.index('SELINUX=enforcing')] | search("^SELINUX=\w+$") ++ - selinux_config_after[selinux_config_after.index('SELINUXTYPE=mls')] | search("^SELINUXTYPE=\w+$") ++ ++- name: TEST 2 | Reset SELinux configuration for next test ++ selinux: ++ state: enforcing ++ policy: targeted diff --git a/SPECS/ansible.spec b/SPECS/ansible.spec index 9325a12..77a2011 100644 --- a/SPECS/ansible.spec +++ b/SPECS/ansible.spec @@ -21,8 +21,8 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Name: ansible Summary: SSH-based configuration management, deployment, and task execution system -Version: 2.3.2.0 -Release: 2%{?dist} +Version: 2.4.0.0 +Release: 5%{?dist} Group: Development/Libraries License: GPLv3+ @@ -31,6 +31,10 @@ Source0: http://releases.ansible.com/ansible/%{name}-%{version}.tar.gz # Provides default search paths, among them /usr/share/ansible/roles, # which will be used in other packages Patch0: ansible-rolepath.patch +# Fixes CVE-2017-7550 +Patch1: ansible-pr-30875.patch +# Fixes upstream #30618 - broken selinux module +Patch2: ansible-selinux-issue-30618.patch # Patch to utilize a newer jinja2 package on epel6 # Non-upstreamable as it creates a dependency on a specific version of jinja. @@ -62,9 +66,11 @@ BuildRequires: python-sphinx %if %{with tests} %if (0%{?fedora} || 0%{?rhel} > 6) BuildRequires: PyYAML -BuildRequires: python-crypto +BuildRequires: python-cryptography BuildRequires: python-paramiko -BuildRequires: python-keyczar +# accelerate is the only thing that makes keyczar mandatory. Since accelerate +# is deprecated, ignore keyczar +#Requires: python-keyczar BuildRequires: python-six BuildRequires: python-nose BuildRequires: python-coverage @@ -100,32 +106,18 @@ BuildRequires: python-jinja2 %endif Requires: PyYAML -Requires: python-crypto +Requires: python-cryptography +Requires: python-passlib Requires: python-paramiko # accelerate is the only thing that makes keyczar mandatory. Since accelerate # is deprecated, just ignore it #Requires: python-keyczar Requires: python-httplib2 -Requires: python-passlib Requires: python-setuptools Requires: python-six Requires: sshpass -%endif - -%if 0%{?rhel} == 6 -# RHEL 6 needs a newer version of the pycrypto library for the ansible-vault -# command. Note: If other pieces of ansible also grow to need pycrypto you may -# need to add: Requires: python-crypto or patch the other pieces of ansible to -# make use of this forward compat package (see the patch for ansible-vault -# above to see what needs to be done.) -Requires: python-crypto2.6 -# The python-2.6 stdlib json module has a bug that affects the ansible -# to_nice_json filter -Requires: python-simplejson - -# For testing -BuildRequires: python-crypto2.6 -BuildRequires: python-simplejson +# needed for json_query filter +Requires: python2-jmespath %endif # @@ -161,7 +153,7 @@ BuildRequires: python3-setuptools # For tests BuildRequires: python3-PyYAML BuildRequires: python3-paramiko -BuildRequires: python3-crypto +BuildRequires: python3-cryptography # accelerate is the only thing that makes keyczar mandatory. Since accelerate # is deprecated, just ignore it #BuildRequires: python-keyczar @@ -179,7 +171,8 @@ BuildRequires: python3-jinja2 Requires: python3-PyYAML Requires: python3-paramiko -Requires: python3-crypto +Requires: python3-cryptography +Requires: python3-passlib # accelerate is the only thing that makes keyczar mandatory. Since accelerate # is deprecated, just ignore it #Requires: python3-keyczar @@ -187,6 +180,8 @@ Requires: python3-setuptools Requires: python3-six Requires: python3-jinja2 Requires: sshpass +# needed for json_query filter +Requires: python3-jmespath %endif @@ -218,6 +213,8 @@ This package installs extensive documentation for ansible %prep %setup -q %patch0 -p1 +%patch1 -p1 +%patch2 -p1 %if 0%{?rhel} == 6 %patch100 -p1 @@ -336,6 +333,18 @@ rm -rf $RPM_BUILD_ROOT %endif %changelog +* Tue Oct 3 2017 Pavel Cahyna - 2.4.0.0-5 +- Backport a fix for the selinux module. Upstream github issue 30618 + +* Tue Oct 3 2017 Pavel Cahyna - 2.4.0.0-4 +- Backport patch for CVE-2017-7550 - PR#30875 rhbz#1473645 + +* Thu Sep 21 2017 Pavel Cahyna - 2.4.0.0-3 +- Require python-jmespath, needed for the json_query filter. bz #1484910 + +* Fri Sep 15 2017 Pavel Cahyna - 2.4.0.0-1 +- Rebase to the Ansible 2.4.0.0 release (bz #1492477). + * Thu Aug 24 2017 Pavel Cahyna - 2.3.2.0-2 - Add a runtime dependency on python-passlib (bz #1484860).