community.general/plugins/lookup/random_string.py
patchback[bot] dac26d12bd
[PR #10710/b5a2c581 backport][stable-11] random_string: Specify seed while generating random string (#10748)
random_string: Specify seed while generating random string (#10710)

* random_string: Specify seed while generating random string

* Allow user to specify seed to generate random string

Fixes: #5362



* Apply suggestions from code review



---------



(cherry picked from commit b5a2c5812c)

Signed-off-by: Abhijeet Kasurde <Akasurde@redhat.com>
Co-authored-by: Abhijeet Kasurde <akasurde@redhat.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
2025-08-27 22:09:41 +02:00

270 lines
9.2 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2021, Abhijeet Kasurde <akasurde@redhat.com>
# Copyright (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r"""
name: random_string
author:
- Abhijeet Kasurde (@Akasurde)
short_description: Generates random string
version_added: '3.2.0'
description:
- Generates random string based upon the given constraints.
- Uses L(random.SystemRandom,https://docs.python.org/3/library/random.html#random.SystemRandom), so should be strong enough
for cryptographic purposes.
options:
length:
description: The length of the string.
default: 8
type: int
upper:
description:
- Possibly include uppercase letters in the string.
- To ensure atleast one uppercase letter, set O(min_upper) to V(1).
default: true
type: bool
lower:
description:
- Possibly include lowercase letters in the string.
- To ensure atleast one lowercase letter, set O(min_lower) to V(1).
default: true
type: bool
numbers:
description:
- Possibly include numbers in the string.
- To ensure atleast one numeric character, set O(min_numeric) to V(1).
default: true
type: bool
special:
description:
- Possibly include special characters in the string.
- Special characters are taken from Python standard library C(string). See L(the documentation of
string.punctuation,https://docs.python.org/3/library/string.html#string.punctuation)
for which characters are used.
- The choice of special characters can be changed to setting O(override_special).
- To ensure atleast one special character, set O(min_special) to V(1).
default: true
type: bool
min_numeric:
description:
- Minimum number of numeric characters in the string.
- If set, overrides O(numbers=false).
default: 0
type: int
min_upper:
description:
- Minimum number of uppercase alphabets in the string.
- If set, overrides O(upper=false).
default: 0
type: int
min_lower:
description:
- Minimum number of lowercase alphabets in the string.
- If set, overrides O(lower=false).
default: 0
type: int
min_special:
description:
- Minimum number of special character in the string.
default: 0
type: int
override_special:
description:
- Override a list of special characters to use in the string.
- If set O(min_special) should be set to a non-default value.
type: str
override_all:
description:
- Override all values of O(numbers), O(upper), O(lower), and O(special) with the given list of characters.
type: str
ignore_similar_chars:
description:
- Ignore similar characters, such as V(l) and V(1), or V(O) and V(0).
- These characters can be configured in O(similar_chars).
default: false
type: bool
version_added: 7.5.0
similar_chars:
description:
- Override a list of characters not to be use in the string.
default: "il1LoO0"
type: str
version_added: 7.5.0
base64:
description:
- Returns base64 encoded string.
type: bool
default: false
seed:
description:
- Seed for random string generator.
- B(Note) that this drastically reduces the security of this plugin. First, when O(seed) is provided, a non-cryptographic random number generator is used.
Second, if the seed does not contain enough entropy, the generated string is weak.
B(Do not use the generated string as a password or a secure token when using this option!)
type: str
version_added: 11.3.0
"""
EXAMPLES = r"""
- name: Generate random string
ansible.builtin.debug:
var: lookup('community.general.random_string')
# Example result: 'DeadBeeF'
- name: Generate random string with seed
ansible.builtin.debug:
var: lookup('community.general.random_string', seed=12345)
# Example result: '6[~(2q5O'
# NOTE: Do **not** use this string as a password or a secure token,
# unless you know exactly what you are doing!
# Specifying seed uses a non-secure random number generator.
- name: Generate random string with length 12
ansible.builtin.debug:
var: lookup('community.general.random_string', length=12)
# Example result: 'Uan0hUiX5kVG'
- name: Generate base64 encoded random string
ansible.builtin.debug:
var: lookup('community.general.random_string', base64=True)
# Example result: 'NHZ6eWN5Qk0='
- name: Generate a random string with 1 lower, 1 upper, 1 number and 1 special char (at least)
ansible.builtin.debug:
var: lookup('community.general.random_string', min_lower=1, min_upper=1, min_special=1, min_numeric=1)
# Example result: '&Qw2|E[-'
- name: Generate a random string with all lower case characters
ansible.builtin.debug:
var: query('community.general.random_string', upper=false, numbers=false, special=false)
# Example result: ['exolxzyz']
- name: Generate random hexadecimal string
ansible.builtin.debug:
var: query('community.general.random_string', upper=false, lower=false, override_special=hex_chars, numbers=false)
vars:
hex_chars: '0123456789ABCDEF'
# Example result: ['D2A40737']
- name: Generate random hexadecimal string with override_all
ansible.builtin.debug:
var: query('community.general.random_string', override_all=hex_chars)
vars:
hex_chars: '0123456789ABCDEF'
# Example result: ['D2A40737']
"""
RETURN = r"""
_raw:
description: A one-element list containing a random string.
type: list
elements: str
"""
import base64
import random
import string
from ansible.errors import AnsibleLookupError
from ansible.plugins.lookup import LookupBase
from ansible.module_utils.common.text.converters import to_bytes, to_text
class LookupModule(LookupBase):
@staticmethod
def get_random(random_generator, chars, length):
if not chars:
raise AnsibleLookupError(
"Available characters cannot be None, please change constraints"
)
return "".join(random_generator.choice(chars) for dummy in range(length))
@staticmethod
def b64encode(string_value, encoding="utf-8"):
return to_text(
base64.b64encode(
to_bytes(string_value, encoding=encoding, errors="surrogate_or_strict")
)
)
def run(self, terms, variables=None, **kwargs):
number_chars = string.digits
lower_chars = string.ascii_lowercase
upper_chars = string.ascii_uppercase
special_chars = string.punctuation
self.set_options(var_options=variables, direct=kwargs)
length = self.get_option("length")
base64_flag = self.get_option("base64")
override_all = self.get_option("override_all")
ignore_similar_chars = self.get_option("ignore_similar_chars")
similar_chars = self.get_option("similar_chars")
seed = self.get_option("seed")
if seed is None:
random_generator = random.SystemRandom()
else:
random_generator = random.Random(seed)
values = ""
available_chars_set = ""
if ignore_similar_chars:
number_chars = "".join([sc for sc in number_chars if sc not in similar_chars])
lower_chars = "".join([sc for sc in lower_chars if sc not in similar_chars])
upper_chars = "".join([sc for sc in upper_chars if sc not in similar_chars])
special_chars = "".join([sc for sc in special_chars if sc not in similar_chars])
if override_all:
# Override all the values
available_chars_set = override_all
else:
upper = self.get_option("upper")
lower = self.get_option("lower")
numbers = self.get_option("numbers")
special = self.get_option("special")
override_special = self.get_option("override_special")
if override_special:
special_chars = override_special
if upper:
available_chars_set += upper_chars
if lower:
available_chars_set += lower_chars
if numbers:
available_chars_set += number_chars
if special:
available_chars_set += special_chars
mapping = {
"min_numeric": number_chars,
"min_lower": lower_chars,
"min_upper": upper_chars,
"min_special": special_chars,
}
for m in mapping:
if self.get_option(m):
values += self.get_random(random_generator, mapping[m], self.get_option(m))
remaining_pass_len = length - len(values)
values += self.get_random(random_generator, available_chars_set, remaining_pass_len)
shuffled_values = list(values)
if seed is None:
# Get pseudo randomization
# Randomize the order
random.shuffle(shuffled_values)
if base64_flag:
return [self.b64encode("".join(shuffled_values))]
return ["".join(shuffled_values)]