Repository: localstack/localstack

Issue:
bug: SNS creates wrong MessageAttributes when relaying msg to an SQS DLQ
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

When relaying a message with `MessageAttributes` to a SQS DLQ after failing to deliver to another SQS queue, the messages attributes were wrong.
Snapshots results:
With `RawDelivery` set to `false`:
```json
{
  "Body": {
    "Message": "test_dlq_after_sqs_endpoint_deleted",
    "MessageAttributes": {
      "attr1": {
        "Type": "Number",
        "Value": "111"
      },
      "attr2": {
        "Type": "Binary",
        "Value": "AgME"
      }
    },
    "MessageId": "<uuid:1>",
    "Signature": "<signature>",
    "SignatureVersion": "1",
    "SigningCertURL": "https://sns.<region>.amazonaws.com/SimpleNotificationService-<signing-cert-file:1>",
    "Timestamp": "date",
    "TopicArn": "arn:aws:sns:<region>:111111111111:<resource:1>",
    "Type": "Notification",
    "UnsubscribeURL": "<unsubscribe-domain>/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:<region>:111111111111:<resource:1>:<resource:2>",
  },
  "MD5OfBody": "<md5-hash>",
  "MD5OfMessageAttributes": "<md5-hash>",
  "MessageAttributes": {
    "ErrorCode": {
      "DataType": "String",
      "StringValue": "200"
    },
    "ErrorMessage": {
      "DataType": "String",
      "StringValue": "An error occurred (AWS.SimpleQueueService.NonExistentQueue) when calling the GetQueueUrl operation (reached max retries: 0): The specified queue does not exist for this wsdl version."
    },
    "RequestID": {
      "DataType": "String",
      "StringValue": "<uuid:?>"
    }
  },
  "MessageId": "<uuid:2>",
  "ReceiptHandle": "<receipt-handle:1>"
}
```
We can see added `MessageAttributes` on the received SQS message.

With `RawDelivery` set to `true`:
```json
{
  "Body": "test_dlq_after_sqs_endpoint_deleted",
  "MD5OfBody": "<md5-hash>",
  "MD5OfMessageAttributes": "<md5-hash>",
  "MessageAttributes": {
    "ErrorCode": {
      "DataType": "String",
      "StringValue": "200"
    },
    "ErrorMessage": {
      "DataType": "String",
      "StringValue": "An error occurred (AWS.SimpleQueueService.NonExistentQueue) when calling the GetQueueUrl operation (reached max retries: 0): The specified queue does not exist for this wsdl version."
    },
    "RequestID": {
      "DataType": "String",
      "StringValue": "<uuid:?>"
    }
  },
  "MessageId": "<uuid:1>",
  "ReceiptHandle": "<receipt-handle:1>"
}
```

### Expected Behavior

With `RawDelivery` set to `false`:
```json
{
  "Body": {
    "Type": "Notification",
    "MessageId": "<uuid:1>",
    "TopicArn": "arn:aws:sns:<region>:111111111111:<resource:1>",
    "Message": "test_dlq_after_sqs_endpoint_deleted",
    "Timestamp": "date",
    "SignatureVersion": "1",
    "Signature": "<signature>",
    "SigningCertURL": "https://sns.<region>.amazonaws.com/SimpleNotificationService-<signing-cert-file:1>",
    "UnsubscribeURL": "<unsubscribe-domain>/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:<region>:111111111111:<resource:1>:<resource:2>",
    "MessageAttributes": {
      "attr2": {
        "Type": "Binary",
        "Value": "AgME"
      },
      "attr1": {
        "Type": "Number",
        "Value": "111"
      }
    }
  },
  "MD5OfBody": "<md5-hash>",
  "MessageId": "<uuid:2>",
  "ReceiptHandle": "<receipt-handle:1>"
}
```
With `RawDelivery` set to `true`:
```json
{
  "Body": "test_dlq_after_sqs_endpoint_deleted",
  "MD5OfBody": "<md5-hash>",
  "MD5OfMessageAttributes": "<md5-hash>",
  "MessageAttributes": {
    "attr1": {
      "DataType": "Number",
      "StringValue": "111"
    },
    "attr2": {
      "BinaryValue": "b'\\x02\\x03\\x04'",
      "DataType": "Binary"
    }
  },
  "MessageId": "<uuid:1>",
  "ReceiptHandle": "<receipt-handle:1>"
}
```
We can see the attributes are kept and not replaced with Error messages. 

### How are you starting LocalStack?

With the `localstack` script

