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:
@@ -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,
|
||||||
|
@@ -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)
|
||||||
|
@@ -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 ({{}}).
|
Reference in New Issue
Block a user