Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to store multiple scrects, use uuid, tags support #1898

Merged
merged 2 commits into from
Dec 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ python_env
.ropeproject/
.pytest_cache/
venv/

.vscode/
89 changes: 46 additions & 43 deletions moto/secretsmanager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import time
import json
import uuid

import boto3

Expand All @@ -18,62 +19,65 @@ class SecretsManager(BaseModel):

def __init__(self, region_name, **kwargs):
self.region = region_name
self.secret_id = kwargs.get('secret_id', '')
self.version_id = kwargs.get('version_id', '')
self.version_stage = kwargs.get('version_stage', '')
self.secret_string = ''


class SecretsManagerBackend(BaseBackend):

def __init__(self, region_name=None, **kwargs):
super(SecretsManagerBackend, self).__init__()
self.region = region_name
self.secret_id = kwargs.get('secret_id', '')
self.name = kwargs.get('name', '')
self.createdate = int(time.time())
self.secret_string = ''
self.rotation_enabled = False
self.rotation_lambda_arn = ''
self.auto_rotate_after_days = 0
self.version_id = ''
self.secrets = {}

def reset(self):
region_name = self.region
self.__dict__ = {}
self.__init__(region_name)

def _is_valid_identifier(self, identifier):
return identifier in (self.name, self.secret_id)
return identifier in self.secrets

def get_secret_value(self, secret_id, version_id, version_stage):

if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException()

secret = self.secrets[secret_id]

response = json.dumps({
"ARN": secret_arn(self.region, self.secret_id),
"Name": self.name,
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
"SecretString": self.secret_string,
"ARN": secret_arn(self.region, secret['secret_id']),
"Name": secret['name'],
"VersionId": secret['version_id'],
"SecretString": secret['secret_string'],
"VersionStages": [
"AWSCURRENT",
],
"CreatedDate": "2018-05-23 13:16:57.198000"
"CreatedDate": secret['createdate']
})

return response

def create_secret(self, name, secret_string, **kwargs):
def create_secret(self, name, secret_string, tags, **kwargs):

generated_version_id = str(uuid.uuid4())

secret = {
'secret_string': secret_string,
'secret_id': name,
'name': name,
'createdate': int(time.time()),
'rotation_enabled': False,
'rotation_lambda_arn': '',
'auto_rotate_after_days': 0,
'version_id': generated_version_id,
'tags': tags
}

self.secret_string = secret_string
self.secret_id = name
self.name = name
self.secrets[name] = secret

response = json.dumps({
"ARN": secret_arn(self.region, name),
"Name": self.name,
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
"Name": name,
"VersionId": generated_version_id,
})

return response
Expand All @@ -82,26 +86,23 @@ def describe_secret(self, secret_id):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException

secret = self.secrets[secret_id]

response = json.dumps({
"ARN": secret_arn(self.region, self.secret_id),
"Name": self.name,
"ARN": secret_arn(self.region, secret['secret_id']),
"Name": secret['name'],
"Description": "",
"KmsKeyId": "",
"RotationEnabled": self.rotation_enabled,
"RotationLambdaARN": self.rotation_lambda_arn,
"RotationEnabled": secret['rotation_enabled'],
"RotationLambdaARN": secret['rotation_lambda_arn'],
"RotationRules": {
"AutomaticallyAfterDays": self.auto_rotate_after_days
"AutomaticallyAfterDays": secret['auto_rotate_after_days']
},
"LastRotatedDate": None,
"LastChangedDate": None,
"LastAccessedDate": None,
"DeletedDate": None,
"Tags": [
{
"Key": "",
"Value": ""
},
]
"Tags": secret['tags']
})

return response
Expand Down Expand Up @@ -141,17 +142,19 @@ def rotate_secret(self, secret_id, client_request_token=None,
)
raise InvalidParameterException(msg)

self.version_id = client_request_token or ''
self.rotation_lambda_arn = rotation_lambda_arn or ''
secret = self.secrets[secret_id]

secret['version_id'] = client_request_token or ''
secret['rotation_lambda_arn'] = rotation_lambda_arn or ''
if rotation_rules:
self.auto_rotate_after_days = rotation_rules.get(rotation_days, 0)
if self.auto_rotate_after_days > 0:
self.rotation_enabled = True
secret['auto_rotate_after_days'] = rotation_rules.get(rotation_days, 0)
if secret['auto_rotate_after_days'] > 0:
secret['rotation_enabled'] = True

response = json.dumps({
"ARN": secret_arn(self.region, self.secret_id),
"Name": self.name,
"VersionId": self.version_id
"ARN": secret_arn(self.region, secret['secret_id']),
"Name": secret['name'],
"VersionId": secret['version_id']
})

return response
Expand Down
4 changes: 3 additions & 1 deletion moto/secretsmanager/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ def get_secret_value(self):
def create_secret(self):
name = self._get_param('Name')
secret_string = self._get_param('SecretString')
tags = self._get_param('Tags', if_none=[])
return secretsmanager_backends[self.region].create_secret(
name=name,
secret_string=secret_string
secret_string=secret_string,
tags=tags
)

