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.
|
description: The length of the generated password.
|
||||||
default: 20
|
default: 20
|
||||||
type: integer
|
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:
|
notes:
|
||||||
- A great alternative to the password lookup plugin,
|
- A great alternative to the password lookup plugin,
|
||||||
if you don't need to generate random passwords on a per-host basis,
|
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
|
- name: create lowercase 8 character name for Kubernetes pod name
|
||||||
set_fact:
|
set_fact:
|
||||||
random_pod_name: "web-{{ lookup('password', '/dev/null chars=ascii_lowercase,digits length=8') }}"
|
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 = """
|
RETURN = """
|
||||||
|
@ -125,7 +136,7 @@ from ansible.utils.path import makedirs_safe
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_LENGTH = 20
|
DEFAULT_LENGTH = 20
|
||||||
VALID_PARAMS = frozenset(('length', 'encrypt', 'chars', 'ident'))
|
VALID_PARAMS = frozenset(('length', 'encrypt', 'chars', 'ident', 'seed'))
|
||||||
|
|
||||||
|
|
||||||
def _parse_parameters(term):
|
def _parse_parameters(term):
|
||||||
|
@ -164,6 +175,7 @@ def _parse_parameters(term):
|
||||||
params['length'] = int(params.get('length', DEFAULT_LENGTH))
|
params['length'] = int(params.get('length', DEFAULT_LENGTH))
|
||||||
params['encrypt'] = params.get('encrypt', None)
|
params['encrypt'] = params.get('encrypt', None)
|
||||||
params['ident'] = params.get('ident', None)
|
params['ident'] = params.get('ident', None)
|
||||||
|
params['seed'] = params.get('seed', None)
|
||||||
|
|
||||||
params['chars'] = params.get('chars', None)
|
params['chars'] = params.get('chars', None)
|
||||||
if params['chars']:
|
if params['chars']:
|
||||||
|
@ -338,7 +350,7 @@ class LookupModule(LookupBase):
|
||||||
content = _read_password_file(b_path)
|
content = _read_password_file(b_path)
|
||||||
|
|
||||||
if content is None or b_path == to_bytes('/dev/null'):
|
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
|
salt = None
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -48,7 +48,7 @@ _LOCK = multiprocessing.Lock()
|
||||||
DEFAULT_PASSWORD_LENGTH = 20
|
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
|
'''Return a random password string of length containing only chars
|
||||||
|
|
||||||
:kwarg length: The number of characters in the new password. Defaults to 20.
|
: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):
|
if not isinstance(chars, text_type):
|
||||||
raise AnsibleAssertionError('%s (%s) is not a text_type' % (chars, type(chars)))
|
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))
|
return u''.join(random_generator.choice(chars) for dummy in range(length))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file',
|
term=u'/path/to/file',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -58,38 +58,39 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/embedded spaces and/file',
|
term=u'/path/with/embedded spaces and/file',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/equals/cn=com.ansible',
|
term=u'/path/with/equals/cn=com.ansible',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/unicode/くらとみ/file',
|
term=u'/path/with/unicode/くらとみ/file',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
|
|
||||||
# Mix several special chars
|
# Mix several special chars
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/utf 8 and spaces/くらとみ/file',
|
term=u'/path/with/utf 8 and spaces/くらとみ/file',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/encoding=unicode/くらとみ/file',
|
term=u'/path/with/encoding=unicode/くらとみ/file',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/encoding=unicode/くらとみ/and spaces file',
|
term=u'/path/with/encoding=unicode/くらとみ/and spaces file',
|
||||||
filename=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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -97,39 +98,48 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file length=42',
|
term=u'/path/to/file length=42',
|
||||||
filename=u'/path/to/file',
|
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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file encrypt=pbkdf2_sha256',
|
term=u'/path/to/file encrypt=pbkdf2_sha256',
|
||||||
filename=u'/path/to/file',
|
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,
|
candidate_chars=DEFAULT_CANDIDATE_CHARS,
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=abcdefghijklmnop',
|
term=u'/path/to/file chars=abcdefghijklmnop',
|
||||||
filename=u'/path/to/file',
|
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',
|
candidate_chars=u'abcdefghijklmnop',
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=digits,abc,def',
|
term=u'/path/to/file chars=digits,abc,def',
|
||||||
filename=u'/path/to/file',
|
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',
|
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
|
# Including comma in chars
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=abcdefghijklmnop,,digits',
|
term=u'/path/to/file chars=abcdefghijklmnop,,digits',
|
||||||
filename=u'/path/to/file',
|
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',
|
candidate_chars=u',abcdefghijklmnop0123456789',
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=,,',
|
term=u'/path/to/file chars=,,',
|
||||||
filename=u'/path/to/file',
|
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',',
|
candidate_chars=u',',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -137,13 +147,15 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=digits,=,,',
|
term=u'/path/to/file chars=digits,=,,',
|
||||||
filename=u'/path/to/file',
|
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',
|
candidate_chars=u',=0123456789',
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=digits,abc=def',
|
term=u'/path/to/file chars=digits,abc=def',
|
||||||
filename=u'/path/to/file',
|
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',
|
candidate_chars=u'abc=def0123456789',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -151,14 +163,16 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=digits,くらとみ,,',
|
term=u'/path/to/file chars=digits,くらとみ,,',
|
||||||
filename=u'/path/to/file',
|
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くらとみ',
|
candidate_chars=u',0123456789くらとみ',
|
||||||
),
|
),
|
||||||
# Including only unicode in chars
|
# Including only unicode in chars
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file chars=くらとみ',
|
term=u'/path/to/file chars=くらとみ',
|
||||||
filename=u'/path/to/file',
|
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'くらとみ',
|
candidate_chars=u'くらとみ',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -166,7 +180,8 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/to/file_with:colon chars=ascii_letters,digits',
|
term=u'/path/to/file_with:colon chars=ascii_letters,digits',
|
||||||
filename=u'/path/to/file_with:colon',
|
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',
|
candidate_chars=u'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -175,19 +190,19 @@ old_style_params_data = (
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/embedded spaces and/file chars=abc=def',
|
term=u'/path/with/embedded spaces and/file chars=abc=def',
|
||||||
filename=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=[u'abc=def']),
|
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abc=def'], seed=None),
|
||||||
candidate_chars=u'abc=def',
|
candidate_chars=u'abc=def',
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/equals/cn=com.ansible chars=abc=def',
|
term=u'/path/with/equals/cn=com.ansible chars=abc=def',
|
||||||
filename=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=[u'abc=def']),
|
params=dict(length=password.DEFAULT_LENGTH, encrypt=None, ident=None, chars=[u'abc=def'], seed=None),
|
||||||
candidate_chars=u'abc=def',
|
candidate_chars=u'abc=def',
|
||||||
),
|
),
|
||||||
dict(
|
dict(
|
||||||
term=u'/path/with/unicode/くらとみ/file chars=くらとみ',
|
term=u'/path/with/unicode/くらとみ/file chars=くらとみ',
|
||||||
filename=u'/path/with/unicode/くらとみ/file',
|
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'くらとみ',
|
candidate_chars=u'くらとみ',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -280,6 +295,13 @@ class TestRandomPassword(unittest.TestCase):
|
||||||
self._assert_valid_chars(res, u'くらとみ')
|
self._assert_valid_chars(res, u'くらとみ')
|
||||||
self.assertEqual(len(res), 11)
|
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):
|
def test_gen_password(self):
|
||||||
for testcase in old_style_params_data:
|
for testcase in old_style_params_data:
|
||||||
params = testcase['params']
|
params = testcase['params']
|
||||||
|
|
Loading…
Reference in a new issue