### Steps To Reproduce

Using `tests.integration.test_sns.TestSNSProvider.test_redrive_policy_sqs_queue_subscription`

### Environment

```markdown
- OS:MacOS Monterey 12.5
- LocalStack: 1.0.5dev (latest)
```


### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/localstack/services/sns/provider.py b/localstack/services/sns/provider.py
--- a/localstack/services/sns/provider.py
+++ b/localstack/services/sns/provider.py
@@ -989,7 +989,13 @@ async def message_to_subscriber(
         except Exception as exc:
             LOG.info("Unable to forward SNS message to SQS: %s %s", exc, traceback.format_exc())
             store_delivery_log(subscriber, False, message, message_id)
-            sns_error_to_dead_letter_queue(subscriber, message_body, str(exc))
+
+            if is_raw_message_delivery(subscriber):
+                msg_attrs = create_sqs_message_attributes(subscriber, message_attributes)
+            else:
+                msg_attrs = {}
+
+            sns_error_to_dead_letter_queue(subscriber, message_body, str(exc), msg_attrs=msg_attrs)
             if "NonExistentQueue" in str(exc):
                 LOG.debug("The SQS queue endpoint does not exist anymore")
                 # todo: if the queue got deleted, even if we recreate a queue with the same name/url
diff --git a/localstack/utils/aws/dead_letter_queue.py b/localstack/utils/aws/dead_letter_queue.py
--- a/localstack/utils/aws/dead_letter_queue.py
+++ b/localstack/utils/aws/dead_letter_queue.py
@@ -1,7 +1,7 @@
 import json
 import logging
 import uuid
-from typing import Dict, List, Union
+from typing import Dict, List
 
 from localstack.utils.aws import aws_stack
 from localstack.utils.aws.aws_models import LambdaFunction
@@ -10,12 +10,13 @@
 LOG = logging.getLogger(__name__)
 
 
-def sns_error_to_dead_letter_queue(sns_subscriber: dict, event: str, error):
-    # event should be of type str if coming from SNS, as it represents the message body being passed down
+def sns_error_to_dead_letter_queue(sns_subscriber: dict, message: str, error, msg_attrs=None):
+    # message should be of type str if coming from SNS, as it represents the message body being passed down
     policy = json.loads(sns_subscriber.get("RedrivePolicy") or "{}")
     target_arn = policy.get("deadLetterTargetArn")
     if not target_arn:
         return
+    event = {"message": message, "message_attributes": msg_attrs or {}}
     return _send_to_dead_letter_queue(sns_subscriber["SubscriptionArn"], target_arn, event, error)
 
 
@@ -25,7 +26,7 @@ def lambda_error_to_dead_letter_queue(func_details: LambdaFunction, event: Dict,
     return _send_to_dead_letter_queue(source_arn, dlq_arn, event, error)
 
 
-def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Union[Dict, str], error):
+def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Dict, error):
     if not dlq_arn:
         return
     LOG.info("Sending failed execution %s to dead letter queue %s", source_arn, dlq_arn)
