Repository: localstack/localstack
Pull request: [SecretsManager] Fixed raising of incorrect error message when creating a secret staged for deletion.
Related to: https://github.com/localstack/localstack/issues/6530

- Updates the `SecretsManager`'s `CreateSecret` routine to raise an `InvalidRequestException` whenever a secret sharing its `SecretId` with another secret staged for deletion is received.
- Updates the arn tagging strategy to ensure creation of new secrets with past secret ids leads to new generation of new arn parts
Achieved by:
- carried out parity checks against AWS about this scenario
- added a patch to moto's SecretsManager backend, specifically to the `create_secret` function, now if condition is met this raises an `InvalidRequestException` with AWS message `AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION`
- added snapshot integration tests for the following scenario `test_exp_raised_on_creation_of_secret_scheduled_for_deletion` and one for creation of secret after deletion (staging completed) `test_can_recreate_delete_secret`.



Problem statement:
bug: When attempting to create a secret that is mark for deletion the wrong exception is thrown.
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

Attempt to create a secret with the same name as a secret waiting for deletion throws `ResourceExistsException`

### Expected Behavior

Attempt to create a secret with the same name as a secret waiting for deletion should throw `InvalidRequestException`

### How are you starting LocalStack?

Custom (please describe below)

### Steps To Reproduce

Started via test containers.

Tested using the 1.12.245 version of the AWS Java SDK on Java 17


### Environment

```markdown
- OS: MacOs 12.4
- LocalStack: 1.0
```


### Anything else?

_No response_

Hints from issue discussion:
Welcome to LocalStack! Thanks for reporting your first issue and our team will be working towards fixing the issue for you or reach out for more background information. We recommend joining our [Slack Community](https://localstack.cloud/contact/) for real-time help and drop a message to LocalStack Pro Support if you are a Pro user! If you are willing to contribute towards fixing this issue, please have a look at our [contributing guidelines](https://github.com/localstack/.github/blob/main/CONTRIBUTING.md) and our [developer guide](https://docs.localstack.cloud/developer-guide/).

Commit message: minor test ident name

Patch to review:
diff --git a/localstack/services/secretsmanager/provider.py b/localstack/services/secretsmanager/provider.py
index 86221226837cd..6a598b727313a 100644
--- a/localstack/services/secretsmanager/provider.py
+++ b/localstack/services/secretsmanager/provider.py
@@ -70,6 +70,12 @@
 AWSPREVIOUS: Final[str] = "AWSPREVIOUS"
 AWSPENDING: Final[str] = "AWSPENDING"
 AWSCURRENT: Final[str] = "AWSCURRENT"
+#
+# Error Messages.
+AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION: Final[str] = (
+    "An error occurred (InvalidRequestException) when calling the CreateSecret operation: "
+    "You can't create this secret because a secret with this name is already scheduled for deletion."
+)
 
 LOG = logging.getLogger(__name__)
 
@@ -338,6 +344,18 @@ def moto_smb_get_secret_value(fn, self, secret_id, version_id, version_stage):
     return res
 
 
+@patch(SecretsManagerBackend.create_secret)
+def moto_smb_create_secret(fn, self, name, *args, **kwargs):
+
+    # Creating a secret with a SecretId equal to one that is scheduled for
+    # deletion should raise an 'InvalidRequestException'.
+    secret: Optional[FakeSecret] = self.secrets.get(name, None)
+    if secret is not None and secret.deleted_date is not None:
+        raise InvalidRequestException(AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION)
+
+    return fn(self, name, *args, **kwargs)
+
+
 @patch(FakeSecret.to_dict)
 def fake_secret_to_dict(fn, self):
     res_dict = fn(self)
diff --git a/tests/integration/secretsmanager/test_secretsmanager.py b/tests/integration/secretsmanager/test_secretsmanager.py
index 7cab791fcd9ad..817e98bacd470 100644
--- a/tests/integration/secretsmanager/test_secretsmanager.py
+++ b/tests/integration/secretsmanager/test_secretsmanager.py
@@ -9,7 +9,11 @@
 from py._code.code import ExceptionInfo
 
 from localstack.aws.accounts import get_aws_account_id
+from localstack.aws.api.secretsmanager import CreateSecretRequest, DeleteSecretRequest
 from localstack.services.awslambda.lambda_utils import LAMBDA_RUNTIME_PYTHON36
+from localstack.services.secretsmanager.provider import (
+    AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION,
+)
 from localstack.utils import testutil
 from localstack.utils.aws import aws_stack
 from localstack.utils.strings import short_uid
@@ -1709,3 +1713,36 @@ def test_delete_non_existent_secret_returns_as_if_secret_exists(self, sm_client)
         assert response["Name"] == secret_id
         assert response["ARN"] is not None
         assert response["DeletionDate"] is not None
+
+    def test_exp_raised_on_creation_of_secret_scheduled_for_deletion(self, sm_client):
+        create_secret_req: CreateSecretRequest = CreateSecretRequest(
+            Name=f"secret-{short_uid()}", SecretString=f"secretstr-{short_uid()}"
+        )
+        stage_deletion_req: DeleteSecretRequest = DeleteSecretRequest(
+            SecretId=create_secret_req["Name"], RecoveryWindowInDays=7
+        )
+
+        sm_client.create_secret(**create_secret_req)
+        sm_client.delete_secret(**stage_deletion_req)
+
+        with pytest.raises(Exception) as invalid_req_ex:
+            sm_client.create_secret(**create_secret_req)
+        assert invalid_req_ex.typename == "InvalidRequestException"
+        assert (
+            invalid_req_ex.value.response["Error"]["Message"]
+            == AWS_INVALID_REQUEST_MESSAGE_CREATE_WITH_SCHEDULED_DELETION
+        )
+
+    def test_can_recreate_delete_secret(self, sm_client):
+        create_secret_req: CreateSecretRequest = CreateSecretRequest(
+            Name=f"secret-{short_uid()}", SecretString=f"secretstr-{short_uid()}"
+        )
+        stage_deletion_req: DeleteSecretRequest = DeleteSecretRequest(
+            SecretId=create_secret_req["Name"], ForceDeleteWithoutRecovery=True
+        )
+
+        sm_client.create_secret(**create_secret_req)
+        sm_client.delete_secret(**stage_deletion_req)
+        sm_client.create_secret(**create_secret_req)
+
+        sm_client.delete_secret(**stage_deletion_req)


Code review comments:
diff_hunk: @@ -1709,3 +1713,36 @@ def test_delete_non_existent_secret_returns_as_if_secret_exists(self, sm_client)
         assert response["Name"] == secret_id
         assert response["ARN"] is not None
         assert response["DeletionDate"] is not None
+
+    def test_exp_raised_on_creation_of_secret_scheduled_for_deletion(self, sm_client):
+        create_secret_req: CreateSecretRequest = CreateSecretRequest(
+            Name=f"secret-{short_uid()}", SecretString=f"secretstr-{short_uid()}"
+        )
+        stage_deletion_req: DeleteSecretRequest = DeleteSecretRequest(
+            SecretId=create_secret_req["Name"], RecoveryWindowInDays=7
+        )
+
+        sm_client.create_secret(**create_secret_req)
path: tests/integration/secretsmanager/test_secretsmanager.py
text: @user1:
nit: as a personal preference, I would directly pass the atomic arguments here instead of creating a `CreateSecretRequest` and later unpacking it.