def get_random_password(self):
Expand Down
5 changes: 3 additions & 2 deletions moto/secretsmanager/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ def random_password(password_length, exclude_characters, exclude_numbers,


def secret_arn(region, secret_id):
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
region, secret_id)
id_string = ''.join(random.choice(string.ascii_letters) for _ in range(5))
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-{2}".format(
region, secret_id, id_string)


def _exclude_characters(password, exclude_characters):
Expand Down
35 changes: 28 additions & 7 deletions tests/test_secretsmanager/test_secretsmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,28 @@ def test_create_secret():
conn = boto3.client('secretsmanager', region_name='us-east-1')

result = conn.create_secret(Name='test-secret', SecretString="foosecret")
assert result['ARN'] == (
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
assert result['ARN']
assert result['Name'] == 'test-secret'
secret = conn.get_secret_value(SecretId='test-secret')
assert secret['SecretString'] == 'foosecret'

@mock_secretsmanager
def test_create_secret_with_tags():
conn = boto3.client('secretsmanager', region_name='us-east-1')
secret_name = 'test-secret-with-tags'

result = conn.create_secret(
Name=secret_name,
SecretString="foosecret",
Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}]
)
assert result['ARN']
assert result['Name'] == secret_name
secret_value = conn.get_secret_value(SecretId=secret_name)
assert secret_value['SecretString'] == 'foosecret'
secret_details = conn.describe_secret(SecretId=secret_name)
assert secret_details['Tags'] == [{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}]

@mock_secretsmanager
def test_get_random_password_default_length():
conn = boto3.client('secretsmanager', region_name='us-west-2')
Expand Down Expand Up @@ -159,10 +175,17 @@ def test_describe_secret():
conn.create_secret(Name='test-secret',
SecretString='foosecret')

conn.create_secret(Name='test-secret-2',
SecretString='barsecret')

secret_description = conn.describe_secret(SecretId='test-secret')
secret_description_2 = conn.describe_secret(SecretId='test-secret-2')

assert secret_description # Returned dict is not empty
assert secret_description['ARN'] == (
'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad')
assert secret_description['Name'] == ('test-secret')
assert secret_description['ARN'] != '' # Test arn not empty
assert secret_description_2['Name'] == ('test-secret-2')
assert secret_description_2['ARN'] != '' # Test arn not empty

@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
Expand Down Expand Up @@ -190,9 +213,7 @@ def test_rotate_secret():
rotated_secret = conn.rotate_secret(SecretId=secret_name)

assert rotated_secret
assert rotated_secret['ARN'] == (
'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad'
)
assert rotated_secret['ARN'] != '' # Test arn not empty
assert rotated_secret['Name'] == secret_name
assert rotated_secret['VersionId'] != ''

Expand Down
41 changes: 33 additions & 8 deletions tests/test_secretsmanager/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,20 @@ def test_create_secret():
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"},
)
res_2 = test_client.post('/',
data={"Name": "test-secret-2",
"SecretString": "bar-secret"},
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"},
)

json_data = json.loads(res.data.decode("utf-8"))
assert json_data['ARN'] == (
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
assert json_data['ARN'] != ''
assert json_data['Name'] == 'test-secret'

json_data_2 = json.loads(res_2.data.decode("utf-8"))
assert json_data_2['ARN'] != ''
assert json_data_2['Name'] == 'test-secret-2'

@mock_secretsmanager
def test_describe_secret():
Expand All @@ -107,12 +116,30 @@ def test_describe_secret():
"X-Amz-Target": "secretsmanager.DescribeSecret"
},
)

create_secret_2 = test_client.post('/',
data={"Name": "test-secret-2",
"SecretString": "barsecret"},
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"
},
)
describe_secret_2 = test_client.post('/',
data={"SecretId": "test-secret-2"},
headers={
"X-Amz-Target": "secretsmanager.DescribeSecret"
},
)

json_data = json.loads(describe_secret.data.decode("utf-8"))
assert json_data # Returned dict is not empty
assert json_data['ARN'] == (
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
)
assert json_data['ARN'] != ''
assert json_data['Name'] == 'test-secret'

json_data_2 = json.loads(describe_secret_2.data.decode("utf-8"))
assert json_data_2 # Returned dict is not empty
assert json_data_2['ARN'] != ''
assert json_data_2['Name'] == 'test-secret-2'

@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
Expand Down Expand Up @@ -179,9 +206,7 @@ def test_rotate_secret():

json_data = json.loads(rotate_secret.data.decode("utf-8"))
assert json_data # Returned dict is not empty
assert json_data['ARN'] == (
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
)
assert json_data['ARN'] != ''
assert json_data['Name'] == 'test-secret'
assert json_data['VersionId'] == client_request_token

Expand Down