385 lines
13 KiB
Diff
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
|
|
|