ansible/CVE-2020-1739.patch
starlet-dx c52b423a72 fix CVE-2019-14904 CVE-2020-10684 CVE-2020-10729 CVE-2020-1735-to-CVE-2020-1740 CVE-2020-1753 CVE-2021-20191
(cherry picked from commit ebf023f03ad09762c8147ad8c963a51b60de62ff)
2021-09-17 20:38:23 +08:00

385 lines
13 KiB
Diff

From 641ff410128a959884b035a2c720c3ba61bf2064 Mon Sep 17 00:00:00 2001
From: Sloane Hertel <shertel@redhat.com>
Date: Mon, 13 Apr 2020 10:21:10 -0400
Subject: [PATCH] subversion module - provide password securely when possible
or warn (#67829)
* subversion module - provide password securely with svn command line option --password-from-stdin when possible, and provide a warning otherwise.
* Update lib/ansible/modules/source_control/subversion.py.
* Add a test.
Co-authored-by: Sam Doran <sdoran@redhat.com>
(cherry picked from commit d91658ec0c8434c82c3ef98bfe9eb4e1027a43a3)
---
changelogs/fragments/subversion_password.yaml | 9 ++
.../modules/source_control/subversion.py | 21 ++-
test/integration/targets/subversion/aliases | 1 +
.../targets/subversion/meta/main.yml | 2 -
.../roles/subversion/tasks/cleanup.yml | 8 ++
.../roles/subversion/tasks/main.yml | 20 +++
.../roles/subversion/tasks/setup_selinux.yml | 11 ++
.../roles/subversion/tasks/warnings.yml | 7 +
test/integration/targets/subversion/runme.sh | 32 +++++
test/integration/targets/subversion/runme.yml | 15 +++
.../targets/subversion/tasks/main.yml | 123 ------------------
11 files changed, 121 insertions(+), 128 deletions(-)
create mode 100644 changelogs/fragments/subversion_password.yaml
delete mode 100644 test/integration/targets/subversion/meta/main.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/main.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml
create mode 100644 test/integration/targets/subversion/roles/subversion/tasks/warnings.yml
create mode 100755 test/integration/targets/subversion/runme.sh
create mode 100644 test/integration/targets/subversion/runme.yml
delete mode 100644 test/integration/targets/subversion/tasks/main.yml
diff --git a/changelogs/fragments/subversion_password.yaml b/changelogs/fragments/subversion_password.yaml
new file mode 100644
index 0000000..42e09fb
--- /dev/null
+++ b/changelogs/fragments/subversion_password.yaml
@@ -0,0 +1,9 @@
+bugfixes:
+- >
+ **security issue** - The ``subversion`` module provided the password
+ via the svn command line option ``--password`` and can be retrieved
+ from the host's /proc/<pid>/cmdline file. Update the module to use
+ the secure ``--password-from-stdin`` option instead, and add a warning
+ in the module and in the documentation if svn version is too old to
+ support it.
+ (CVE-2020-1739)
diff --git a/lib/ansible/modules/source_control/subversion.py b/lib/ansible/modules/source_control/subversion.py
index 3b2547d..e9112cb 100644
--- a/lib/ansible/modules/source_control/subversion.py
+++ b/lib/ansible/modules/source_control/subversion.py
@@ -49,7 +49,9 @@ options:
- C(--username) parameter passed to svn.
password:
description:
- - C(--password) parameter passed to svn.
+ - C(--password) parameter passed to svn when svn is less than version 1.10.0. This is not secure and
+ the password will be leaked to argv.
+ - C(--password-from-stdin) parameter when svn is greater or equal to version 1.10.0.
executable:
description:
- Path to svn executable to use. If not supplied,
@@ -103,6 +105,8 @@ EXAMPLES = '''
import os
import re
+from distutils.version import LooseVersion
+
from ansible.module_utils.basic import AnsibleModule
@@ -116,6 +120,10 @@ class Subversion(object):
self.password = password
self.svn_path = svn_path
+ def has_option_password_from_stdin(self):
+ rc, version, err = self.module.run_command([self.svn_path, '--version', '--quiet'], check_rc=True)
+ return LooseVersion(version) >= LooseVersion('1.10.0')
+
def _exec(self, args, check_rc=True):
'''Execute a subversion command, and return output. If check_rc is False, returns the return code instead of the output.'''
bits = [
@@ -124,12 +132,19 @@ class Subversion(object):
'--trust-server-cert',
'--no-auth-cache',
]
+ stdin_data = None
if self.username:
bits.extend(["--username", self.username])
if self.password:
- bits.extend(["--password", self.password])
+ if self.has_option_password_from_stdin():
+ bits.append("--password-from-stdin")
+ stdin_data = self.password
+ else:
+ self.module.warn("The authentication provided will be used on the svn command line and is not secure. "
+ "To securely pass credentials, upgrade svn to version 1.10.0 or greater.")
+ bits.extend(["--password", self.password])
bits.extend(args)
- rc, out, err = self.module.run_command(bits, check_rc)
+ rc, out, err = self.module.run_command(bits, check_rc, data=stdin_data)
if check_rc:
return out.splitlines()
else:
diff --git a/test/integration/targets/subversion/aliases b/test/integration/targets/subversion/aliases
index c364b48..8be436d 100644
--- a/test/integration/targets/subversion/aliases
+++ b/test/integration/targets/subversion/aliases
@@ -1,2 +1,3 @@
+setup/always/setup_passlib
posix/ci/group2
skip/osx
diff --git a/test/integration/targets/subversion/meta/main.yml b/test/integration/targets/subversion/meta/main.yml
deleted file mode 100644
index 07faa21..0000000
--- a/test/integration/targets/subversion/meta/main.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-dependencies:
- - prepare_tests
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml b/test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml
new file mode 100644
index 0000000..9be43b4
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/cleanup.yml
@@ -0,0 +1,8 @@
+---
+- name: stop apache after tests
+ shell: "kill -9 $(cat '{{ subversion_server_dir }}/apache.pid')"
+
+- name: remove tmp subversion server dir
+ file:
+ path: '{{ subversion_server_dir }}'
+ state: absent
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/main.yml b/test/integration/targets/subversion/roles/subversion/tasks/main.yml
new file mode 100644
index 0000000..0d6acb8
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/main.yml
@@ -0,0 +1,20 @@
+---
+- name: setup subversion server
+ import_tasks: setup.yml
+ tags: setup
+
+- name: verify that subversion is installed so this test can continue
+ shell: which svn
+ tags: always
+
+- name: run tests
+ import_tasks: tests.yml
+ tags: tests
+
+- name: run warning
+ import_tasks: warnings.yml
+ tags: warnings
+
+- name: clean up
+ import_tasks: cleanup.yml
+ tags: cleanup
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml b/test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml
new file mode 100644
index 0000000..a9ffa71
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/setup_selinux.yml
@@ -0,0 +1,11 @@
+- name: set SELinux security context for SVN folder
+ sefcontext:
+ target: '{{ subversion_server_dir }}(/.*)?'
+ setype: '{{ item }}'
+ state: present
+ with_items:
+ - httpd_sys_content_t
+ - httpd_sys_rw_content_t
+
+- name: apply new SELinux context to filesystem
+ command: restorecon -irv {{ subversion_server_dir | quote }}
diff --git a/test/integration/targets/subversion/roles/subversion/tasks/warnings.yml b/test/integration/targets/subversion/roles/subversion/tasks/warnings.yml
new file mode 100644
index 0000000..50ebd44
--- /dev/null
+++ b/test/integration/targets/subversion/roles/subversion/tasks/warnings.yml
@@ -0,0 +1,7 @@
+---
+- name: checkout using a password to test for a warning when using svn lt 1.10.0
+ subversion:
+ repo: '{{ subversion_repo_auth_url }}'
+ dest: '{{ subversion_test_dir }}/svn'
+ username: '{{ subversion_username }}'
+ password: '{{ subversion_password }}'
diff --git a/test/integration/targets/subversion/runme.sh b/test/integration/targets/subversion/runme.sh
new file mode 100755
index 0000000..f505e58
--- /dev/null
+++ b/test/integration/targets/subversion/runme.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+
+set -eu
+
+cleanup() {
+ echo "Cleanup"
+ ansible-playbook runme.yml -e "output_dir=${OUTPUT_DIR}" "$@" --tags cleanup
+ echo "Done"
+}
+
+trap cleanup INT TERM EXIT
+
+export ANSIBLE_ROLES_PATH=roles/
+
+# Ensure subversion is set up
+ansible-playbook runme.yml "$@" -v --tags setup
+
+# Test functionality
+ansible-playbook runme.yml "$@" -v --tags tests
+
+# Test a warning is displayed for versions < 1.10.0 when a password is provided
+ansible-playbook runme.yml "$@" --tags warnings 2>&1 | tee out.txt
+
+version="$(svn --version -q)"
+secure=$(python -c "from distutils.version import LooseVersion; print(LooseVersion('$version') >= LooseVersion('1.10.0'))")
+
+if [[ "${secure}" = "False" ]] && [[ "$(grep -c 'To securely pass credentials, upgrade svn to version 1.10.0' out.txt)" -eq 1 ]]; then
+ echo "Found the expected warning"
+elif [[ "${secure}" = "False" ]]; then
+ echo "Expected a warning"
+ exit 1
+fi
diff --git a/test/integration/targets/subversion/runme.yml b/test/integration/targets/subversion/runme.yml
new file mode 100644
index 0000000..c67d7b8
--- /dev/null
+++ b/test/integration/targets/subversion/runme.yml
@@ -0,0 +1,15 @@
+---
+- hosts: localhost
+ tasks:
+ - name: load OS specific vars
+ include_vars: '{{ item }}'
+ with_first_found:
+ - files:
+ - '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml'
+ - '{{ ansible_os_family }}.yml'
+ paths: '../vars'
+ tags: always
+
+ - include_role:
+ name: subversion
+ tags: always
diff --git a/test/integration/targets/subversion/tasks/main.yml b/test/integration/targets/subversion/tasks/main.yml
deleted file mode 100644
index 5631adb..0000000
--- a/test/integration/targets/subversion/tasks/main.yml
+++ /dev/null
@@ -1,123 +0,0 @@
-# test code for the svn module
-# (c) 2014, Michael DeHaan <michael.dehaan@gmail.com>
-
-# 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 <http://www.gnu.org/licenses/>.
-
-- name: set where to extract the repo
- set_fact: checkout_dir={{ output_dir }}/svn
-
-- name: set what repo to use
- set_fact: repo=https://github.com/jimi-c/test_role
-
-- name: clean out the output_dir
- shell: rm -rf {{ output_dir }}/*
-
-- name: install subversion
- package:
- name: subversion
- when: ansible_distribution != "MacOSX"
-
-- name: verify that subversion is installed so this test can continue
- shell: which svn
-
-# checks out every branch so using a small repo
-
-- name: initial checkout
- subversion: repo={{ repo }} dest={{ checkout_dir }}
- register: subverted
-
-- debug: var=subverted
-
-- shell: ls {{ checkout_dir }}
-
-# FIXME: the before/after logic here should be fixed to make them hashes, see GitHub 6078
-# looks like this: {
-# "after": [
-# "Revision: 9",
-# "URL: https://github.com/jimi-c/test_role"
-# ],
-# "before": null,
-# "changed": true,
-# "item": ""
-# }
-
-- name: verify information about the initial clone
- assert:
- that:
- - "'after' in subverted"
- - "subverted.after.1 == 'URL: https://github.com/jimi-c/test_role'"
- - "not subverted.before"
- - "subverted.changed"
-
-- name: repeated checkout
- subversion: repo={{ repo }} dest={{ checkout_dir }}
- register: subverted2
-
-- name: verify on a reclone things are marked unchanged
- assert:
- that:
- - "not subverted2.changed"
-
-- name: check for tags
- stat: path={{ checkout_dir }}/tags
- register: tags
-
-- name: check for trunk
- stat: path={{ checkout_dir }}/trunk
- register: trunk
-
-- name: check for branches
- stat: path={{ checkout_dir }}/branches
- register: branches
-
-- name: assert presence of tags/trunk/branches
- assert:
- that:
- - "tags.stat.isdir"
- - "trunk.stat.isdir"
- - "branches.stat.isdir"
-
-- name: checkout with quotes in username
- subversion: repo={{ repo }} dest={{ checkout_dir }} username="quoteme'''"
- register: subverted3
-
-- debug: var=subverted3
-
-- name: checkout with export
- subversion: repo={{ repo }} dest={{ output_dir }}/svn-export export=True
- register: subverted4
-
-- name: check for tags
- stat: path={{ output_dir }}/svn-export/tags
- register: export_tags
-
-- name: check for trunk
- stat: path={{ output_dir }}/svn-export/trunk
- register: export_trunk
-
-- name: check for branches
- stat: path={{ output_dir }}/svn-export/branches
- register: export_branches
-
-- name: assert presence of tags/trunk/branches in export
- assert:
- that:
- - "export_tags.stat.isdir"
- - "export_trunk.stat.isdir"
- - "export_branches.stat.isdir"
- - "subverted4.changed"
-
-# TBA: test for additional options or URL variants welcome
--
2.27.0