Add password-regex parameter

As it is now, python-tempestconf can't work with password_regex
restriction given by keystone. To give users an option to set
password that meets the required regex, we added a new option.

Change-Id: Ide61a222d2dac79c15fbe41c058410e091531aef
Signed-off-by: Katarina Strenkova <kstrenko@redhat.com>
This commit is contained in:
Katarina Strenkova
2025-07-29 09:44:23 +00:00
parent 405c80b5c1
commit 8515371b7c
3 changed files with 94 additions and 0 deletions

View File

@@ -37,6 +37,9 @@ obtained by querying the cloud.
import argparse
import logging
import os
import re
import secrets
import string
import sys
import openstack
@@ -258,6 +261,18 @@ def get_arg_parser():
$ discover-tempest-config \\
identity.username myname \\
identity.password mypass""")
parser.add_argument('--password-regex', default=None,
help="""Define the password regex required to
generate a password that meets these
conditions. NOTE: In regular expression use
double curly brackets `{{`instead of single
curly bracket `{` to make the command pass.
""")
parser.add_argument('--password-length', default=8,
help="""Specify the password length in combination
with the --password-regex option to generate
a password that meets these conditions.
""")
parser.add_argument('--debug', action='store_true', default=False,
help='Print debugging information.')
parser.add_argument('--verbose', '-v', action='store_true', default=False,
@@ -451,6 +466,30 @@ def parse_overrides(overrides):
return new_overrides
def generate_password_by_regex(regex, length):
"""Generate a new password that meets the required regex and length.
:type regex: string
:type length: int
"""
characters = string.ascii_letters + string.digits + string.punctuation
regex = regex.replace('{{', '{').replace('}}', '}')
try:
regex_object = re.compile(regex)
except re.error:
raise Exception("The provided regex %s is invalid." % regex)
for _ in range(200):
password = ''.join(secrets.choice(characters) for i in range(length))
if regex_object.fullmatch(password):
return password
raise Exception("Could not generate a password matching the specified"
" regex. Check if the regex %s is possible with the"
" given length %d." % (regex, length))
def set_cloud_config_values(non_admin, cloud_creds, conf):
"""Set values from client's cloud config file.
@@ -553,6 +592,12 @@ def config_tempest(**kwargs):
clients = ClientManager(conf, credentials)
if kwargs.get('create', False) and kwargs.get('test_accounts') is None:
if kwargs.get('password_regex') is not None:
password_regex = kwargs.get('password_regex')
password_length = kwargs.get('password_length')
new_password = generate_password_by_regex(password_regex,
int(password_length))
conf.set('identity', 'password', new_password)
users = Users(clients.projects, clients.roles, clients.users, conf)
users.create_tempest_users()
@@ -632,6 +677,8 @@ def main():
os_cloud=args.os_cloud,
out=args.out,
overrides=args.overrides,
password_regex=args.password_regex,
password_length=args.password_length,
remove=args.remove,
test_accounts=args.test_accounts,
verbose=args.verbose,

View File

@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
from unittest import mock
from fixtures import MonkeyPatch
@@ -119,3 +120,38 @@ class TestOsClientConfigSupport(BaseConfigTempestTest):
self.conf.get('auth', 'admin_username'),
self.conf.get('auth', 'admin_password'),
self.conf.get('auth', 'admin_project_name'))
class TestGeneratePassword(BaseConfigTempestTest):
def test_generate_password_success_easy(self):
regex = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$'
length = 12
password = tool.generate_password_by_regex(regex, length)
self.assertIsNotNone(password)
self.assertEqual(length, len(password))
self.assertTrue(re.fullmatch(regex, password))
def test_generate_password_success_hard(self):
regex = r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*_+-=]).{8}$'
length = 8
password = tool.generate_password_by_regex(regex, length)
self.assertIsNotNone(password)
self.assertEqual(length, len(password))
self.assertTrue(re.fullmatch(regex, password))
def test_generate_password_invalid_regex(self):
invalid_regex = r'['
exc = Exception
self.assertRaises(exc,
tool.generate_password_by_regex,
invalid_regex, 8)
def test_generate_password_impossible(self):
impossible_regex = r'^[a-z]{10}$'
exc = Exception
self.assertRaises(exc,
tool.generate_password_by_regex,
impossible_regex, 8)

View File

@@ -0,0 +1,11 @@
---
features:
- |
New parameters, ``--password-regex`` and ``--password-length`` have been
added to allow users to generate a password following these constraints.
It is helpful for meeting password requirements given by services (e.g.
Keystone). When using both parameters, ensure the value for
``--password-length`` does not conflict with any length defined within
the regular expression. Furthermore, to use a regex from the command
line, you must modify it by changing all single curly brackets ({}) to
double curly brackets ({{}}).