mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 12:50:22 -07:00
[passwordstore] Use builtin _random_password function instead of pwgen (#25843)
* [password] _random_password -> random_password and moved to util/encrypt.py * [passwordstore] Use built-in random_password instead of pwgen utility * [passwordstore] Add integration tests
This commit is contained in:
parent
f345ba5c38
commit
554496c404
21 changed files with 217 additions and 49 deletions
|
@ -21,15 +21,12 @@ __metaclass__ = type
|
|||
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six import text_type
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.parsing.splitter import parse_kv
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
from ansible.utils.encrypt import do_encrypt
|
||||
from ansible.utils.encrypt import do_encrypt, random_password
|
||||
from ansible.utils.path import makedirs_safe
|
||||
|
||||
|
||||
|
@ -136,35 +133,13 @@ def _gen_candidate_chars(characters):
|
|||
return chars
|
||||
|
||||
|
||||
def _random_password(length=DEFAULT_LENGTH, chars=C.DEFAULT_PASSWORD_CHARS):
|
||||
'''Return a random password string of length containing only chars
|
||||
|
||||
:kwarg length: The number of characters in the new password. Defaults to 20.
|
||||
:kwarg chars: The characters to choose from. The default is all ascii
|
||||
letters, ascii digits, and these symbols ``.,:-_``
|
||||
|
||||
.. note: this was moved from the old ansible utils code, as nothing
|
||||
else appeared to use it.
|
||||
'''
|
||||
assert isinstance(chars, text_type), '%s (%s) is not a text_type' % (chars, type(chars))
|
||||
|
||||
random_generator = random.SystemRandom()
|
||||
|
||||
password = []
|
||||
while len(password) < length:
|
||||
new_char = random_generator.choice(chars)
|
||||
password.append(new_char)
|
||||
|
||||
return u''.join(password)
|
||||
|
||||
|
||||
def _random_salt():
|
||||
"""Return a text string suitable for use as a salt for the hash functions we use to encrypt passwords.
|
||||
"""
|
||||
# Note passlib salt values must be pure ascii so we can't let the user
|
||||
# configure this
|
||||
salt_chars = _gen_candidate_chars(['ascii_letters', 'digits', './'])
|
||||
return _random_password(length=8, chars=salt_chars)
|
||||
return random_password(length=8, chars=salt_chars)
|
||||
|
||||
|
||||
def _parse_content(content):
|
||||
|
@ -234,7 +209,7 @@ class LookupModule(LookupBase):
|
|||
content = _read_password_file(b_path)
|
||||
|
||||
if content is None or b_path == to_bytes('/dev/null'):
|
||||
plaintext_password = _random_password(params['length'], chars)
|
||||
plaintext_password = random_password(params['length'], chars)
|
||||
salt = None
|
||||
changed = True
|
||||
else:
|
||||
|
|
|
@ -20,8 +20,11 @@ __metaclass__ = type
|
|||
import os
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
from distutils import util
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.utils.encrypt import random_password
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
|
||||
|
@ -35,25 +38,31 @@ def check_output2(*popenargs, **kwargs):
|
|||
if 'input' in kwargs:
|
||||
if 'stdin' in kwargs:
|
||||
raise ValueError('stdin and input arguments may not both be used.')
|
||||
inputdata = kwargs['input']
|
||||
b_inputdata = to_bytes(kwargs['input'], errors='surrogate_or_strict')
|
||||
del kwargs['input']
|
||||
kwargs['stdin'] = subprocess.PIPE
|
||||
else:
|
||||
inputdata = None
|
||||
b_inputdata = None
|
||||
process = subprocess.Popen(*popenargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
|
||||
try:
|
||||
out, err = process.communicate(inputdata)
|
||||
b_out, b_err = process.communicate(b_inputdata)
|
||||
except:
|
||||
process.kill()
|
||||
process.wait()
|
||||
raise
|
||||
retcode = process.poll()
|
||||
if retcode:
|
||||
if retcode != 0 or \
|
||||
b'encryption failed: Unusable public key' in b_out or \
|
||||
b'encryption failed: Unusable public key' in b_err:
|
||||
cmd = kwargs.get("args")
|
||||
if cmd is None:
|
||||
cmd = popenargs[0]
|
||||
raise subprocess.CalledProcessError(retcode, cmd, out + err)
|
||||
return out
|
||||
raise subprocess.CalledProcessError(
|
||||
retcode,
|
||||
cmd,
|
||||
to_native(b_out + b_err, errors='surrogate_or_strict')
|
||||
)
|
||||
return b_out
|
||||
|
||||
|
||||
class LookupModule(LookupBase):
|
||||
|
@ -95,11 +104,14 @@ class LookupModule(LookupBase):
|
|||
|
||||
def check_pass(self):
|
||||
try:
|
||||
self.passoutput = check_output2(["pass", self.passname]).splitlines()
|
||||
self.passoutput = to_text(
|
||||
check_output2(["pass", self.passname]),
|
||||
errors='surrogate_or_strict'
|
||||
).splitlines()
|
||||
self.password = self.passoutput[0]
|
||||
self.passdict = {}
|
||||
for line in self.passoutput[1:]:
|
||||
if ":" in line:
|
||||
if ':' in line:
|
||||
name, value = line.split(':', 1)
|
||||
self.passdict[name.strip()] = value.strip()
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
|
@ -118,10 +130,7 @@ class LookupModule(LookupBase):
|
|||
if self.paramvals['userpass']:
|
||||
newpass = self.paramvals['userpass']
|
||||
else:
|
||||
try:
|
||||
newpass = check_output2(['pwgen', '-cns', str(self.paramvals['length']), '1']).rstrip()
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
raise AnsibleError(e)
|
||||
newpass = random_password(length=self.paramvals['length'])
|
||||
return newpass
|
||||
|
||||
def update_password(self):
|
||||
|
@ -131,7 +140,7 @@ class LookupModule(LookupBase):
|
|||
msg = newpass + '\n' + '\n'.join(self.passoutput[1:])
|
||||
msg += "\nlookup_pass: old password was {} (Updated on {})\n".format(self.password, datetime)
|
||||
try:
|
||||
generate = check_output2(['pass', 'insert', '-f', '-m', self.passname], input=msg)
|
||||
check_output2(['pass', 'insert', '-f', '-m', self.passname], input=msg)
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
raise AnsibleError(e)
|
||||
return newpass
|
||||
|
@ -143,7 +152,7 @@ class LookupModule(LookupBase):
|
|||
datetime = time.strftime("%d/%m/%Y %H:%M:%S")
|
||||
msg = newpass + '\n' + "lookup_pass: First generated by ansible on {}\n".format(datetime)
|
||||
try:
|
||||
generate = check_output2(['pass', 'insert', '-f', '-m', self.passname], input=msg)
|
||||
check_output2(['pass', 'insert', '-f', '-m', self.passname], input=msg)
|
||||
except (subprocess.CalledProcessError) as e:
|
||||
raise AnsibleError(e)
|
||||
return newpass
|
||||
|
|
|
@ -23,11 +23,14 @@ import stat
|
|||
import tempfile
|
||||
import time
|
||||
import warnings
|
||||
import random
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six import text_type
|
||||
from ansible.module_utils._text import to_text, to_bytes
|
||||
|
||||
|
||||
PASSLIB_AVAILABLE = False
|
||||
try:
|
||||
import passlib.hash
|
||||
|
@ -162,3 +165,19 @@ def keyczar_decrypt(key, msg):
|
|||
return key.Decrypt(msg)
|
||||
except key_errors.InvalidSignatureError:
|
||||
raise AnsibleError("decryption failed")
|
||||
|
||||
|
||||
DEFAULT_PASSWORD_LENGTH = 20
|
||||
|
||||
|
||||
def random_password(length=DEFAULT_PASSWORD_LENGTH, chars=C.DEFAULT_PASSWORD_CHARS):
|
||||
'''Return a random password string of length containing only chars
|
||||
|
||||
:kwarg length: The number of characters in the new password. Defaults to 20.
|
||||
:kwarg chars: The characters to choose from. The default is all ascii
|
||||
letters, ascii digits, and these symbols ``.,:-_``
|
||||
'''
|
||||
assert isinstance(chars, text_type), '%s (%s) is not a text_type' % (chars, type(chars))
|
||||
|
||||
random_generator = random.SystemRandom()
|
||||
return u''.join(random_generator.choice(chars) for dummy in range(length))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue