From e8d60435fe5157953b8a18e5edc9b5867c4b60dd Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Fri, 23 Aug 2019 10:57:15 +0100 Subject: [PATCH] #2366 - SecretsManager - put_secret_value should support binary values --- moto/secretsmanager/models.py | 4 +- moto/secretsmanager/responses.py | 7 ++- .../test_secretsmanager.py | 59 ++++++++++++++++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 3e0424b6b..63d847c49 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -154,9 +154,9 @@ class SecretsManagerBackend(BaseBackend): return version_id - def put_secret_value(self, secret_id, secret_string, version_stages): + def put_secret_value(self, secret_id, secret_string, secret_binary, version_stages): - version_id = self._add_secret(secret_id, secret_string, version_stages=version_stages) + version_id = self._add_secret(secret_id, secret_string, secret_binary, version_stages=version_stages) response = json.dumps({ 'ARN': secret_arn(self.region, secret_id), diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py index 090688351..4995c4bc7 100644 --- a/moto/secretsmanager/responses.py +++ b/moto/secretsmanager/responses.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse +from moto.secretsmanager.exceptions import InvalidRequestException from .models import secretsmanager_backends @@ -71,10 +72,14 @@ class SecretsManagerResponse(BaseResponse): def put_secret_value(self): secret_id = self._get_param('SecretId', if_none='') - secret_string = self._get_param('SecretString', if_none='') + secret_string = self._get_param('SecretString') + secret_binary = self._get_param('SecretBinary') + if not secret_binary and not secret_string: + raise InvalidRequestException('You must provide either SecretString or SecretBinary.') version_stages = self._get_param('VersionStages', if_none=['AWSCURRENT']) return secretsmanager_backends[self.region].put_secret_value( secret_id=secret_id, + secret_binary=secret_binary, secret_string=secret_string, version_stages=version_stages, ) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 78b95ee6a..62de93bab 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -5,9 +5,9 @@ import boto3 from moto import mock_secretsmanager from botocore.exceptions import ClientError import string -import unittest import pytz from datetime import datetime +import sure # noqa from nose.tools import assert_raises from six import b @@ -23,6 +23,7 @@ def test_get_secret_value(): result = conn.get_secret_value(SecretId='java-util-test-password') assert result['SecretString'] == 'foosecret' + @mock_secretsmanager def test_get_secret_value_binary(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -32,6 +33,7 @@ def test_get_secret_value_binary(): result = conn.get_secret_value(SecretId='java-util-test-password') assert result['SecretBinary'] == b('foosecret') + @mock_secretsmanager def test_get_secret_that_does_not_exist(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -39,6 +41,7 @@ def test_get_secret_that_does_not_exist(): with assert_raises(ClientError): result = conn.get_secret_value(SecretId='i-dont-exist') + @mock_secretsmanager def test_get_secret_that_does_not_match(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -72,6 +75,7 @@ def test_create_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') @@ -216,6 +220,7 @@ def test_get_random_exclude_lowercase(): 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') @@ -224,6 +229,7 @@ def test_get_random_exclude_uppercase(): 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') @@ -232,6 +238,7 @@ def test_get_random_exclude_characters_and_symbols(): 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') @@ -240,6 +247,7 @@ def test_get_random_exclude_numbers(): 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') @@ -249,6 +257,7 @@ def test_get_random_exclude_punctuation(): 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') @@ -256,6 +265,7 @@ def test_get_random_include_space_false(): 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') @@ -264,6 +274,7 @@ def test_get_random_include_space_true(): 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') @@ -275,6 +286,7 @@ def test_get_random_require_each_included_type(): 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') @@ -282,6 +294,7 @@ def test_get_random_too_short_password(): 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') @@ -289,6 +302,7 @@ def test_get_random_too_long_password(): with assert_raises(Exception): random_password = conn.get_random_password(PasswordLength=5555) + @mock_secretsmanager def test_describe_secret(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -307,6 +321,7 @@ def test_describe_secret(): 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(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -314,6 +329,7 @@ def test_describe_secret_that_does_not_exist(): with assert_raises(ClientError): result = conn.get_secret_value(SecretId='i-dont-exist') + @mock_secretsmanager def test_describe_secret_that_does_not_match(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -500,6 +516,7 @@ def test_rotate_secret_rotation_period_zero(): # test_server actually handles this error. assert True + @mock_secretsmanager def test_rotate_secret_rotation_period_too_long(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -511,6 +528,7 @@ def test_rotate_secret_rotation_period_too_long(): result = conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME, RotationRules=rotation_rules) + @mock_secretsmanager def test_put_secret_value_puts_new_secret(): conn = boto3.client('secretsmanager', region_name='us-west-2') @@ -526,6 +544,45 @@ def test_put_secret_value_puts_new_secret(): assert get_secret_value_dict assert get_secret_value_dict['SecretString'] == 'foosecret' + +@mock_secretsmanager +def test_put_secret_binary_value_puts_new_secret(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + put_secret_value_dict = conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME, + SecretBinary=b('foosecret'), + VersionStages=['AWSCURRENT']) + version_id = put_secret_value_dict['VersionId'] + + get_secret_value_dict = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME, + VersionId=version_id, + VersionStage='AWSCURRENT') + + assert get_secret_value_dict + assert get_secret_value_dict['SecretBinary'] == b('foosecret') + + +@mock_secretsmanager +def test_create_and_put_secret_binary_value_puts_new_secret(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b("foosecret")) + conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME, SecretBinary=b('foosecret_update')) + + latest_secret = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME) + + assert latest_secret + assert latest_secret['SecretBinary'] == b('foosecret_update') + + +@mock_secretsmanager +def test_put_secret_binary_requires_either_string_or_binary(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + with assert_raises(ClientError) as ire: + conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME) + + ire.exception.response['Error']['Code'].should.equal('InvalidRequestException') + ire.exception.response['Error']['Message'].should.equal('You must provide either SecretString or SecretBinary.') + + @mock_secretsmanager def test_put_secret_value_can_get_first_version_if_put_twice(): conn = boto3.client('secretsmanager', region_name='us-west-2')