@@ -63,7 +64,7 @@ def _send_to_dead_letter_queue(source_arn: str, dlq_arn: str, event: Union[Dict,
     return dlq_arn
 
 
-def _prepare_messages_to_dlq(source_arn: str, event: Union[Dict, str], error) -> List[Dict]:
+def _prepare_messages_to_dlq(source_arn: str, event: Dict, error) -> List[Dict]:
     messages = []
     custom_attrs = {
         "RequestID": {"DataType": "String", "StringValue": str(uuid.uuid4())},
@@ -86,8 +87,8 @@ def _prepare_messages_to_dlq(source_arn: str, event: Union[Dict, str], error) ->
         messages.append(
             {
                 "Id": str(uuid.uuid4()),
-                "MessageBody": event,
-                "MessageAttributes": custom_attrs,
+                "MessageBody": event["message"],
+                "MessageAttributes": event.get("message_attributes", {}),
             }
         )
     elif ":lambda:" in source_arn:


Test patch:
diff --git a/tests/integration/test_sns.py b/tests/integration/test_sns.py
--- a/tests/integration/test_sns.py
+++ b/tests/integration/test_sns.py
@@ -836,16 +836,13 @@ def test_redrive_policy_lambda_subscription(
         sns_client.publish(
             TopicArn=topic_arn,
             Message="test_redrive_policy",
+            MessageAttributes={"attr1": {"DataType": "Number", "StringValue": "1"}},
         )
 
-        response = sqs_client.receive_message(QueueUrl=dlq_url, WaitTimeSeconds=10)
+        response = sqs_client.receive_message(
+            QueueUrl=dlq_url, WaitTimeSeconds=10, MessageAttributeNames=["All"]
+        )
         snapshot.match("messages", response)
-        assert (
-            len(response["Messages"]) == 1
-        ), f"invalid number of messages in DLQ response {response}"
-        message = json.loads(response["Messages"][0]["Body"])
-        assert message["Type"] == "Notification"
-        assert message["Message"] == "test_redrive_policy"
 
     @pytest.mark.aws_validated
     def test_publish_with_empty_subject(self, sns_client, sns_create_topic, snapshot):
@@ -1813,7 +1810,12 @@ def test_redrive_policy_sqs_queue_subscription(
         }
         sns_client.publish(TopicArn=topic_arn, Message=message, MessageAttributes=message_attr)
 
-        response = sqs_client.receive_message(QueueUrl=dlq_url, WaitTimeSeconds=10)
+        response = sqs_client.receive_message(
+            QueueUrl=dlq_url,
+            WaitTimeSeconds=10,
+            AttributeNames=["All"],
+            MessageAttributeNames=["All"],
+        )
         snapshot.match("messages", response)
 
     @pytest.mark.aws_validated
diff --git a/tests/integration/test_sns.snapshot.json b/tests/integration/test_sns.snapshot.json
--- a/tests/integration/test_sns.snapshot.json
+++ b/tests/integration/test_sns.snapshot.json
@@ -56,13 +56,30 @@
     }
   },
   "tests/integration/test_sns.py::TestSNSProvider::test_redrive_policy_sqs_queue_subscription[True]": {
-    "recorded-date": "09-08-2022, 11:36:01",
+    "recorded-date": "10-08-2022, 11:36:11",
     "recorded-content": {
       "messages": {
         "Messages": [
           {
+            "Attributes": {
+              "ApproximateFirstReceiveTimestamp": "timestamp",
+              "ApproximateReceiveCount": "1",
+              "SenderId": "<sender-id>",
+              "SentTimestamp": "timestamp"
+            },
             "Body": "test_dlq_after_sqs_endpoint_deleted",
             "MD5OfBody": "<md5-hash>",
+            "MD5OfMessageAttributes": "<md5-hash>",
+            "MessageAttributes": {
+              "attr1": {
+                "DataType": "Number",
+                "StringValue": "111"
+              },
+              "attr2": {
+                "BinaryValue": "b'\\x02\\x03\\x04'",
+                "DataType": "Binary"
+              }
+            },
             "MessageId": "<uuid:1>",
             "ReceiptHandle": "<receipt-handle:1>"
           }
@@ -75,11 +92,17 @@
     }
   },
   "tests/integration/test_sns.py::TestSNSProvider::test_redrive_policy_sqs_queue_subscription[False]": {
-    "recorded-date": "09-08-2022, 11:36:04",
+    "recorded-date": "10-08-2022, 11:36:14",
     "recorded-content": {
       "messages": {
         "Messages": [
           {
+            "Attributes": {
+              "ApproximateFirstReceiveTimestamp": "timestamp",
+              "ApproximateReceiveCount": "1",
+              "SenderId": "<sender-id>",
+              "SentTimestamp": "timestamp"
+            },
             "Body": {
               "Type": "Notification",
               "MessageId": "<uuid:1>",
@@ -666,7 +689,7 @@
     }
   },
   "tests/integration/test_sns.py::TestSNSProvider::test_redrive_policy_lambda_subscription": {
-    "recorded-date": "09-08-2022, 11:34:55",
+    "recorded-date": "11-08-2022, 14:36:27",
     "recorded-content": {
       "subscription-attributes": {
         "Attributes": {
@@ -699,7 +722,13 @@
               "SignatureVersion": "1",
               "Signature": "<signature>",
               "SigningCertURL": "https://sns.<region>.amazonaws.com/SimpleNotificationService-<signing-cert-file:1>",
-              "UnsubscribeURL": "<unsubscribe-domain>/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:<region>:111111111111:<resource:4>:<resource:3>"
+              "UnsubscribeURL": "<unsubscribe-domain>/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:<region>:111111111111:<resource:4>:<resource:3>",
+              "MessageAttributes": {
+                "attr1": {
+                  "Type": "Number",
+                  "Value": "1"
+                }
+              }
             },
             "MD5OfBody": "<md5-hash>",
             "MessageId": "<uuid:2>",