diff --git a/moto/secretsmanager/exceptions.py b/moto/secretsmanager/exceptions.py index 6506fccfb..3c5b99886 100644 --- a/moto/secretsmanager/exceptions.py +++ b/moto/secretsmanager/exceptions.py @@ -30,6 +30,15 @@ class SecretHasNoValueException(SecretsManagerClientError): ) +class SecretStageVersionMismatchException(SecretsManagerClientError): + def __init__(self): + self.code = 404 + super().__init__( + "InvalidRequestException", + message="You provided a VersionStage that is not associated to the provided VersionId.", + ) + + class ClientError(SecretsManagerClientError): def __init__(self, message): super().__init__("InvalidParameterValue", message) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 70e6c1e22..0e2e96622 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -13,6 +13,7 @@ from .exceptions import ( InvalidParameterException, ResourceExistsException, ResourceNotFoundException, + SecretStageVersionMismatchException, InvalidRequestException, ClientError, ) @@ -226,6 +227,14 @@ class SecretsManagerBackend(BaseBackend): if not self._is_valid_identifier(secret_id): raise SecretNotFoundException() + if version_id and version_stage: + versions_dict = self.secrets[secret_id].versions + if ( + version_id in versions_dict + and version_stage not in versions_dict[version_id]["version_stages"] + ): + raise SecretStageVersionMismatchException() + if not version_id and version_stage: # set version_id to match version_stage versions_dict = self.secrets[secret_id].versions diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 7568805a4..67e0cfa07 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -153,6 +153,33 @@ def test_get_secret_version_that_does_not_exist(): ) == 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") diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py index 3aade3977..0f8e31bb8 100644 --- a/tests/test_secretsmanager/test_server.py +++ b/tests/test_secretsmanager/test_server.py @@ -618,7 +618,7 @@ def test_put_secret_value_can_get_first_version_if_put_twice(): data={ "SecretId": DEFAULT_SECRET_NAME, "VersionId": first_secret_version_id, - "VersionStage": "AWSCURRENT", + "VersionStage": "AWSPREVIOUS", }, headers={"X-Amz-Target": "secretsmanager.GetSecretValue"}, )