summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Erdfelt <johannes.erdfelt@rackspace.com>2011-12-29 17:23:27 +0000
committerJohannes Erdfelt <johannes.erdfelt@rackspace.com>2011-12-29 19:38:24 +0000
commit52b96e7b45bd3528bcf66e18b4f1491655597775 (patch)
tree92a607638345b7b332aa41af7b745ade1198aaef
parent783ac027f934b25e225619f9cf2cf26814fd7da6 (diff)
downloadnova-52b96e7b45bd3528bcf66e18b4f1491655597775.tar.gz
nova-52b96e7b45bd3528bcf66e18b4f1491655597775.tar.xz
nova-52b96e7b45bd3528bcf66e18b4f1491655597775.zip
Ensure generated passwords meet minimum complexity
Windows has a complexity requirement of at least three of these criteria: - one or more upper case characters - one or more lower case characters - one or more numbers - one or more special characters In some cases, the passwords generated didn't meet three of these four critera. This change enforces that three of these criteria will be met in the generated passwords. Change-Id: Ibe0055b8830b426aee1c9b722cc2fae2f5db4c5c
-rw-r--r--nova/tests/test_utils.py8
-rw-r--r--nova/utils.py35
2 files changed, 37 insertions, 6 deletions
diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py
index 513461dcc..92376f1f2 100644
--- a/nova/tests/test_utils.py
+++ b/nova/tests/test_utils.py
@@ -360,6 +360,14 @@ class GenericUtilsTestCase(test.TestCase):
self.mox.UnsetStubs()
self.assertEqual(data, fake_contents)
+ def test_generate_password(self):
+ password = utils.generate_password()
+ self.assertTrue([c for c in password if c in '0123456789'])
+ self.assertTrue([c for c in password
+ if c in 'abcdefghijklmnopqrstuvwxyz'])
+ self.assertTrue([c for c in password
+ if c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'])
+
class IsUUIDLikeTestCase(test.TestCase):
def assertUUIDLike(self, val, expected):
diff --git a/nova/utils.py b/nova/utils.py
index 9fb92f08d..c6cb958d8 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -351,13 +351,13 @@ def generate_uid(topic, size=8):
# Default symbols to use for passwords. Avoids visually confusing characters.
# ~6 bits per symbol
-DEFAULT_PASSWORD_SYMBOLS = ('23456789' # Removed: 0,1
- 'ABCDEFGHJKLMNPQRSTUVWXYZ' # Removed: I, O
+DEFAULT_PASSWORD_SYMBOLS = ('23456789', # Removed: 0,1
+ 'ABCDEFGHJKLMNPQRSTUVWXYZ', # Removed: I, O
'abcdefghijkmnopqrstuvwxyz') # Removed: l
# ~5 bits per symbol
-EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1
+EASIER_PASSWORD_SYMBOLS = ('23456789', # Removed: 0, 1
'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O
@@ -421,14 +421,37 @@ def usage_from_instance(instance_ref, **kw):
return usage_info
-def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS):
- """Generate a random password from the supplied symbols.
+def generate_password(length=20, symbolgroups=DEFAULT_PASSWORD_SYMBOLS):
+ """Generate a random password from the supplied symbol groups.
+
+ At least one symbol from each group will be included. Unpredictable
+ results if length is less than the number of symbol groups.
Believed to be reasonably secure (with a reasonable password length!)
"""
r = random.SystemRandom()
- return ''.join([r.choice(symbols) for _i in xrange(length)])
+
+ # NOTE(jerdfelt): Some password policies require at least one character
+ # from each group of symbols, so start off with one random character
+ # from each symbol group
+ password = [r.choice(s) for s in symbolgroups]
+ # If length < len(symbolgroups), the leading characters will only
+ # be from the first length groups. Try our best to not be predictable
+ # by shuffling and then truncating.
+ r.shuffle(password)
+ password = password[:length]
+ length -= len(password)
+
+ # then fill with random characters from all symbol groups
+ symbols = ''.join(symbolgroups)
+ password.extend([r.choice(symbols) for _i in xrange(length)])
+
+ # finally shuffle to ensure first x characters aren't from a
+ # predictable group
+ r.shuffle(password)
+
+ return ''.join(password)
def last_octet(address):