moto/tests/test_secretsmanager/test_secretsmanager.py

1737 lines
60 KiB
Python

from datetime import datetime, timedelta, timezone
import os
import re
import string
from unittest import SkipTest
from uuid import uuid4
import boto3
from botocore.exceptions import ClientError, ParamValidationError
from dateutil.tz import tzlocal
from freezegun import freeze_time
import pytest
from moto import mock_secretsmanager, mock_lambda, settings
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
DEFAULT_SECRET_NAME = "test-secret7"
@mock_secretsmanager
def test_get_secret_value():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="java-util-test-password", SecretString="foosecret")
result = conn.get_secret_value(SecretId="java-util-test-password")
assert result["SecretString"] == "foosecret"
@mock_secretsmanager
def test_secret_arn():
region = "us-west-2"
conn = boto3.client("secretsmanager", region_name=region)
create_dict = conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString="secret_string",
)
assert re.match(
f"arn:aws:secretsmanager:{region}:{ACCOUNT_ID}:secret:{DEFAULT_SECRET_NAME}-"
+ r"\w{6}",
create_dict["ARN"],
)
@mock_secretsmanager
def test_create_secret_with_client_request_token():
conn = boto3.client("secretsmanager", region_name="us-west-2")
version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
create_dict = conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString="secret_string",
ClientRequestToken=version_id,
)
assert create_dict
assert create_dict["VersionId"] == version_id
@mock_secretsmanager
def test_get_secret_value_by_arn():
conn = boto3.client("secretsmanager", region_name="us-west-2")
name = "java-util-test-password"
secret_value = "test_get_secret_value_by_arn"
result = conn.create_secret(Name=name, SecretString=secret_value)
arn = result["ARN"]
assert re.match(
f"^arn:aws:secretsmanager:us-west-2:{ACCOUNT_ID}:secret:{name}", arn
)
result = conn.get_secret_value(SecretId=arn)
assert result["SecretString"] == secret_value
@mock_secretsmanager
def test_get_secret_value_binary():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="java-util-test-password", SecretBinary=b"foosecret")
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")
with pytest.raises(ClientError) as cm:
conn.get_secret_value(SecretId="i-dont-exist")
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_get_secret_that_does_not_match():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="java-util-test-password", SecretString="foosecret")
with pytest.raises(ClientError) as cm:
conn.get_secret_value(SecretId="i-dont-match")
assert (
"Secrets Manager can't find the specified secret."
== cm.value.response["Error"]["Message"]
)
@mock_secretsmanager
def test_get_secret_value_that_is_marked_deleted():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
with pytest.raises(ClientError):
conn.get_secret_value(SecretId="test-secret")
@mock_secretsmanager
def test_get_secret_that_has_no_value():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="secret-no-value")
with pytest.raises(ClientError) as cm:
conn.get_secret_value(SecretId="secret-no-value")
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", SecretString="v")
secret_arn = result["ARN"]
missing_version_id = "00000000-0000-0000-0000-000000000000"
with pytest.raises(ClientError) as cm:
conn.get_secret_value(SecretId=secret_arn, VersionId=missing_version_id)
assert (
"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_get_secret_version_stage_mismatch():
conn = boto3.client("secretsmanager", region_name="us-west-2")
result = conn.create_secret(Name="test-secret", SecretString="secret")
secret_arn = result["ARN"]
rotated_secret = conn.rotate_secret(
SecretId=secret_arn, RotationRules={"AutomaticallyAfterDays": 42}
)
desc_secret = conn.describe_secret(SecretId=secret_arn)
versions_to_stages = desc_secret["VersionIdsToStages"]
version_for_test = rotated_secret["VersionId"]
stages_for_version = versions_to_stages[version_for_test]
assert "AWSPENDING" not in stages_for_version
with pytest.raises(ClientError) as cm:
conn.get_secret_value(
SecretId=secret_arn, VersionId=version_for_test, VersionStage="AWSPENDING"
)
assert (
"You provided a VersionStage that is not associated to the provided VersionId."
) == cm.value.response["Error"]["Message"]
@mock_secretsmanager
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"]
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_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_create_secret_without_value():
conn = boto3.client("secretsmanager", region_name="us-east-2")
secret_name = f"secret-{str(uuid4())[0:6]}"
create = conn.create_secret(Name=secret_name)
assert set(create.keys()) == {"ARN", "Name", "ResponseMetadata"}
describe = conn.describe_secret(SecretId=secret_name)
assert set(describe.keys()) == {
"ARN",
"Name",
"LastChangedDate",
"CreatedDate",
"ResponseMetadata",
}
with pytest.raises(ClientError) as exc:
conn.get_secret_value(SecretId=secret_name)
err = exc.value.response["Error"]
assert err["Code"] == "ResourceNotFoundException"
updated = conn.update_secret(
SecretId=secret_name,
Description="new desc",
)
assert set(updated.keys()) == {"ARN", "Name", "ResponseMetadata"}
describe = conn.describe_secret(SecretId=secret_name)
assert set(describe.keys()) == {
"ARN",
"Name",
"Description",
"LastChangedDate",
"CreatedDate",
"ResponseMetadata",
}
deleted = conn.delete_secret(SecretId=secret_name)
assert set(deleted.keys()) == {"ARN", "Name", "DeletionDate", "ResponseMetadata"}
@mock_secretsmanager
def test_create_secret_that_has_no_value_and_then_update():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="secret-no-value")
conn.update_secret(
SecretId="secret-no-value",
SecretString="barsecret",
Description="desc",
)
secret = conn.get_secret_value(SecretId="secret-no-value")
assert secret["SecretString"] == "barsecret"
@mock_secretsmanager
def test_update_secret_without_value():
conn = boto3.client("secretsmanager", region_name="us-east-2")
secret_name = f"secret-{str(uuid4())[0:6]}"
create = conn.create_secret(Name=secret_name, SecretString="foosecret")
assert set(create.keys()) == {"ARN", "Name", "VersionId", "ResponseMetadata"}
version_id = create["VersionId"]
describe1 = conn.describe_secret(SecretId=secret_name)
assert set(describe1.keys()) == {
"ARN",
"Name",
"LastChangedDate",
"VersionIdsToStages",
"CreatedDate",
"ResponseMetadata",
}
conn.get_secret_value(SecretId=secret_name)
updated = conn.update_secret(SecretId=secret_name, Description="desc")
assert set(updated.keys()) == {"ARN", "Name", "ResponseMetadata"}
describe2 = conn.describe_secret(SecretId=secret_name)
# AWS also includes 'LastAccessedDate'
assert set(describe2.keys()) == {
"ARN",
"Name",
"Description",
"LastChangedDate",
"VersionIdsToStages",
"CreatedDate",
"ResponseMetadata",
}
assert describe1["VersionIdsToStages"] == describe2["VersionIdsToStages"]
value = conn.get_secret_value(SecretId=secret_name)
assert value["SecretString"] == "foosecret"
assert value["VersionId"] == version_id
conn.delete_secret(SecretId=secret_name)
@mock_secretsmanager
def test_delete_secret():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
deleted_secret = conn.delete_secret(SecretId="test-secret")
assert deleted_secret["ARN"]
assert deleted_secret["Name"] == "test-secret"
assert deleted_secret["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)
secret_details = conn.describe_secret(SecretId="test-secret")
assert secret_details["ARN"]
assert secret_details["Name"] == "test-secret"
assert secret_details["DeletedDate"] > datetime.fromtimestamp(1, timezone.utc)
@mock_secretsmanager
def test_delete_secret_by_arn():
conn = boto3.client("secretsmanager", region_name="us-west-2")
secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
deleted_secret = conn.delete_secret(SecretId=secret["ARN"])
assert deleted_secret["ARN"] == secret["ARN"]
assert deleted_secret["Name"] == "test-secret"
assert deleted_secret["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)
secret_details = conn.describe_secret(SecretId="test-secret")
assert secret_details["ARN"] == secret["ARN"]
assert secret_details["Name"] == "test-secret"
assert secret_details["DeletedDate"] > datetime.fromtimestamp(1, timezone.utc)
@mock_secretsmanager
def test_delete_secret_force():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
result = conn.delete_secret(SecretId="test-secret", ForceDeleteWithoutRecovery=True)
assert result["ARN"]
assert result["DeletionDate"] > datetime.fromtimestamp(1, timezone.utc)
assert result["Name"] == "test-secret"
with pytest.raises(ClientError):
conn.get_secret_value(SecretId="test-secret")
@mock_secretsmanager
def test_delete_secret_force_no_such_secret():
conn = boto3.client("secretsmanager", region_name="us-west-2")
deleted_secret = conn.delete_secret(
SecretId=DEFAULT_SECRET_NAME, ForceDeleteWithoutRecovery=True
)
assert deleted_secret
assert deleted_secret["Name"] == DEFAULT_SECRET_NAME
@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, timezone.utc)
assert result["Name"] == "test-secret"
with pytest.raises(ClientError):
conn.get_secret_value(SecretId="test-secret")
@mock_secretsmanager
def test_delete_secret_that_does_not_exist():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
conn.delete_secret(SecretId="i-dont-exist")
@mock_secretsmanager
def test_delete_secret_fails_with_both_force_delete_flag_and_recovery_window_flag():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
conn.delete_secret(
SecretId="test-secret",
RecoveryWindowInDays=1,
ForceDeleteWithoutRecovery=True,
)
@mock_secretsmanager
def test_delete_secret_recovery_window_invalid_values():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
for nr in [0, 2, 6, 31, 100]:
with pytest.raises(ClientError) as exc:
conn.delete_secret(SecretId="test-secret", RecoveryWindowInDays=nr)
err = exc.value.response["Error"]
assert err["Code"] == "InvalidParameterException"
assert (
"RecoveryWindowInDays value must be between 7 and 30 days (inclusive)"
in err["Message"]
)
@mock_secretsmanager
def test_delete_secret_force_no_such_secret_with_invalid_recovery_window():
conn = boto3.client("secretsmanager", region_name="us-west-2")
for nr in [0, 2, 6, 31, 100]:
with pytest.raises(ClientError) as exc:
conn.delete_secret(
SecretId="test-secret",
RecoveryWindowInDays=nr,
ForceDeleteWithoutRecovery=True,
)
err = exc.value.response["Error"]
assert err["Code"] == "InvalidParameterException"
assert (
"RecoveryWindowInDays value must be between 7 and 30 days (inclusive)"
in err["Message"]
)
@mock_secretsmanager
def test_delete_secret_that_is_marked_deleted():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
with pytest.raises(ClientError):
conn.delete_secret(SecretId="test-secret")
@mock_secretsmanager
def test_get_random_password_default_length():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password()
assert len(random_password["RandomPassword"]) == 32
@mock_secretsmanager
def test_get_random_password_default_requirements():
# When require_each_included_type, default true
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password()
# Should contain lowercase, upppercase, digit, special character
assert any(c.islower() for c in random_password["RandomPassword"])
assert any(c.isupper() for c in random_password["RandomPassword"])
assert any(c.isdigit() for c in random_password["RandomPassword"])
assert any(c in string.punctuation for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_password_custom_length():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(PasswordLength=50)
assert len(random_password["RandomPassword"]) == 50
@mock_secretsmanager
def test_get_random_exclude_lowercase():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(PasswordLength=55, ExcludeLowercase=True)
assert not any(c.islower() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_exclude_uppercase():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(PasswordLength=55, ExcludeUppercase=True)
assert not any(c.isupper() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_exclude_characters_and_symbols():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(
PasswordLength=20, ExcludeCharacters="xyzDje@?!."
)
assert 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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
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():
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(PasswordLength=4, IncludeSpace=True)
assert any(c.isspace() for c in random_password["RandomPassword"])
@mock_secretsmanager
def test_get_random_require_each_included_type():
conn = boto3.client("secretsmanager", region_name="us-west-2")
random_password = conn.get_random_password(
PasswordLength=4, RequireEachIncludedType=True
)
assert any(c in string.punctuation for c in random_password["RandomPassword"])
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
conn.get_random_password(PasswordLength=3)
@mock_secretsmanager
def test_get_random_too_long_password():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(Exception):
conn.get_random_password(PasswordLength=5555)
@mock_secretsmanager
def test_describe_secret():
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")
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
assert secret_description["CreatedDate"] <= datetime.now(tz=tzlocal())
assert secret_description["CreatedDate"] > datetime.fromtimestamp(1, timezone.utc)
assert secret_description_2["CreatedDate"] <= datetime.now(tz=tzlocal())
assert secret_description_2["CreatedDate"] > datetime.fromtimestamp(1, timezone.utc)
assert secret_description["LastChangedDate"] <= datetime.now(tz=tzlocal())
assert secret_description["LastChangedDate"] > datetime.fromtimestamp(
1, timezone.utc
)
assert secret_description_2["LastChangedDate"] <= datetime.now(tz=tzlocal())
assert secret_description_2["LastChangedDate"] > datetime.fromtimestamp(
1, timezone.utc
)
@mock_secretsmanager
@pytest.mark.parametrize("name", ["testsecret", "test-secret"])
def test_describe_secret_with_arn(name):
conn = boto3.client("secretsmanager", region_name="us-west-2")
results = conn.create_secret(Name=name, SecretString="foosecret")
secret_description = conn.describe_secret(SecretId=results["ARN"])
assert secret_description # Returned dict is not empty
assert secret_description["Name"] == name
assert secret_description["ARN"] == results["ARN"]
assert conn.list_secrets()["SecretList"][0]["ARN"] == results["ARN"]
# We can also supply a partial ARN
partial_arn = f"arn:aws:secretsmanager:us-west-2:{ACCOUNT_ID}:secret:{name}"
resp = conn.get_secret_value(SecretId=partial_arn)
assert resp["Name"] == name
resp = conn.describe_secret(SecretId=partial_arn)
assert resp["Name"] == name
@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"])
assert secret_description["KmsKeyId"] == "dummy_arn"
assert conn.list_secrets()["SecretList"][0]["KmsKeyId"] == (
secret_description["KmsKeyId"]
)
@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
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")
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
conn.get_secret_value(SecretId="i-dont-match")
@mock_secretsmanager
def test_restore_secret():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
described_secret_before = conn.describe_secret(SecretId="test-secret")
assert described_secret_before["DeletedDate"] > datetime.fromtimestamp(
1, timezone.utc
)
restored_secret = conn.restore_secret(SecretId="test-secret")
assert restored_secret["ARN"]
assert restored_secret["Name"] == "test-secret"
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
with pytest.raises(ClientError):
conn.restore_secret(SecretId="i-dont-exist")
@mock_secretsmanager
def test_cancel_rotate_secret_with_invalid_secret_id():
conn = boto3.client("secretsmanager", region_name="us-east-1")
with pytest.raises(ClientError):
conn.cancel_rotate_secret(SecretId="invalid_id")
@mock_secretsmanager
def test_cancel_rotate_secret_after_delete():
conn = boto3.client("secretsmanager", region_name="us-east-1")
conn.create_secret(
Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
)
conn.delete_secret(
SecretId=DEFAULT_SECRET_NAME,
RecoveryWindowInDays=7,
ForceDeleteWithoutRecovery=False,
)
with pytest.raises(ClientError):
conn.cancel_rotate_secret(SecretId=DEFAULT_SECRET_NAME)
@mock_secretsmanager
def test_cancel_rotate_secret_before_enable():
conn = boto3.client("secretsmanager", region_name="us-east-1")
conn.create_secret(
Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
)
with pytest.raises(ClientError):
conn.cancel_rotate_secret(SecretId=DEFAULT_SECRET_NAME)
@mock_secretsmanager
def test_cancel_rotate_secret():
if not settings.TEST_SERVER_MODE:
raise SkipTest("rotation requires a server to be running")
from tests.test_awslambda.utilities import get_role_name
lambda_conn = boto3.client(
"lambda", region_name="us-east-1", endpoint_url="http://localhost:5000"
)
func = lambda_conn.create_function(
FunctionName="testFunction",
Runtime="python3.11",
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-east-1")
secrets_conn.create_secret(
Name=DEFAULT_SECRET_NAME, SecretString="foosecret", Description="foodescription"
)
secrets_conn.rotate_secret(
SecretId=DEFAULT_SECRET_NAME,
RotationLambdaARN=func["FunctionArn"],
RotationRules={"AutomaticallyAfterDays": 30},
)
secrets_conn.cancel_rotate_secret(SecretId=DEFAULT_SECRET_NAME)
cancelled_rotation = secrets_conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert not cancelled_rotation["RotationEnabled"]
# The function config should be preserved
assert cancelled_rotation["RotationLambdaARN"]
@mock_secretsmanager
def test_rotate_secret():
# Setup
frozen_time = datetime(2023, 5, 20, 10, 20, 30, tzinfo=tzlocal())
rotate_after_days = 10
with freeze_time(frozen_time):
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString="foosecret",
Description="foodescription",
)
# Execute
rotated_secret = conn.rotate_secret(
SecretId=DEFAULT_SECRET_NAME,
RotationRules={"AutomaticallyAfterDays": rotate_after_days},
)
describe_secret = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
# Verify
assert rotated_secret
assert rotated_secret["ARN"] != "" # Test arn not empty
assert rotated_secret["Name"] == DEFAULT_SECRET_NAME
assert rotated_secret["VersionId"] != ""
assert describe_secret["Description"] == "foodescription"
assert "NextRotationDate" in describe_secret
assert "LastRotatedDate" in describe_secret
# can't do freeze time tests in servermode tests
if settings.TEST_SERVER_MODE:
return
assert describe_secret["LastChangedDate"] == frozen_time
assert describe_secret["NextRotationDate"] == frozen_time + timedelta(
days=rotate_after_days
)
@mock_secretsmanager
def test_rotate_secret_without_secretstring():
# This test just verifies that Moto does not fail
conn = boto3.client("secretsmanager", region_name="us-east-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, Description="foodescription")
# AWS will always require a Lambda ARN to do the actual rotating
rotated_secret = conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME)
assert rotated_secret["Name"] == DEFAULT_SECRET_NAME
# Without secret-value, and without actual rotating, we can't verify much
# Just that the secret exists/can be described
# We cannot verify any versions info (as that is not created without a secret-value)
describe_secret = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert describe_secret["Description"] == "foodescription"
@mock_secretsmanager
def test_rotate_secret_enable_rotation():
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 "RotationEnabled" not in initial_description
conn.rotate_secret(
SecretId=DEFAULT_SECRET_NAME, RotationRules={"AutomaticallyAfterDays": 42}
)
rotated_description = conn.describe_secret(SecretId=DEFAULT_SECRET_NAME)
assert rotated_description
assert rotated_description["RotationEnabled"] is True
assert rotated_description["RotationRules"]["AutomaticallyAfterDays"] == 42
@mock_secretsmanager
def test_rotate_secret_that_is_marked_deleted():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
with pytest.raises(ClientError):
conn.rotate_secret(SecretId="test-secret")
@mock_secretsmanager
def test_rotate_secret_that_does_not_exist():
conn = boto3.client("secretsmanager", "us-west-2")
with pytest.raises(ClientError):
conn.rotate_secret(SecretId="i-dont-exist")
@mock_secretsmanager
def test_rotate_secret_that_does_not_match():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name="test-secret", SecretString="foosecret")
with pytest.raises(ClientError):
conn.rotate_secret(SecretId="i-dont-match")
@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
@mock_secretsmanager
def test_rotate_secret_client_request_token_too_long():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
client_request_token = (
"ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C-ED9F8B6C-85B7-446A-B7E4-38F2A3BEB13C"
)
with pytest.raises(ClientError):
conn.rotate_secret(
SecretId=DEFAULT_SECRET_NAME, ClientRequestToken=client_request_token
)
@mock_secretsmanager
def test_rotate_secret_rotation_lambda_arn_too_long():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
rotation_lambda_arn = "85B7-446A-B7E4" * 147 # == 2058 characters
with pytest.raises(ClientError):
conn.rotate_secret(
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretString="foosecret")
rotation_rules = {"AutomaticallyAfterDays": 1001}
with pytest.raises(ClientError):
conn.rotate_secret(SecretId=DEFAULT_SECRET_NAME, RotationRules=rotation_rules)
def get_rotation_zip_file():
from tests.test_awslambda.utilities import _process_lambda
func_str = """
import boto3
import json
def lambda_handler(event, context):
arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
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))
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}),
VersionStages=['AWSPENDING']
)
if step == 'setSecret':
client.put_secret_value(
SecretId=arn,
ClientRequestToken=token,
SecretString='UpdatedValue',
VersionStages=["AWSPENDING"]
)
elif step == 'finishSecret':
current_version = next(
version
for version, stages in metadata['VersionIdsToStages'].items()
if 'AWSCURRENT' in stages
)
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():
from tests.test_awslambda.utilities import get_role_name
# 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.11",
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={"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"],
)
assert cm.value.response["Error"]["Message"] == (
"Secrets Manager can't find the specified secret."
)
@mock_secretsmanager
def test_put_secret_value_puts_new_secret():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
put_secret_value_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="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["SecretString"] == "foosecret"
@mock_secretsmanager
def test_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")
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 pytest.raises(ClientError) as ire:
conn.put_secret_value(SecretId=DEFAULT_SECRET_NAME)
assert ire.value.response["Error"]["Code"] == "InvalidRequestException"
assert ire.value.response["Error"]["Message"] == (
"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")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary=b"foosecret")
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"],
)
first_secret_value_dict = conn.get_secret_value(
SecretId=DEFAULT_SECRET_NAME, VersionId=first_version_id
)
first_secret_value = first_secret_value_dict["SecretString"]
assert first_secret_value == "first_secret"
@mock_secretsmanager
def test_put_secret_value_versions_differ_if_same_secret_put_twice():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
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():
conn = boto3.client("secretsmanager", region_name="us-west-2")
conn.create_secret(Name=DEFAULT_SECRET_NAME, SecretBinary="foosecret")
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)
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
def test_put_secret_value_version_stages_response():
conn = boto3.client("secretsmanager", region_name="us-west-2")
# Creation.
first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString="first_secret_string",
ClientRequestToken=first_version_id,
)
# Use PutSecretValue to push a new version with new version stages.
second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
second_version_stages = ["SAMPLESTAGE1", "SAMPLESTAGE0"]
second_put_res_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="second_secret_string",
VersionStages=second_version_stages,
ClientRequestToken=second_version_id,
)
assert second_put_res_dict
assert second_put_res_dict["VersionId"] == second_version_id
assert second_put_res_dict["VersionStages"] == second_version_stages
@mock_secretsmanager
def test_put_secret_value_version_stages_pending_response():
conn = boto3.client("secretsmanager", region_name="us-west-2")
# Creation.
first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString="first_secret_string",
ClientRequestToken=first_version_id,
)
# Use PutSecretValue to push a new version with new version stages.
second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
second_version_stages = ["AWSPENDING"]
second_put_res_dict = conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="second_secret_string",
VersionStages=second_version_stages,
ClientRequestToken=second_version_id,
)
assert second_put_res_dict
assert second_put_res_dict["VersionId"] == second_version_id
assert second_put_res_dict["VersionStages"] == second_version_stages
@mock_secretsmanager
def test_after_put_secret_value_version_stages_can_get_current():
conn = boto3.client("secretsmanager", region_name="us-west-2")
# Creation.
first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
first_secret_string = "first_secret_string"
conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString=first_secret_string,
ClientRequestToken=first_version_id,
)
# Use PutSecretValue to push a new version with new version stages.
second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="second_secret_string",
VersionStages=["SAMPLESTAGE1", "SAMPLESTAGE0"],
ClientRequestToken=second_version_id,
)
# Get current.
get_dict = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)
assert get_dict
assert get_dict["VersionId"] == first_version_id
assert get_dict["SecretString"] == first_secret_string
assert get_dict["VersionStages"] == ["AWSCURRENT"]
@mock_secretsmanager
def test_after_put_secret_value_version_stages_can_get_current_with_custom_version_stage():
conn = boto3.client("secretsmanager", region_name="us-west-2")
# Creation.
first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
first_secret_string = "first_secret_string"
conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString=first_secret_string,
ClientRequestToken=first_version_id,
)
# Use PutSecretValue to push a new version with new version stages.
second_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="second_secret_string",
VersionStages=["SAMPLESTAGE1", "SAMPLESTAGE0"],
ClientRequestToken=second_version_id,
)
# Create a third version with one of the old stages
third_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce73"
third_secret_string = "third_secret_string"
conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString=third_secret_string,
VersionStages=["SAMPLESTAGE1"],
ClientRequestToken=third_version_id,
)
# Get current with the stage label of the third version.
get_dict = conn.get_secret_value(
SecretId=DEFAULT_SECRET_NAME, VersionStage="SAMPLESTAGE1"
)
versions = conn.list_secret_version_ids(SecretId=DEFAULT_SECRET_NAME)["Versions"]
versions_by_key = {version["VersionId"]: version for version in versions}
# Check if indeed the third version is returned
assert get_dict
assert get_dict["VersionId"] == third_version_id
assert get_dict["SecretString"] == third_secret_string
assert get_dict["VersionStages"] == ["SAMPLESTAGE1"]
# Check if all the versions have the proper labels
assert versions_by_key[first_version_id]["VersionStages"] == ["AWSCURRENT"]
assert versions_by_key[second_version_id]["VersionStages"] == ["SAMPLESTAGE0"]
assert versions_by_key[third_version_id]["VersionStages"] == ["SAMPLESTAGE1"]
@mock_secretsmanager
def test_after_put_secret_value_version_stages_pending_can_get_current():
conn = boto3.client("secretsmanager", region_name="us-west-2")
# Creation.
first_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce71"
first_secret_string = "first_secret_string"
conn.create_secret(
Name=DEFAULT_SECRET_NAME,
SecretString=first_secret_string,
ClientRequestToken=first_version_id,
)
# Use PutSecretValue to push a new version with new version stages.
pending_version_id = "eb41453f-25bb-4025-b7f4-850cfca0ce72"
conn.put_secret_value(
SecretId=DEFAULT_SECRET_NAME,
SecretString="second_secret_string",
VersionStages=["AWSPENDING"],
ClientRequestToken=pending_version_id,
)
# Get current.
get_dict = conn.get_secret_value(SecretId=DEFAULT_SECRET_NAME)
assert get_dict
assert get_dict["VersionId"] == first_version_id
assert get_dict["SecretString"] == first_secret_string
assert get_dict["VersionStages"] == ["AWSCURRENT"]
@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",
Description="new desc",
)
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"]
assert conn.describe_secret(SecretId=secret_id)["Description"] == "new desc"
@mock_secretsmanager
@pytest.mark.parametrize("pass_arn", [True, False])
def test_update_secret_updates_last_changed_dates(pass_arn):
conn = boto3.client("secretsmanager", region_name="us-west-2")
# create a secret
created_secret = conn.create_secret(Name="test-secret", SecretString="foosecret")
secret_id = created_secret["ARN"] if pass_arn else "test-secret"
# save details for secret before modification
secret_details_1 = conn.describe_secret(SecretId=secret_id)
# check if only LastChangedDate changed, CreatedDate should stay the same
with freeze_time(timedelta(minutes=1)):
conn.update_secret(SecretId="test-secret", Description="new-desc")
secret_details_2 = conn.describe_secret(SecretId=secret_id)
assert secret_details_1["CreatedDate"] == secret_details_2["CreatedDate"]
if os.environ.get("TEST_SERVER_MODE", "false").lower() == "false":
assert (
secret_details_1["LastChangedDate"]
< secret_details_2["LastChangedDate"]
)
else:
# Can't manipulate time in server mode, so use weaker constraints here
assert (
secret_details_1["LastChangedDate"]
<= secret_details_2["LastChangedDate"]
)
@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")
assert secret_details["KmsKeyId"] == "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")
assert secret_details["KmsKeyId"] == "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:
conn.update_secret(SecretId="test-secret", SecretString="barsecret")
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")
conn.create_secret(Name="test-secret", SecretString="foosecret")
conn.delete_secret(SecretId="test-secret")
with pytest.raises(ClientError) as cm:
conn.update_secret(SecretId="test-secret", SecretString="barsecret")
assert (
"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")
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="test-secret", Tags=[{"Key": "FirstTag", "Value": "SomeOtherValue"}]
)
conn.tag_resource(
SecretId=secret_id, Tags=[{"Key": "SecondTag", "Value": "AnotherValue"}]
)
secrets = conn.list_secrets()
assert secrets["SecretList"][0].get("Tags") == [
{"Key": "FirstTag", "Value": "SomeOtherValue"},
{"Key": "SecondTag", "Value": "AnotherValue"},
]
with pytest.raises(ClientError) as cm:
conn.tag_resource(
SecretId="dummy-test-secret",
Tags=[{"Key": "FirstTag", "Value": "SomeValue"}],
)
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,
)
assert pve.value.response["Error"]["Code"] == "InvalidParameterException"
assert pve.value.response["Error"]["Message"] == (
"ClientRequestToken must be 32-64 characters long."
)