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 argparse
import logging import logging
import os import os
import re
import secrets
import string
import sys import sys
import openstack import openstack
@@ -258,6 +261,18 @@ def get_arg_parser():
$ discover-tempest-config \\ $ discover-tempest-config \\
identity.username myname \\ identity.username myname \\
identity.password mypass""") 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, parser.add_argument('--debug', action='store_true', default=False,
help='Print debugging information.') help='Print debugging information.')
parser.add_argument('--verbose', '-v', action='store_true', default=False, parser.add_argument('--verbose', '-v', action='store_true', default=False,
@@ -451,6 +466,30 @@ def parse_overrides(overrides):
return new_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): def set_cloud_config_values(non_admin, cloud_creds, conf):
"""Set values from client's cloud config file. """Set values from client's cloud config file.
@@ -553,6 +592,12 @@ def config_tempest(**kwargs):
clients = ClientManager(conf, credentials) clients = ClientManager(conf, credentials)
if kwargs.get('create', False) and kwargs.get('test_accounts') is None: 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 = Users(clients.projects, clients.roles, clients.users, conf)
users.create_tempest_users() users.create_tempest_users()
@@ -632,6 +677,8 @@ def main():
os_cloud=args.os_cloud, os_cloud=args.os_cloud,
out=args.out, out=args.out,
overrides=args.overrides, overrides=args.overrides,
password_regex=args.password_regex,
password_length=args.password_length,
remove=args.remove, remove=args.remove,
test_accounts=args.test_accounts, test_accounts=args.test_accounts,
verbose=args.verbose, verbose=args.verbose,

View File

@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import re
from unittest import mock from unittest import mock
from fixtures import MonkeyPatch from fixtures import MonkeyPatch
@@ -119,3 +120,38 @@ class TestOsClientConfigSupport(BaseConfigTempestTest):
self.conf.get('auth', 'admin_username'), self.conf.get('auth', 'admin_username'),
self.conf.get('auth', 'admin_password'), self.conf.get('auth', 'admin_password'),
self.conf.get('auth', 'admin_project_name')) 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 ({{}}).