Merge pull request #1721 from sepulworld/adding_secretsmanager_random_password
Added SecretsManager get_random_password mock
This commit is contained in:
commit
a1d095c14b
@ -13,3 +13,17 @@ class ResourceNotFoundException(SecretsManagerClientError):
|
|||||||
"ResourceNotFoundException",
|
"ResourceNotFoundException",
|
||||||
"Secrets Manager can't find the specified secret"
|
"Secrets Manager can't find the specified secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClientError(SecretsManagerClientError):
|
||||||
|
def __init__(self, message):
|
||||||
|
super(ClientError, self).__init__(
|
||||||
|
'InvalidParameterValue',
|
||||||
|
message)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidParameterException(SecretsManagerClientError):
|
||||||
|
def __init__(self, message):
|
||||||
|
super(InvalidParameterException, self).__init__(
|
||||||
|
'InvalidParameterException',
|
||||||
|
message)
|
||||||
|
@ -6,7 +6,12 @@ import json
|
|||||||
import boto3
|
import boto3
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from .exceptions import ResourceNotFoundException
|
from .exceptions import (
|
||||||
|
ResourceNotFoundException,
|
||||||
|
InvalidParameterException,
|
||||||
|
ClientError
|
||||||
|
)
|
||||||
|
from .utils import random_password, secret_arn
|
||||||
|
|
||||||
|
|
||||||
class SecretsManager(BaseModel):
|
class SecretsManager(BaseModel):
|
||||||
@ -40,7 +45,7 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
|
|
||||||
response = json.dumps({
|
response = json.dumps({
|
||||||
"ARN": self.secret_arn(self.region, self.secret_id),
|
"ARN": secret_arn(self.region, self.secret_id),
|
||||||
"Name": self.secret_id,
|
"Name": self.secret_id,
|
||||||
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
|
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
|
||||||
"SecretString": self.secret_string,
|
"SecretString": self.secret_string,
|
||||||
@ -58,16 +63,41 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
self.secret_id = name
|
self.secret_id = name
|
||||||
|
|
||||||
response = json.dumps({
|
response = json.dumps({
|
||||||
"ARN": self.secret_arn(self.region, name),
|
"ARN": secret_arn(self.region, name),
|
||||||
"Name": self.secret_id,
|
"Name": self.secret_id,
|
||||||
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
|
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def secret_arn(self, region, secret_id):
|
def get_random_password(self, password_length,
|
||||||
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
|
exclude_characters, exclude_numbers,
|
||||||
region, secret_id)
|
exclude_punctuation, exclude_uppercase,
|
||||||
|
exclude_lowercase, include_space,
|
||||||
|
require_each_included_type):
|
||||||
|
# password size must have value less than or equal to 4096
|
||||||
|
if password_length > 4096:
|
||||||
|
raise ClientError(
|
||||||
|
"ClientError: An error occurred (ValidationException) \
|
||||||
|
when calling the GetRandomPassword operation: 1 validation error detected: Value '{}' at 'passwordLength' \
|
||||||
|
failed to satisfy constraint: Member must have value less than or equal to 4096".format(password_length))
|
||||||
|
if password_length < 4:
|
||||||
|
raise InvalidParameterException(
|
||||||
|
"InvalidParameterException: An error occurred (InvalidParameterException) \
|
||||||
|
when calling the GetRandomPassword operation: Password length is too short based on the required types.")
|
||||||
|
|
||||||
|
response = json.dumps({
|
||||||
|
"RandomPassword": random_password(password_length,
|
||||||
|
exclude_characters,
|
||||||
|
exclude_numbers,
|
||||||
|
exclude_punctuation,
|
||||||
|
exclude_uppercase,
|
||||||
|
exclude_lowercase,
|
||||||
|
include_space,
|
||||||
|
require_each_included_type)
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
available_regions = (
|
available_regions = (
|
||||||
|
@ -23,3 +23,24 @@ class SecretsManagerResponse(BaseResponse):
|
|||||||
name=name,
|
name=name,
|
||||||
secret_string=secret_string
|
secret_string=secret_string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_random_password(self):
|
||||||
|
password_length = self._get_param('PasswordLength', if_none=32)
|
||||||
|
exclude_characters = self._get_param('ExcludeCharacters', if_none='')
|
||||||
|
exclude_numbers = self._get_param('ExcludeNumbers', if_none=False)
|
||||||
|
exclude_punctuation = self._get_param('ExcludePunctuation', if_none=False)
|
||||||
|
exclude_uppercase = self._get_param('ExcludeUppercase', if_none=False)
|
||||||
|
exclude_lowercase = self._get_param('ExcludeLowercase', if_none=False)
|
||||||
|
include_space = self._get_param('IncludeSpace', if_none=False)
|
||||||
|
require_each_included_type = self._get_param(
|
||||||
|
'RequireEachIncludedType', if_none=True)
|
||||||
|
return secretsmanager_backends[self.region].get_random_password(
|
||||||
|
password_length=password_length,
|
||||||
|
exclude_characters=exclude_characters,
|
||||||
|
exclude_numbers=exclude_numbers,
|
||||||
|
exclude_punctuation=exclude_punctuation,
|
||||||
|
exclude_uppercase=exclude_uppercase,
|
||||||
|
exclude_lowercase=exclude_lowercase,
|
||||||
|
include_space=include_space,
|
||||||
|
require_each_included_type=require_each_included_type
|
||||||
|
)
|
||||||
|
72
moto/secretsmanager/utils.py
Normal file
72
moto/secretsmanager/utils.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import six
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def random_password(password_length, exclude_characters, exclude_numbers,
|
||||||
|
exclude_punctuation, exclude_uppercase, exclude_lowercase,
|
||||||
|
include_space, require_each_included_type):
|
||||||
|
|
||||||
|
password = ''
|
||||||
|
required_characters = ''
|
||||||
|
|
||||||
|
if not exclude_lowercase and not exclude_uppercase:
|
||||||
|
password += string.ascii_letters
|
||||||
|
required_characters += random.choice(_exclude_characters(
|
||||||
|
string.ascii_lowercase, exclude_characters))
|
||||||
|
required_characters += random.choice(_exclude_characters(
|
||||||
|
string.ascii_uppercase, exclude_characters))
|
||||||
|
elif not exclude_lowercase:
|
||||||
|
password += string.ascii_lowercase
|
||||||
|
required_characters += random.choice(_exclude_characters(
|
||||||
|
string.ascii_lowercase, exclude_characters))
|
||||||
|
elif not exclude_uppercase:
|
||||||
|
password += string.ascii_uppercase
|
||||||
|
required_characters += random.choice(_exclude_characters(
|
||||||
|
string.ascii_uppercase, exclude_characters))
|
||||||
|
if not exclude_numbers:
|
||||||
|
password += string.digits
|
||||||
|
required_characters += random.choice(_exclude_characters(
|
||||||
|
string.digits, exclude_characters))
|
||||||
|
if not exclude_punctuation:
|
||||||
|
password += string.punctuation
|
||||||
|
required_characters += random.choice(_exclude_characters(
|
||||||
|
string.punctuation, exclude_characters))
|
||||||
|
if include_space:
|
||||||
|
password += " "
|
||||||
|
required_characters += " "
|
||||||
|
|
||||||
|
password = ''.join(
|
||||||
|
six.text_type(random.choice(password))
|
||||||
|
for x in range(password_length))
|
||||||
|
|
||||||
|
if require_each_included_type:
|
||||||
|
password = _add_password_require_each_included_type(
|
||||||
|
password, required_characters)
|
||||||
|
|
||||||
|
password = _exclude_characters(password, exclude_characters)
|
||||||
|
return password
|
||||||
|
|
||||||
|
|
||||||
|
def secret_arn(region, secret_id):
|
||||||
|
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
|
||||||
|
region, secret_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _exclude_characters(password, exclude_characters):
|
||||||
|
for c in exclude_characters:
|
||||||
|
if c in string.punctuation:
|
||||||
|
# Escape punctuation regex usage
|
||||||
|
c = "\{0}".format(c)
|
||||||
|
password = re.sub(c, '', str(password))
|
||||||
|
return password
|
||||||
|
|
||||||
|
|
||||||
|
def _add_password_require_each_included_type(password, required_characters):
|
||||||
|
password_with_required_char = password[:-len(required_characters)]
|
||||||
|
password_with_required_char += required_characters
|
||||||
|
|
||||||
|
return password_with_required_char
|
@ -5,6 +5,8 @@ import boto3
|
|||||||
from moto import mock_secretsmanager
|
from moto import mock_secretsmanager
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
import string
|
||||||
|
import unittest
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
|
|
||||||
@mock_secretsmanager
|
@mock_secretsmanager
|
||||||
@ -33,3 +35,111 @@ def test_create_secret():
|
|||||||
assert result['Name'] == 'test-secret'
|
assert result['Name'] == 'test-secret'
|
||||||
secret = conn.get_secret_value(SecretId='test-secret')
|
secret = conn.get_secret_value(SecretId='test-secret')
|
||||||
assert secret['SecretString'] == 'foosecret'
|
assert secret['SecretString'] == 'foosecret'
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_password_default_length():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password()
|
||||||
|
assert len(random_password['RandomPassword']) == 32
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_password_default_requirements():
|
||||||
|
# When require_each_included_type, default true
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password()
|
||||||
|
# Should contain lowercase, upppercase, digit, special character
|
||||||
|
assert any(c.islower() for c in random_password['RandomPassword'])
|
||||||
|
assert any(c.isupper() for c in random_password['RandomPassword'])
|
||||||
|
assert any(c.isdigit() for c in random_password['RandomPassword'])
|
||||||
|
assert any(c in string.punctuation
|
||||||
|
for c in random_password['RandomPassword'])
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_password_custom_length():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=50)
|
||||||
|
assert len(random_password['RandomPassword']) == 50
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_exclude_lowercase():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=55,
|
||||||
|
ExcludeLowercase=True)
|
||||||
|
assert any(c.islower() for c in random_password['RandomPassword']) == False
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_exclude_uppercase():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=55,
|
||||||
|
ExcludeUppercase=True)
|
||||||
|
assert any(c.isupper() for c in random_password['RandomPassword']) == False
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_exclude_characters_and_symbols():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=20,
|
||||||
|
ExcludeCharacters='xyzDje@?!.')
|
||||||
|
assert any(c in 'xyzDje@?!.' for c in random_password['RandomPassword']) == False
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_exclude_numbers():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=100,
|
||||||
|
ExcludeNumbers=True)
|
||||||
|
assert any(c.isdigit() for c in random_password['RandomPassword']) == False
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_exclude_punctuation():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=100,
|
||||||
|
ExcludePunctuation=True)
|
||||||
|
assert any(c in string.punctuation
|
||||||
|
for c in random_password['RandomPassword']) == False
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_include_space_false():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=300)
|
||||||
|
assert any(c.isspace() for c in random_password['RandomPassword']) == False
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_include_space_true():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=4,
|
||||||
|
IncludeSpace=True)
|
||||||
|
assert any(c.isspace() for c in random_password['RandomPassword']) == True
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_require_each_included_type():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
random_password = conn.get_random_password(PasswordLength=4,
|
||||||
|
RequireEachIncludedType=True)
|
||||||
|
assert any(c in string.punctuation for c in random_password['RandomPassword']) == True
|
||||||
|
assert any(c in string.ascii_lowercase for c in random_password['RandomPassword']) == True
|
||||||
|
assert any(c in string.ascii_uppercase for c in random_password['RandomPassword']) == True
|
||||||
|
assert any(c in string.digits for c in random_password['RandomPassword']) == True
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_too_short_password():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
with assert_raises(ClientError):
|
||||||
|
random_password = conn.get_random_password(PasswordLength=3)
|
||||||
|
|
||||||
|
@mock_secretsmanager
|
||||||
|
def test_get_random_too_long_password():
|
||||||
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
|
|
||||||
|
with assert_raises(Exception):
|
||||||
|
random_password = conn.get_random_password(PasswordLength=5555)
|
||||||
|
Loading…
Reference in New Issue
Block a user