* [stable-2.6] Wrap CLI passwords as AnsibleUnsafeText (#63352)
* isa string should rewrap as unsafe in get_validated_value
* _is_unsafe shouldn't be concerned with underlying types
* Start with passwords as text, instead of bytes
* Remove unused imports
* Add changelog fragment
* Update changelog with CVE.
(cherry picked from commit baeff7462d
)
Co-authored-by: Matt Martz <matt@sivel.net>
* Update tests
This commit is contained in:
parent
d961f676c0
commit
16684f1187
8 changed files with 52 additions and 8 deletions
6
changelogs/fragments/dont-template-cli-passwords.yml
Normal file
6
changelogs/fragments/dont-template-cli-passwords.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
bugfixes:
|
||||
- >
|
||||
**security issue** - Convert CLI provided passwords to text initially, to
|
||||
prevent unsafe context being lost when converting from bytes->text during
|
||||
post processing of PlayContext. This prevents CLI provided passwords from
|
||||
being incorrectly templated (CVE-2019-14856)
|
|
@ -42,7 +42,7 @@ from ansible.parsing.dataloader import DataLoader
|
|||
from ansible.release import __version__
|
||||
from ansible.utils.path import unfrackpath
|
||||
from ansible.utils.vars import load_extra_vars, load_options_vars
|
||||
from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes
|
||||
from ansible.utils.unsafe_proxy import AnsibleUnsafeText
|
||||
from ansible.vars.manager import VariableManager
|
||||
from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret
|
||||
|
||||
|
@ -329,8 +329,6 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
if op.ask_pass:
|
||||
sshpass = getpass.getpass(prompt="SSH password: ")
|
||||
become_prompt = "%s password[defaults to SSH password]: " % become_prompt_method
|
||||
if sshpass:
|
||||
sshpass = to_bytes(sshpass, errors='strict', nonstring='simplerepr')
|
||||
else:
|
||||
become_prompt = "%s password: " % become_prompt_method
|
||||
|
||||
|
@ -338,17 +336,15 @@ class CLI(with_metaclass(ABCMeta, object)):
|
|||
becomepass = getpass.getpass(prompt=become_prompt)
|
||||
if op.ask_pass and becomepass == '':
|
||||
becomepass = sshpass
|
||||
if becomepass:
|
||||
becomepass = to_bytes(becomepass)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
# we 'wrap' the passwords to prevent templating as
|
||||
# they can contain special chars and trigger it incorrectly
|
||||
if sshpass:
|
||||
sshpass = AnsibleUnsafeBytes(sshpass)
|
||||
sshpass = AnsibleUnsafeText(to_text(sshpass))
|
||||
if becomepass:
|
||||
becomepass = AnsibleUnsafeBytes(becomepass)
|
||||
becomepass = AnsibleUnsafeText(to_text(becomepass))
|
||||
|
||||
return (sshpass, becomepass)
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ class AnsibleContext(Context):
|
|||
for item in val:
|
||||
if self._is_unsafe(item):
|
||||
return True
|
||||
elif isinstance(val, string_types) and hasattr(val, '__UNSAFE__'):
|
||||
elif hasattr(val, '__UNSAFE__'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
|
2
test/integration/targets/cli/aliases
Normal file
2
test/integration/targets/cli/aliases
Normal file
|
@ -0,0 +1,2 @@
|
|||
needs/target/setup_pexpect
|
||||
shippable/posix/group3
|
7
test/integration/targets/cli/runme.sh
Executable file
7
test/integration/targets/cli/runme.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eux
|
||||
|
||||
ANSIBLE_ROLES_PATH=../ ansible-playbook setup.yml
|
||||
|
||||
python test-cli.py
|
4
test/integration/targets/cli/setup.yml
Normal file
4
test/integration/targets/cli/setup.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
- hosts: localhost
|
||||
gather_facts: no
|
||||
roles:
|
||||
- setup_pexpect
|
21
test/integration/targets/cli/test-cli.py
Executable file
21
test/integration/targets/cli/test-cli.py
Executable file
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python
|
||||
# Copyright (c) 2019 Matt Martz <matt@sivel.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
import pexpect
|
||||
|
||||
os.environ['ANSIBLE_NOCOLOR'] = '1'
|
||||
out = pexpect.run(
|
||||
'ansible localhost -m debug -a msg="{{ ansible_password }}" -k',
|
||||
events={
|
||||
'SSH password:': '{{ 1 + 2 }}\n'
|
||||
}
|
||||
)
|
||||
|
||||
assert b'{{ 1 + 2 }}' in out
|
|
@ -26,6 +26,7 @@ from ansible.module_utils.six import string_types
|
|||
from ansible.playbook.attribute import FieldAttribute
|
||||
from ansible.template import Templar
|
||||
from ansible.playbook import base
|
||||
from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes, AnsibleUnsafeText
|
||||
|
||||
from units.mock.loader import DictDataLoader
|
||||
|
||||
|
@ -632,3 +633,10 @@ class TestBaseSubClass(TestBase):
|
|||
ds = {'test_attr_method_missing': a_string}
|
||||
bsc = self._base_validate(ds)
|
||||
self.assertEquals(bsc.test_attr_method_missing, a_string)
|
||||
|
||||
def test_get_validated_value_string_rewrap_unsafe(self):
|
||||
value = AnsibleUnsafeText(u'bar')
|
||||
ds = {'test_attr_string': value}
|
||||
bsc = self._base_validate(ds)
|
||||
self.assertIsInstance(bsc.test_attr_string, AnsibleUnsafeText)
|
||||
self.assertEquals(bsc.test_attr_string, AnsibleUnsafeText(u'bar'))
|
||||
|
|
Loading…
Reference in a new issue