moto/tests/test_secretsmanager/test_secretsmanager.py

1206 lines
41 KiB
Python
Raw Normal View History

2018-06-08 05:31:44 +00:00
import boto3
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
from moto import mock_secretsmanager, mock_lambda, settings
2021-06-23 17:03:11 +00:00
from moto.core import ACCOUNT_ID
from botocore.exceptions import ClientError, ParamValidationError
import string
2019-04-18 16:27:13 +00:00
import pytz
from datetime import datetime
2021-10-18 19:44:29 +00:00
import sure # noqa # pylint: disable=unused-import
from uuid import uuid4
import pytest
2018-06-08 05:31:44 +00:00
2019-10-31 15:44:26 +00:00
DEFAULT_SECRET_NAME = "test-secret"
2018-06-08 05:31:44 +00:00
@mock_secretsmanager
def test_get_secret_value():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2018-06-08 05:31:44 +00:00
2021-10-18 19:44:29 +00:00
conn.create_secret(Name="java-util-test-password", SecretString="foosecret")
2019-10-31 15:44:26 +00:00
result = conn.get_secret_value(SecretId="java-util-test-password")
assert result["SecretString"] == "foosecret"
2019-11-23 06:29:30 +00:00
@mock_secretsmanager
def test_get_secret_value_by_arn():
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-06-23 17:03:11 +00:00
name = "java-util-test-password"
2019-11-23 06:29:30 +00:00
secret_value = "test_get_secret_value_by_arn"
2021-06-23 17:03:11 +00:00
result = conn.create_secret(Name=name, SecretString=secret_value)
arn = result["ARN"]
arn.should.match(
"^arn:aws:secretsmanager:us-west-2:{}:secret:{}".format(ACCOUNT_ID, name)
2019-11-23 06:29:30 +00:00
)
2021-06-23 17:03:11 +00:00
result = conn.get_secret_value(SecretId=arn)
2019-11-23 06:29:30 +00:00
assert result["SecretString"] == secret_value
@mock_secretsmanager
def test_get_secret_value_binary():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-10-18 19:44:29 +00:00
conn.create_secret(Name="java-util-test-password", SecretBinary=b"foosecret")
2019-10-31 15:44:26 +00:00
result = conn.get_secret_value(SecretId="java-util-test-password")
2021-07-26 06:40:39 +00:00
assert result["SecretBinary"] == b"foosecret"
@mock_secretsmanager
def test_get_secret_that_does_not_exist():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError) as cm:
2021-10-18 19:44:29 +00:00
conn.get_secret_value(SecretId="i-dont-exist")
2020-10-06 06:46:05 +00:00
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_get_secret_that_does_not_match():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-10-18 19:44:29 +00:00
conn.create_secret(Name="java-util-test-password", SecretString="foosecret")
with pytest.raises(ClientError) as cm:
2021-10-18 19:44:29 +00:00
conn.get_secret_value(SecretId="i-dont-match")
2020-10-06 06:46:05 +00:00
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
2019-04-18 11:58:50 +00:00
2019-04-18 11:58:50 +00:00
@mock_secretsmanager
def test_get_secret_value_that_is_marked_deleted():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
conn.delete_secret(SecretId="test-secret")
2019-04-18 11:58:50 +00:00
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.get_secret_value(SecretId="test-secret")
2019-04-18 11:58:50 +00:00
@mock_secretsmanager
def test_get_secret_that_has_no_value():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-10-18 19:44:29 +00:00
conn.create_secret(Name="java-util-test-password")
with pytest.raises(ClientError) as cm:
2021-10-18 19:44:29 +00:00
conn.get_secret_value(SecretId="java-util-test-password")
2020-10-06 06:46:05 +00:00
assert (
"Secrets Manager can't find the specified secret value for staging label: AWSCURRENT"
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_get_secret_version_that_does_not_exist():
conn = boto3.client("secretsmanager", region_name="us-west-2")
result = conn.create_secret(Name="java-util-test-password")
secret_arn = result["ARN"]
missing_version_id = "00000000-0000-0000-0000-000000000000"
2020-10-06 06:04:09 +00:00
with pytest.raises(ClientError) as cm:
conn.get_secret_value(SecretId=secret_arn, VersionId=missing_version_id)
2020-10-06 06:46:05 +00:00
assert (
"An error occurred (ResourceNotFoundException) when calling the GetSecretValue operation: Secrets "
"Manager can't find the specified secret value for VersionId: 00000000-0000-0000-0000-000000000000"
) == cm.value.response["Error"]["Message"]
@mock_secretsmanager
def test_create_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-east-1")
2019-10-31 15:44:26 +00:00
result = conn.create_secret(Name="test-secret", SecretString="foosecret")
assert result["ARN"]
assert result["Name"] == "test-secret"
secret = conn.get_secret_value(SecretId="test-secret")
assert secret["SecretString"] == "foosecret"
2018-12-21 22:04:52 +00:00
@mock_secretsmanager
def test_create_secret_with_tags():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-east-1")
secret_name = "test-secret-with-tags"
2018-12-21 22:04:52 +00:00
result = conn.create_secret(
Name=secret_name,
SecretString="foosecret",
2019-10-31 15:44:26 +00:00
Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
2018-12-21 22:04:52 +00:00
)
2019-10-31 15:44:26 +00:00
assert result["ARN"]
assert result["Name"] == secret_name
2018-12-21 22:04:52 +00:00
secret_value = conn.get_secret_value(SecretId=secret_name)
2019-10-31 15:44:26 +00:00
assert secret_value["SecretString"] == "foosecret"
2018-12-21 22:04:52 +00:00
secret_details = conn.describe_secret(SecretId=secret_name)
2019-10-31 15:44:26 +00:00
assert secret_details["Tags"] == [
{"Key": "Foo", "Value": "Bar"},
{"Key": "Mykey", "Value": "Myvalue"},
]
2018-12-21 22:04:52 +00:00
@mock_secretsmanager
def test_create_secret_with_description():
conn = boto3.client("secretsmanager", region_name="us-east-1")
secret_name = "test-secret-with-tags"
result = conn.create_secret(
Name=secret_name, SecretString="foosecret", Description="desc"
)
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["Description"] == "desc"
@mock_secretsmanager
def test_create_secret_with_tags_and_description():
conn = boto3.client("secretsmanager", region_name="us-east-1")
secret_name = "test-secret-with-tags"
result = conn.create_secret(
Name=secret_name,
SecretString="foosecret",
Description="desc",
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"},
]
assert secret_details["Description"] == "desc"
@mock_secretsmanager
def test_delete_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-10-31 15:44:26 +00:00
deleted_secret = conn.delete_secret(SecretId="test-secret")
2019-10-31 15:44:26 +00:00
assert deleted_secret["ARN"]
assert deleted_secret["Name"] == "test-secret"
assert deleted_secret["DeletionDate"] > datetime.fromtimestamp(1, pytz.utc)
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
secret_details = conn.describe_secret(SecretId="test-secret")
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
assert secret_details["ARN"]
assert secret_details["Name"] == "test-secret"
assert secret_details["DeletedDate"] > datetime.fromtimestamp(1, pytz.utc)
2019-04-18 11:58:50 +00:00
@mock_secretsmanager
def test_delete_secret_force():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-10-31 15:44:26 +00:00
result = conn.delete_secret(SecretId="test-secret", ForceDeleteWithoutRecovery=True)
2019-10-31 15:44:26 +00:00
assert result["ARN"]
assert result["DeletionDate"] > datetime.fromtimestamp(1, pytz.utc)
assert result["Name"] == "test-secret"
with pytest.raises(ClientError):
2019-10-31 15:44:26 +00:00
result = conn.get_secret_value(SecretId="test-secret")
@mock_secretsmanager
def test_delete_secret_force_with_arn():
conn = boto3.client("secretsmanager", region_name="us-west-2")
create_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
result = conn.delete_secret(
SecretId=create_secret["ARN"], ForceDeleteWithoutRecovery=True
)
assert result["ARN"]
assert result["DeletionDate"] > datetime.fromtimestamp(1, pytz.utc)
assert result["Name"] == "test-secret"
with pytest.raises(ClientError):
result = conn.get_secret_value(SecretId="test-secret")
@mock_secretsmanager
def test_delete_secret_that_does_not_exist():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.delete_secret(SecretId="i-dont-exist", ForceDeleteWithoutRecovery=True)
@mock_secretsmanager
2019-04-18 11:58:50 +00:00
def test_delete_secret_fails_with_both_force_delete_flag_and_recovery_window_flag():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.delete_secret(
2019-10-31 15:44:26 +00:00
SecretId="test-secret",
RecoveryWindowInDays=1,
ForceDeleteWithoutRecovery=True,
)
@mock_secretsmanager
2019-04-18 11:58:50 +00:00
def test_delete_secret_recovery_window_too_short():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.delete_secret(SecretId="test-secret", RecoveryWindowInDays=6)
2019-04-18 11:58:50 +00:00
@mock_secretsmanager
def test_delete_secret_recovery_window_too_long():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-04-18 11:58:50 +00:00
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.delete_secret(SecretId="test-secret", RecoveryWindowInDays=31)
2019-04-18 11:58:50 +00:00
@mock_secretsmanager
def test_delete_secret_that_is_marked_deleted():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-04-18 11:58:50 +00:00
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-04-18 11:58:50 +00:00
2021-10-18 19:44:29 +00:00
conn.delete_secret(SecretId="test-secret")
2019-04-18 11:58:50 +00:00
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.delete_secret(SecretId="test-secret")
@mock_secretsmanager
def test_get_random_password_default_length():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password()
2019-10-31 15:44:26 +00:00
assert len(random_password["RandomPassword"]) == 32
@mock_secretsmanager
def test_get_random_password_default_requirements():
# When require_each_included_type, default true
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password()
# Should contain lowercase, upppercase, digit, special character
2019-10-31 15:44:26 +00:00
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():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(PasswordLength=50)
2019-10-31 15:44:26 +00:00
assert len(random_password["RandomPassword"]) == 50
@mock_secretsmanager
def test_get_random_exclude_lowercase():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(PasswordLength=55, ExcludeLowercase=True)
assert not any(c.islower() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_exclude_uppercase():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(PasswordLength=55, ExcludeUppercase=True)
assert not any(c.isupper() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_exclude_characters_and_symbols():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(
PasswordLength=20, ExcludeCharacters="xyzDje@?!."
)
assert not any(c in "xyzDje@?!." for c in random_password["RandomPassword"])
assert len(random_password["RandomPassword"]) == 20
@mock_secretsmanager
def test_get_random_exclude_numbers():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(PasswordLength=100, ExcludeNumbers=True)
assert not any(c.isdigit() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_exclude_punctuation():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(
PasswordLength=100, ExcludePunctuation=True
)
assert not any(c in string.punctuation for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_include_space_false():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(PasswordLength=300)
assert not any(c.isspace() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_include_space_true():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(PasswordLength=4, IncludeSpace=True)
assert any(c.isspace() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_require_each_included_type():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
random_password = conn.get_random_password(
PasswordLength=4, RequireEachIncludedType=True
)
assert any(c in string.punctuation for c in random_password["RandomPassword"])
assert any(c in string.ascii_lowercase for c in random_password["RandomPassword"])
assert any(c in string.ascii_uppercase for c in random_password["RandomPassword"])
assert any(c in string.digits for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_too_short_password():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.get_random_password(PasswordLength=3)
@mock_secretsmanager
def test_get_random_too_long_password():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(Exception):
2021-10-18 19:44:29 +00:00
conn.get_random_password(PasswordLength=5555)
@mock_secretsmanager
def test_describe_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
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")
2019-10-31 15:44:26 +00:00
assert secret_description # Returned dict is not empty
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_with_arn():
conn = boto3.client("secretsmanager", region_name="us-west-2")
results = conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-11-23 10:02:00 +00:00
secret_description = conn.describe_secret(SecretId=results["ARN"])
assert secret_description # Returned dict is not empty
2020-09-28 13:49:14 +00:00
secret_description["Name"].should.equal("test-secret")
secret_description["ARN"].should.equal(results["ARN"])
conn.list_secrets()["SecretList"][0]["ARN"].should.equal(results["ARN"])
@mock_secretsmanager
def test_describe_secret_with_KmsKeyId():
conn = boto3.client("secretsmanager", region_name="us-west-2")
results = conn.create_secret(
Name="test-secret", SecretString="foosecret", KmsKeyId="dummy_arn"
)
secret_description = conn.describe_secret(SecretId=results["ARN"])
secret_description["KmsKeyId"].should.equal("dummy_arn")
conn.list_secrets()["SecretList"][0]["KmsKeyId"].should.equal(
secret_description["KmsKeyId"]
)
@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.get_secret_value(SecretId="i-dont-exist")
@mock_secretsmanager
def test_describe_secret_that_does_not_match():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.get_secret_value(SecretId="i-dont-match")
2019-04-05 12:33:28 +00:00
@mock_secretsmanager
def test_restore_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-10-31 15:44:26 +00:00
conn.delete_secret(SecretId="test-secret")
2019-10-31 15:44:26 +00:00
described_secret_before = conn.describe_secret(SecretId="test-secret")
assert described_secret_before["DeletedDate"] > datetime.fromtimestamp(1, pytz.utc)
2019-10-31 15:44:26 +00:00
restored_secret = conn.restore_secret(SecretId="test-secret")
assert restored_secret["ARN"]
assert restored_secret["Name"] == "test-secret"
2019-10-31 15:44:26 +00:00
described_secret_after = conn.describe_secret(SecretId="test-secret")
assert "DeletedDate" not in described_secret_after
@mock_secretsmanager
def test_restore_secret_that_is_not_deleted():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-10-31 15:44:26 +00:00
restored_secret = conn.restore_secret(SecretId="test-secret")
assert restored_secret["ARN"]
assert restored_secret["Name"] == "test-secret"
@mock_secretsmanager
def test_restore_secret_that_does_not_exist():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.restore_secret(SecretId="i-dont-exist")
@mock_secretsmanager
def test_rotate_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(
Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
)
rotated_secret = conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME)
assert rotated_secret
2019-10-31 15:44:26 +00:00
assert rotated_secret["ARN"] != "" # Test arn not empty
assert rotated_secret["Name"] == DEFAULT_SECRET_NAME
assert rotated_secret["VersionId"] != ""
describe_secret = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert describe_secret["Description"] == "foodescription"
@mock_secretsmanager
def test_rotate_secret_enable_rotation():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
initial_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert initial_description
2019-10-31 15:44:26 +00:00
assert initial_description["RotationEnabled"] is False
assert initial_description["RotationRules"]["AutomaticallyAfterDays"] == 0
2019-10-31 15:44:26 +00:00
conn.rotate_secret(
SecretId=DEFAULT_SECRET_NAME, RotationRules={"AutomaticallyAfterDays": 42}
)
rotated_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert rotated_description
2019-10-31 15:44:26 +00:00
assert rotated_description["RotationEnabled"] is True
assert rotated_description["RotationRules"]["AutomaticallyAfterDays"] == 42
@mock_secretsmanager
def test_rotate_secret_that_is_marked_deleted():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2019-10-31 15:44:26 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
2019-10-31 15:44:26 +00:00
conn.delete_secret(SecretId="test-secret")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.rotate_secret(SecretId="test-secret")
@mock_secretsmanager
def test_rotate_secret_that_does_not_exist():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", "us-west-2")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.rotate_secret(SecretId="i-dont-exist")
2019-10-31 15:44:26 +00:00
@mock_secretsmanager
def test_rotate_secret_that_does_not_match():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.rotate_secret(SecretId="i-dont-match")
2019-10-31 15:44:26 +00:00
@mock_secretsmanager
def test_rotate_secret_client_request_token_too_short():
# Test is intentionally empty. Boto3 catches too short ClientRequestToken
# and raises ParamValidationError before Moto can see it.
# test_server actually handles this error.
assert True
2019-10-31 15:44:26 +00:00
@mock_secretsmanager
def test_rotate_secret_client_request_token_too_long():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
client_request_token = (
2019-10-31 15:44:26 +00:00
"ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C-" "ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C"
)
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.rotate_secret(
2019-10-31 15:44:26 +00:00
SecretId=DEFAULT_SECRET_NAME, ClientRequestToken=client_request_token
)
@mock_secretsmanager
def test_rotate_secret_rotation_lambda_arn_too_long():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
2019-10-31 15:44:26 +00:00
rotation_lambda_arn = "85B7-446A-B7E4" * 147 # == 2058 characters
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.rotate_secret(
2019-10-31 15:44:26 +00:00
SecretId=DEFAULT_SECRET_NAME, RotationLambdaARN=rotation_lambda_arn
)
@mock_secretsmanager
def test_rotate_secret_rotation_period_zero():
# Test is intentionally empty. Boto3 catches zero day rotation period
# and raises ParamValidationError before Moto can see it.
# test_server actually handles this error.
assert True
@mock_secretsmanager
def test_rotate_secret_rotation_period_too_long():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
2019-10-31 15:44:26 +00:00
rotation_rules = {"AutomaticallyAfterDays": 1001}
with pytest.raises(ClientError):
2021-10-18 19:44:29 +00:00
conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME, RotationRules=rotation_rules)
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
def get_rotation_zip_file():
2021-09-21 15:19:49 +00:00
from tests.test_awslambda.utilities import _process_lambda
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
func_str = """
import boto3
import json
def lambda_handler(event, context):
arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
client = boto3.client("secretsmanager", region_name="us-west-2", endpoint_url="http://motoserver:5000")
metadata = client.describe_secret(SecretId=arn)
value = client.get_secret_value(SecretId=arn, VersionId=token, VersionStage="AWSPENDING")
if not metadata['RotationEnabled']:
print("Secret %s is not enabled for rotation." % arn)
raise ValueError("Secret %s is not enabled for rotation." % arn)
versions = metadata['VersionIdsToStages']
if token not in versions:
print("Secret version %s has no stage for rotation of secret %s." % (token, arn))
raise ValueError("Secret version %s has no stage for rotation of secret %s." % (token, arn))
if "AWSCURRENT" in versions[token]:
print("Secret version %s already set as AWSCURRENT for secret %s." % (token, arn))
return
elif "AWSPENDING" not in versions[token]:
print("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
raise ValueError("Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn))
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
if step == 'createSecret':
try:
client.get_secret_value(SecretId=arn, VersionId=token, VersionStage='AWSPENDING')
except client.exceptions.ResourceNotFoundException:
client.put_secret_value(
SecretId=arn,
ClientRequestToken=token,
SecretString=json.dumps({'create': True}),
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
VersionStages=['AWSPENDING']
)
if step == 'setSecret':
client.put_secret_value(
SecretId=arn,
ClientRequestToken=token,
SecretString='UpdatedValue',
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
VersionStages=["AWSPENDING"],
)
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
elif step == 'finishSecret':
current_version = next(
version
for version, stages in metadata['VersionIdsToStages'].items()
if 'AWSCURRENT' in stages
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
)
print("current: %s new: %s" % (current_version, token))
client.update_secret_version_stage(
SecretId=arn,
VersionStage='AWSCURRENT',
MoveToVersionId=token,
RemoveFromVersionId=current_version,
)
client.update_secret_version_stage(
SecretId=arn,
VersionStage='AWSPENDING',
RemoveFromVersionId=token,
)
"""
return _process_lambda(func_str)
if settings.TEST_SERVER_MODE:
@mock_lambda
@mock_secretsmanager
def test_rotate_secret_using_lambda():
2021-09-21 15:19:49 +00:00
from tests.test_awslambda.utilities import get_role_name
Support rotating secrets using Lambda [#3905] (#3912) * Support rotating secrets using Lambda The Secrets manager rotation process uses an AWS Lambda function to perform the rotation of a secret. [1] In fact, it's not possible to trigger rotation of a Secret without specifying a Lambda function at some point in the life of the secret: ``` $ aws secretsmanager rotate-secret --secret-id /rotationTest An error occurred (InvalidRequestException) when calling the RotateSecret operation: No Lambda rotation function ARN is associated with this secret. ``` `moto` can be a little more lenient in this regard and allow `rotate_secret` to be called without a Lambda function being present, if only to allow simulation of the `AWSCURRENT` and `AWSPREVIOUS` labels moving across versions. However, if a lambda function _has_ been specified when calling `rotate_secret`, it should be invoked therefore providing the developer with the full multi-stage process [3] which can be used to test the Lambda function itself and ensuring that full end-to-end testing is performed. Without this there's no easy way to configure the Secret in the state needed to provide the Lambda function with the data in the format it needs to be in at each step of the invocation process. [1]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html [2]: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.rotate_secret [3]: https://docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets-lambda-function-overview.html#rotation-explanation-of-steps * Run `black` over `secretsmanager/models.py` * Make `lambda_backends` import local to the condition * Implement `update_secret_version_stage` Allow a staging label to be moved across versions. https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/secretsmanager.html#SecretsManager.Client.update_secret_version_stage * Add an integration test for Secrets Manager & Lambda * Support passing `ClientRequestToken` to `put_secret_value` By passing `ClientRequestToken` to `put_secret_value` within the lambda function invoked by calling `rotate_secret`, one can update the value associated with the existing (pending) version, without causing a new secret version to be created. * Add application logic for `AWSPENDING` The rotation function must end with the versions of the secret in one of two states: - The `AWSPENDING` and `AWSCURRENT` staging labels are attached to the same version of the secret, or - The `AWSPENDING` staging label is not attached to any version of the secret. If the `AWSPENDING` staging label is present but not attached to the same version as `AWSCURRENT` then any later invocation of RotateSecret assumes that a previous rotation request is still in progress and returns an error. * Update `default_version_id` after Lambda rotation concludes Call `set_default_version_id` directly, rather than going through `reset_default_version` as the Lambda function is responsible for moving the version labels around, not `rotate_secret`. * Run `black` over changed files * Fix Python 2.7 compatibility * Add additional test coverage for Secrets Manager * Fix bug found by tests AWSPENDING + AWSCURRENT check wasn't using `version_stages`. Also tidy up the AWSCURRENT moving in `update_secret_version_stage` to remove AWSPREVIOUS it from the new stage. * Run `black` over changed files * Add additional `rotate_secret` tests * Skip `test_rotate_secret_lambda_invocations` in test server mode * Add test for invalid Lambda ARN
2021-05-11 11:08:01 +00:00
# Passing a `RotationLambdaARN` value to `rotate_secret` should invoke lambda
lambda_conn = boto3.client(
"lambda", region_name="us-west-2", endpoint_url="http://localhost:5000",
)
func = lambda_conn.create_function(
FunctionName="testFunction",
Runtime="python3.8",
Role=get_role_name(),
Handler="lambda_function.lambda_handler",
Code={"ZipFile": get_rotation_zip_file()},
Description="Secret rotator",
Timeout=3,
MemorySize=128,
Publish=True,
)
secrets_conn = boto3.client(
"secretsmanager",
region_name="us-west-2",
endpoint_url="http://localhost:5000",
)
secret = secrets_conn.create_secret(
Name=DEFAULT_SECRET_NAME, SecretString="InitialValue",
)
initial_version = secret["VersionId"]
rotated_secret = secrets_conn.rotate_secret(
SecretId=DEFAULT_SECRET_NAME,
RotationLambdaARN=func["FunctionArn"],
RotationRules=dict(AutomaticallyAfterDays=30,),
)
# Ensure we received an updated VersionId from `rotate_secret`
assert rotated_secret["VersionId"] != initial_version
updated_secret = secrets_conn.get_secret_value(
SecretId=DEFAULT_SECRET_NAME, VersionStage="AWSCURRENT",
)
rotated_version = updated_secret["VersionId"]
assert initial_version != rotated_version
metadata = secrets_conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert metadata["VersionIdsToStages"][initial_version] == ["AWSPREVIOUS"]
assert metadata["VersionIdsToStages"][rotated_version] == ["AWSCURRENT"]
assert updated_secret["SecretString"] == "UpdatedValue"
@mock_secretsmanager
def test_put_secret_value_on_non_existing_secret():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError) as cm:
conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="foosecret",
VersionStages=["AWSCURRENT"],
)
2020-11-11 15:55:37 +00:00
cm.value.response["Error"]["Message"].should.equal(
"Secrets Manager can't find the specified secret."
)
@mock_secretsmanager
def test_put_secret_value_puts_new_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-07-26 06:40:39 +00:00
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
2019-10-31 15:44:26 +00:00
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="foosecret",
VersionStages=["AWSCURRENT"],
)
version_id = put_secret_value_dict["VersionId"]
2019-10-31 15:44:26 +00:00
get_secret_value_dict = conn.get_secret_value(
SecretId=DEFAULT_SECRET_NAME, VersionId=version_id, VersionStage="AWSCURRENT"
)
assert get_secret_value_dict
2019-10-31 15:44:26 +00:00
assert get_secret_value_dict["SecretString"] == "foosecret"
@mock_secretsmanager
def test_put_secret_binary_value_puts_new_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-07-26 06:40:39 +00:00
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
2019-10-31 15:44:26 +00:00
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
2021-07-26 06:40:39 +00:00
SecretBinary=b"foosecret",
2019-10-31 15:44:26 +00:00
VersionStages=["AWSCURRENT"],
)
version_id = put_secret_value_dict["VersionId"]
2019-10-31 15:44:26 +00:00
get_secret_value_dict = conn.get_secret_value(
SecretId=DEFAULT_SECRET_NAME, VersionId=version_id, VersionStage="AWSCURRENT"
)
assert get_secret_value_dict
2021-07-26 06:40:39 +00:00
assert get_secret_value_dict["SecretBinary"] == b"foosecret"
@mock_secretsmanager
def test_create_and_put_secret_binary_value_puts_new_secret():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-07-26 06:40:39 +00:00
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
2019-10-31 15:44:26 +00:00
conn.put_secret_value(
2021-07-26 06:40:39 +00:00
SecretId=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret_update"
2019-10-31 15:44:26 +00:00
)
latest_secret = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)
assert latest_secret
2021-07-26 06:40:39 +00:00
assert latest_secret["SecretBinary"] == b"foosecret_update"
@mock_secretsmanager
def test_put_secret_binary_requires_either_string_or_binary():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError) as ire:
conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME)
2020-10-06 06:04:09 +00:00
ire.value.response["Error"]["Code"].should.equal("InvalidRequestException")
ire.value.response["Error"]["Message"].should.equal(
2019-10-31 15:44:26 +00:00
"You must provide either SecretString or SecretBinary."
)
@mock_secretsmanager
def test_put_secret_value_can_get_first_version_if_put_twice():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-07-26 06:40:39 +00:00
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
2019-10-31 15:44:26 +00:00
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="first_secret",
VersionStages=["AWSCURRENT"],
)
first_version_id = put_secret_value_dict["VersionId"]
conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="second_secret",
VersionStages=["AWSCURRENT"],
)
2019-10-31 15:44:26 +00:00
first_secret_value_dict = conn.get_secret_value(
SecretId=DEFAULT_SECRET_NAME, VersionId=first_version_id
)
first_secret_value = first_secret_value_dict["SecretString"]
2019-10-31 15:44:26 +00:00
assert first_secret_value == "first_secret"
@mock_secretsmanager
def test_put_secret_value_versions_differ_if_same_secret_put_twice():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
2019-10-31 15:44:26 +00:00
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="dupe_secret",
VersionStages=["AWSCURRENT"],
)
first_version_id = put_secret_value_dict["VersionId"]
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="dupe_secret",
VersionStages=["AWSCURRENT"],
)
second_version_id = put_secret_value_dict["VersionId"]
assert first_version_id != second_version_id
@mock_secretsmanager
def test_put_secret_value_maintains_description_and_tags():
conn = boto3.client("secretsmanager", region_name="us-west-2")
previous_response = conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString="foosecret",
Description="desc",
Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
)
previous_version_id = previous_response["VersionId"]
conn = boto3.client("secretsmanager", region_name="us-west-2")
current_response = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="dupe_secret",
VersionStages=["AWSCURRENT"],
)
current_version_id = current_response["VersionId"]
secret_details = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert secret_details["Tags"] == [
{"Key": "Foo", "Value": "Bar"},
{"Key": "Mykey", "Value": "Myvalue"},
]
assert secret_details["Description"] == "desc"
assert secret_details["VersionIdsToStages"] is not None
assert previous_version_id in secret_details["VersionIdsToStages"]
assert current_version_id in secret_details["VersionIdsToStages"]
assert secret_details["VersionIdsToStages"][previous_version_id] == ["AWSPREVIOUS"]
assert secret_details["VersionIdsToStages"][current_version_id] == ["AWSCURRENT"]
@mock_secretsmanager
def test_can_list_secret_version_ids():
2019-10-31 15:44:26 +00:00
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
2019-10-31 15:44:26 +00:00
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="dupe_secret",
VersionStages=["AWSCURRENT"],
)
first_version_id = put_secret_value_dict["VersionId"]
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="dupe_secret",
VersionStages=["AWSCURRENT"],
)
second_version_id = put_secret_value_dict["VersionId"]
versions_list = conn.list_secret_version_ids(SecretId=DEFAULT_SECRET_NAME)
2019-10-31 15:44:26 +00:00
returned_version_ids = [v["VersionId"] for v in versions_list["Versions"]]
assert [first_version_id, second_version_id].sort() == returned_version_ids.sort()
@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_update_secret(pass_arn):
conn = boto3.client("secretsmanager", region_name="us-west-2")
created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
assert created_secret["ARN"]
assert created_secret["Name"] == "test-secret"
assert created_secret["VersionId"] != ""
secret_id = created_secret["ARN"] if pass_arn else "test-secret"
secret = conn.get_secret_value(SecretId=secret_id)
assert secret["SecretString"] == "foosecret"
updated_secret = conn.update_secret(SecretId=secret_id, SecretString="barsecret")
assert updated_secret["ARN"]
assert updated_secret["Name"] == "test-secret"
assert updated_secret["VersionId"] != ""
secret = conn.get_secret_value(SecretId=secret_id)
assert secret["SecretString"] == "barsecret"
assert created_secret["VersionId"] != updated_secret["VersionId"]
@mock_secretsmanager
def test_update_secret_with_tags_and_description():
conn = boto3.client("secretsmanager", region_name="us-west-2")
created_secret = conn.create_secret(
Name="test-secret",
SecretString="foosecret",
Description="desc",
Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}],
)
assert created_secret["ARN"]
assert created_secret["Name"] == "test-secret"
assert created_secret["VersionId"] != ""
secret = conn.get_secret_value(SecretId="test-secret")
assert secret["SecretString"] == "foosecret"
updated_secret = conn.update_secret(
SecretId="test-secret", SecretString="barsecret"
)
assert updated_secret["ARN"]
assert updated_secret["Name"] == "test-secret"
assert updated_secret["VersionId"] != ""
secret = conn.get_secret_value(SecretId="test-secret")
assert secret["SecretString"] == "barsecret"
assert created_secret["VersionId"] != updated_secret["VersionId"]
secret_details = conn.describe_secret(SecretId="test-secret")
assert secret_details["Tags"] == [
{"Key": "Foo", "Value": "Bar"},
{"Key": "Mykey", "Value": "Myvalue"},
]
assert secret_details["Description"] == "desc"
@mock_secretsmanager
def test_update_secret_with_KmsKeyId():
conn = boto3.client("secretsmanager", region_name="us-west-2")
created_secret = conn.create_secret(
Name="test-secret", SecretString="foosecret", KmsKeyId="foo_arn"
)
assert created_secret["ARN"]
assert created_secret["Name"] == "test-secret"
assert created_secret["VersionId"] != ""
secret = conn.get_secret_value(SecretId="test-secret")
assert secret["SecretString"] == "foosecret"
secret_details = conn.describe_secret(SecretId="test-secret")
secret_details["KmsKeyId"].should.equal("foo_arn")
updated_secret = conn.update_secret(
SecretId="test-secret", SecretString="barsecret", KmsKeyId="bar_arn"
)
assert updated_secret["ARN"]
assert updated_secret["Name"] == "test-secret"
assert updated_secret["VersionId"] != ""
secret = conn.get_secret_value(SecretId="test-secret")
assert secret["SecretString"] == "barsecret"
assert created_secret["VersionId"] != updated_secret["VersionId"]
secret_details = conn.describe_secret(SecretId="test-secret")
secret_details["KmsKeyId"].should.equal("bar_arn")
@mock_secretsmanager
def test_update_secret_which_does_not_exit():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError) as cm:
2021-10-18 19:44:29 +00:00
conn.update_secret(SecretId="test-secret", SecretString="barsecret")
2020-10-06 06:46:05 +00:00
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_update_secret_marked_as_deleted():
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-10-18 19:44:29 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
with pytest.raises(ClientError) as cm:
2021-10-18 19:44:29 +00:00
conn.update_secret(SecretId="test-secret", SecretString="barsecret")
assert (
2020-10-06 06:46:05 +00:00
"because it was marked for deletion." in cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_update_secret_marked_as_deleted_after_restoring():
conn = boto3.client("secretsmanager", region_name="us-west-2")
2021-10-18 19:44:29 +00:00
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
conn.restore_secret(SecretId="test-secret")
updated_secret = conn.update_secret(
SecretId="test-secret", SecretString="barsecret"
)
assert updated_secret["ARN"]
assert updated_secret["Name"] == "test-secret"
assert updated_secret["VersionId"] != ""
@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_tag_resource(pass_arn):
conn = boto3.client("secretsmanager", region_name="us-west-2")
created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
secret_id = created_secret["ARN"] if pass_arn else "test-secret"
conn.tag_resource(
SecretId=secret_id, Tags=[{"Key": "FirstTag", "Value": "SomeValue"},],
)
conn.tag_resource(
SecretId=secret_id, Tags=[{"Key": "SecondTag", "Value": "AnotherValue"},],
)
secrets = conn.list_secrets()
assert secrets["SecretList"][0].get("Tags") == [
{"Key": "FirstTag", "Value": "SomeValue"},
{"Key": "SecondTag", "Value": "AnotherValue"},
]
with pytest.raises(ClientError) as cm:
conn.tag_resource(
SecretId="dummy-test-secret",
2020-11-11 15:55:37 +00:00
Tags=[{"Key": "FirstTag", "Value": "SomeValue"},],
)
2020-11-11 15:55:37 +00:00
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_untag_resource(pass_arn):
conn = boto3.client("secretsmanager", region_name="us-west-2")
created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
secret_id = created_secret["ARN"] if pass_arn else "test-secret"
conn.tag_resource(
SecretId=secret_id,
Tags=[
{"Key": "FirstTag", "Value": "SomeValue"},
{"Key": "SecondTag", "Value": "SomeValue"},
],
)
conn.untag_resource(SecretId=secret_id, TagKeys=["FirstTag"])
secrets = conn.list_secrets()
assert secrets["SecretList"][0].get("Tags") == [
{"Key": "SecondTag", "Value": "SomeValue"},
]
with pytest.raises(ClientError) as cm:
conn.untag_resource(
SecretId="dummy-test-secret", TagKeys=["FirstTag"],
)
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_secret_versions_to_stages_attribute_discrepancy():
client = boto3.client("secretsmanager", region_name="us-west-2")
resp = client.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
previous_version_id = resp["VersionId"]
resp = client.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="dupe_secret",
VersionStages=["AWSCURRENT"],
)
current_version_id = resp["VersionId"]
secret = client.describe_secret(SecretId=DEFAULT_SECRET_NAME)
describe_vtos = secret["VersionIdsToStages"]
assert describe_vtos[current_version_id] == ["AWSCURRENT"]
assert describe_vtos[previous_version_id] == ["AWSPREVIOUS"]
secret = client.list_secrets(
Filters=[{"Key": "name", "Values": [DEFAULT_SECRET_NAME]}]
).get("SecretList")[0]
list_vtos = secret["SecretVersionsToStages"]
assert list_vtos[current_version_id] == ["AWSCURRENT"]
assert list_vtos[previous_version_id] == ["AWSPREVIOUS"]
assert describe_vtos == list_vtos
@mock_secretsmanager
def test_update_secret_with_client_request_token():
client = boto3.client("secretsmanager", region_name="us-west-2")
secret_name = "test-secret"
client_request_token = str(uuid4())
client.create_secret(Name=secret_name, SecretString="first-secret")
updated_secret = client.update_secret(
SecretId=secret_name,
SecretString="second-secret",
ClientRequestToken=client_request_token,
)
assert client_request_token == updated_secret["VersionId"]
updated_secret = client.update_secret(
SecretId=secret_name, SecretString="third-secret",
)
assert client_request_token != updated_secret["VersionId"]
invalid_request_token = "test-token"
with pytest.raises(ParamValidationError) as pve:
client.update_secret(
SecretId=secret_name,
SecretString="fourth-secret",
ClientRequestToken=invalid_request_token,
)
pve.value.response["Error"]["Code"].should.equal("InvalidParameterException")
pve.value.response["Error"]["Message"].should.equal(
"ClientRequestToken must be 32-64 characters long."
)