Add seed parameter to password lookup (#69775)

This commit is contained in:
s-hamann 2021-06-04 16:20:44 +00:00 committed by GitHub
parent ffa548503d
commit e2658801f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 25 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- password - add new parameter ``seed`` in lookup plugin (https://github.com/ansible/ansible/pull/69775).

View file

@ -57,6 +57,13 @@ DOCUMENTATION = """
description: The length of the generated password.
default: 20
type: integer
seed:
version_added: "2.12"
description:
- A seed to initialize the random number generator.
- Identical seeds will yield identical passwords.
- Use this for random-but-idempotent password generation.
type: str
notes:
- A great alternative to the password lookup plugin,
if you don't need to generate random passwords on a per-host basis,
@ -100,6 +107,10 @@ EXAMPLES = """
- name: create lowercase 8 character name for Kubernetes pod name
set_fact:
random_pod_name: "web-{{ lookup('password', '/dev/null chars=ascii_lowercase,digits length=8') }}"
- name: create random but idempotent password
set_fact:
password: "{{ lookup('password', '/dev/null', seed=inventory_hostname) }}"
"""
RETURN = """
@ -125,7 +136,7 @@ from ansible.utils.path import makedirs_safe
DEFAULT_LENGTH = 20
VALID_PARAMS = frozenset(('length', 'encrypt', 'chars', 'ident'))
VALID_PARAMS = frozenset(('length', 'encrypt', 'chars', 'ident', 'seed'))
def _parse_parameters(term):
@ -164,6 +175,7 @@ def _parse_parameters(term):
params['length'] = int(params.get('length', DEFAULT_LENGTH))
params['encrypt'] = params.get('encrypt', None)
params['ident'] = params.get('ident', None)
params['seed'] = params.get('seed', None)
params['chars'] = params.get('chars', None)
if params['chars']:
@ -338,7 +350,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, params['seed'])
salt = None
changed = True
else:

View file

@ -48,7 +48,7 @@ _LOCK = multiprocessing.Lock()
DEFAULT_PASSWORD_LENGTH = 20
def random_password(length=DEFAULT_PASSWORD_LENGTH, chars=C.DEFAULT_PASSWORD_CHARS):
def random_password(length=DEFAULT_PASSWORD_LENGTH, chars=C.DEFAULT_PASSWORD_CHARS, seed=None):
'''Return a random password string of length containing only chars
:kwarg length: The number of characters in the new password. Defaults to 20.
@ -58,7 +58,10 @@ def random_password(length=DEFAULT_PASSWORD_LENGTH, chars=C.DEFAULT_PASSWORD_CHA
if not isinstance(chars, text_type):
raise AnsibleAssertionError('%s (%s) is not a text_type' % (chars, type(chars)))
random_generator = random.SystemRandom()
if seed is None:
random_generator = random.SystemRandom()
else:
random_generator = random.Random(seed)
return u''.join(random_generator.choice(chars) for dummy in range(length))

View file

@ -50,7 +50,7 @@ old_style_params_data = (
dict(
term=u'/path/to/file',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
@ -58,38 +58,39 @@ old_style_params_data = (
dict(
term=u'/path/with/embedded spaces and/file',
filename=u'/path/with/embedded spaces and/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
dict(
term=u'/path/with/equals/cn=com.ansible',
filename=u'/path/with/equals/cn=com.ansible',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
dict(
term=u'/path/with/unicode/くらとみ/file',
filename=u'/path/with/unicode/くらとみ/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
# Mix several special chars
dict(
term=u'/path/with/utf 8 and spaces/くらとみ/file',
filename=u'/path/with/utf 8 and spaces/くらとみ/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
dict(
term=u'/path/with/encoding=unicode/くらとみ/file',
filename=u'/path/with/encoding=unicode/くらとみ/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
dict(
term=u'/path/with/encoding=unicode/くらとみ/and spaces file',
filename=u'/path/with/encoding=unicode/くらとみ/and spaces file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
@ -97,39 +98,48 @@ old_style_params_data = (
dict(
term=u'/path/to/file length=42',
filename=u'/path/to/file',
params=dict(length=42, encrypt=None, ident=None, chars=DEFAULT_CHARS),
params=dict(length=42, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
dict(
term=u'/path/to/file encrypt=pbkdf2_sha256',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt='pbkdf2_sha256', ident=None, chars=DEFAULT_CHARS),
params=dict(length=password.DEFAULT_LENGTH, encrypt='pbkdf2_sha256', ident=None, chars=DEFAULT_CHARS, seed=None),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
dict(
term=u'/path/to/file chars=abcdefghijklmnop',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abcdefghijklmnop']),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abcdefghijklmnop'], seed=None),
candidate_chars=u'abcdefghijklmnop',
),
dict(
term=u'/path/to/file chars=digits,abc,def',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'digits', u'abc', u'def'])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'digits', u'abc', u'def']), seed=None),
candidate_chars=u'abcdef0123456789',
),
dict(
term=u'/path/to/file seed=1',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=DEFAULT_CHARS, seed='1'),
candidate_chars=DEFAULT_CANDIDATE_CHARS,
),
# Including comma in chars
dict(
term=u'/path/to/file chars=abcdefghijklmnop,,digits',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'abcdefghijklmnop', u',', u'digits'])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'abcdefghijklmnop', u',', u'digits']), seed=None),
candidate_chars=u',abcdefghijklmnop0123456789',
),
dict(
term=u'/path/to/file chars=,,',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u',']),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=[u','], seed=None),
candidate_chars=u',',
),
@ -137,13 +147,15 @@ old_style_params_data = (
dict(
term=u'/path/to/file chars=digits,=,,',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'digits', u'=', u','])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'digits', u'=', u',']), seed=None),
candidate_chars=u',=0123456789',
),
dict(
term=u'/path/to/file chars=digits,abc=def',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'digits', u'abc=def'])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'digits', u'abc=def']), seed=None),
candidate_chars=u'abc=def0123456789',
),
@ -151,14 +163,16 @@ old_style_params_data = (
dict(
term=u'/path/to/file chars=digits,くらとみ,,',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'digits', u'くらとみ', u','])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'digits', u'くらとみ', u',']), seed=None),
candidate_chars=u',0123456789くらとみ',
),
# Including only unicode in chars
dict(
term=u'/path/to/file chars=くらとみ',
filename=u'/path/to/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'くらとみ'])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'くらとみ']), seed=None),
candidate_chars=u'くらとみ',
),
@ -166,7 +180,8 @@ old_style_params_data = (
dict(
term=u'/path/to/file_with:colon chars=ascii_letters,digits',
filename=u'/path/to/file_with:colon',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=sorted([u'ascii_letters', u'digits'])),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None,
chars=sorted([u'ascii_letters', u'digits']), seed=None),
candidate_chars=u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
),
@ -175,19 +190,19 @@ old_style_params_data = (
dict(
term=u'/path/with/embedded spaces and/file chars=abc=def',
filename=u'/path/with/embedded spaces and/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abc=def']),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abc=def'], seed=None),
candidate_chars=u'abc=def',
),
dict(
term=u'/path/with/equals/cn=com.ansible chars=abc=def',
filename=u'/path/with/equals/cn=com.ansible',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abc=def']),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abc=def'], seed=None),
candidate_chars=u'abc=def',
),
dict(
term=u'/path/with/unicode/くらとみ/file chars=くらとみ',
filename=u'/path/with/unicode/くらとみ/file',
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'くらとみ']),
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'くらとみ'], seed=None),
candidate_chars=u'くらとみ',
),
)
@ -280,6 +295,13 @@ class TestRandomPassword(unittest.TestCase):
self._assert_valid_chars(res, u'くらとみ')
self.assertEqual(len(res), 11)
def test_seed(self):
pw1 = password.random_password(seed=1)
pw2 = password.random_password(seed=1)
pw3 = password.random_password(seed=2)
self.assertEqual(pw1, pw2)
self.assertNotEqual(pw1, pw3)
def test_gen_password(self):
for testcase in old_style_params_data:
params = testcase['params']