Add seed parameter to password lookup (#69775)
This commit is contained in:
parent
ffa548503d
commit
e2658801f6
4 changed files with 64 additions and 25 deletions
2
changelogs/fragments/password_seed.yml
Normal file
2
changelogs/fragments/password_seed.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- password - add new parameter ``seed`` in lookup plugin (https://github.com/ansible/ansible/pull/69775).
|
|
@ -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:
|
||||
|
|
|
@ -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)))
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
Loading…
Reference in a new issue