diff --git a/bootstrap_cfn/config.py b/bootstrap_cfn/config.py index 42273fc..1bf1816 100644 --- a/bootstrap_cfn/config.py +++ b/bootstrap_cfn/config.py @@ -481,12 +481,12 @@ def rds(self, template): """ # REQUIRED FIELDS MAPPING required_fields = { - 'db-name': 'DBName', - 'db-master-username': 'MasterUsername', - 'db-master-password': 'MasterUserPassword', } optional_fields = { + 'db-name': 'DBName', + 'db-master-username': 'MasterUsername', + 'db-master-password': 'MasterUserPassword', 'storage': 'AllocatedStorage', 'storage-type': 'StorageType', 'backup-retention-period': 'BackupRetentionPeriod', @@ -498,6 +498,28 @@ def rds(self, template): 'identifier': 'DBInstanceIdentifier' } + # Do random generation of RDS vars, + # See http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html + # for limitation details + if 'identifier' not in self.data['rds']: + logging.info("config::rds: No RDS identifier found, generating random value...") + identifier = ('%s-%s' + % (self.application, + utils.get_random_string(length=63, alphanumeric=True))) + # Truncate to meet requirements + self.data['rds']['identifier'] = identifier[:63] + if 'db-name' not in self.data['rds']: + logging.info("config::rds: No RDS database name found, generating random value...") + self.data['rds']['db-name'] = ('%s_%s' + % (self.application, + utils.get_random_string(length=12, alphanumeric=True))) + if 'db-master-username' not in self.data['rds']: + logging.info("config::rds: No RDS database username found, generating random value...") + self.data['rds']['db-master-username'] = utils.get_random_string(length=63, alphanumeric=True) + if 'db-master-password' not in self.data['rds']: + logging.info("config::rds: No RDS database password found, generating random value...") + self.data['rds']['db-master-password'] = utils.get_random_string(length=128, alphanumeric=True) + # LOAD STACK TEMPLATE resources = [] rds_subnet_group = DBSubnetGroup( diff --git a/bootstrap_cfn/utils.py b/bootstrap_cfn/utils.py index 52a5545..743adfb 100644 --- a/bootstrap_cfn/utils.py +++ b/bootstrap_cfn/utils.py @@ -1,4 +1,6 @@ import os +import random +import string import sys import time @@ -151,3 +153,25 @@ def sleep_countdown(sleep_time): sys.stdout.flush() time.sleep(1) sleep_time -= 1 + + +def get_random_string(length=64, alphanumeric=False): + """ + Return a basic pseudo-random string + + Args: + length(int): The length of the string to return + alphanumeric(bool): True to use only alphanumeric chars, + False to use all chars + + Returns: + random_string(string): The random string of required length + """ + # Just alphanumeric characters + if alphanumeric: + chars = string.letters + string.digits + else: + # Alphanumeric + special characters + chars = string.letters + string.digits + string.punctuation + random_string = ''.join([random.choice(chars) for x in range(length)]) + return random_string diff --git a/setup.py b/setup.py index 335f3b6..8f16f29 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ 'mock>=1.0.1', 'testfixtures>=4.1.2', 'nose', + 'entropy', ], classifiers=[ 'Development Status :: 3 - Alpha', diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..b559acb --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,33 @@ +import random + +import unittest + +import entropy + +from bootstrap_cfn import utils + + +class TestUtils(unittest.TestCase): + + def test_get_random_string_alphanumeric(self): + """ + TestUtils::test_get_random_string: Test getting a random string + """ + no_of_tests = 1000 + min_key_length = 24 + max_key_length = 128 + # Generate some keys and test how random they are + for i in range(no_of_tests): + length = random.randint(min_key_length, max_key_length) + alphanumeric = random.choice([True, False]) + if alphanumeric: + entropy_floor = 0.4 + else: + entropy_floor = 0.5 + random_string = utils.get_random_string(length, alphanumeric=alphanumeric) + string_entropy = entropy.shannon_entropy(random_string) + message = ("TestUtils::test_get_random_string: Test: %s: Generated string length:%s alphanumeric:%s entropy:%s string:%s" + % (i, length, alphanumeric, string_entropy, random_string)) + print(message) + self.assertEqual(len(random_string), length) + self.assertGreater(string_entropy, entropy_floor, message)