Repository: localstack/localstack

Issue:
bug: impossible to create/update signatureversion on SNS (Terraform / CDKTF)
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

I am using Terraform (via CDKTF) to create SNS Topics, explicitely stating that I want to use SignatureVersion 2:

```ts
const myTopic = new SnsTopic(this, 'topic', {
    name: 'myTopic',
    signatureVersion: 2,
  })
```

However, on every plan / apply, I see this logged, just like the signatureVersion wasn't set properly on the topic (see screenshot below showing the planned changes). I can also confirm that my Sns subscription receives a SignatureVersion of 1 all the time.

<img width="377" alt="image" src="https://github.com/localstack/localstack/assets/3542313/6039191e-56b3-4359-bcb3-02da94adca59">



### Expected Behavior

The SignatureVersion should be properly set on the SnsTopic to 2.

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

docker-compose.yml

```yaml
version: '3.9'
services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack-pro:2.1.0
    ports:
      - "127.0.0.1:4566:4566"            # LocalStack Gateway
      - "127.0.0.1:4510-4559:4510-4559"  # external services port range
    environment:
      - DEBUG=${DEBUG-}
      - DOCKER_HOST=unix:///var/run/docker.sock
      - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY- }
      - LOCALSTACK_ENFORCE_IAM=1
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-~/.localstack-volume}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
  ```

Ran using `docker compose up -d localstack`

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

NA


### Environment

```markdown
- OS: MacOs 13.3.1
- LocalStack: 2.1.0 (Pro)
```


### Anything else?

_No response_


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 [contributing guide](https://docs.localstack.cloud/contributing/).

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
@@ -120,13 +120,19 @@ def get_topic_attributes(
                 raise NotFoundException("Topic does not exist")
             raise
         # TODO: fix some attributes by moto, see snapshot
+        # DeliveryPolicy
+        # EffectiveDeliveryPolicy
+        # Policy.Statement..Action -> SNS:Receive is added by moto but not returned in AWS
         # TODO: very hacky way to get the attributes we need instead of a moto patch
+        # see the attributes we need: https://docs.aws.amazon.com/sns/latest/dg/sns-topic-attributes.html
         # would need more work to have the proper format out of moto, maybe extract the model to our store
         moto_topic_model = self._get_topic(topic_arn, context)
         for attr in vars(moto_topic_model):
             if "success_feedback" in attr:
                 key = camelcase_to_pascal(underscores_to_camelcase(attr))
                 moto_response["Attributes"][key] = getattr(moto_topic_model, attr)
+            elif attr == "signature_version":
+                moto_response["Attributes"]["SignatureVersion"] = moto_topic_model.signature_version
         return moto_response
 
     def publish_batch(
@@ -387,7 +393,7 @@ def get_subscription_attributes(
         store = self.get_store(account_id=context.account_id, region_name=context.region)
         sub = store.subscriptions.get(subscription_arn)
         if not sub:
-            raise NotFoundException(f"Subscription with arn {subscription_arn} not found")
+            raise NotFoundException("Subscription does not exist")
         removed_attrs = ["sqs_queue_url"]
         if "FilterPolicyScope" in sub and "FilterPolicy" not in sub:
             removed_attrs.append("FilterPolicyScope")
@@ -696,7 +702,7 @@ def create_topic(
             )
         if tags:
             self.tag_resource(context=context, resource_arn=topic_arn, tags=tags)
-        store.topic_subscriptions[topic_arn] = store.topic_subscriptions.get(topic_arn) or []
+        store.topic_subscriptions.setdefault(topic_arn, [])
         return CreateTopicResponse(TopicArn=topic_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
@@ -196,6 +196,68 @@ def test_unsubscribe_from_non_existing_subscription(
         response = aws_client.sns.unsubscribe(SubscriptionArn=subscription["SubscriptionArn"])
         snapshot.match("empty-unsubscribe", response)
 
+    @pytest.mark.aws_validated
+    @pytest.mark.skip_snapshot_verify(
+        paths=[
+            "$.get-topic-attrs.Attributes.DeliveryPolicy",
+            "$.get-topic-attrs.Attributes.EffectiveDeliveryPolicy",
+            "$.get-topic-attrs.Attributes.Policy.Statement..Action",  # SNS:Receive is added by moto but not returned in AWS
+        ]
+    )
+    def test_create_topic_with_attributes(self, sns_create_topic, snapshot, aws_client):
+        create_topic = sns_create_topic(
+            Name="topictest.fifo",
+            Attributes={
+                "DisplayName": "TestTopic",
+                "SignatureVersion": "2",
+                "FifoTopic": "true",
+            },
+        )
+        topic_arn = create_topic["TopicArn"]
+
+        get_attrs_resp = aws_client.sns.get_topic_attributes(
+            TopicArn=topic_arn,
+        )
+        snapshot.match("get-topic-attrs", get_attrs_resp)
+
+        with pytest.raises(ClientError) as e:
+            wrong_topic_arn = f"{topic_arn[:-8]}{short_uid()}"
+            aws_client.sns.get_topic_attributes(TopicArn=wrong_topic_arn)
+
+        snapshot.match("get-attrs-nonexistent-topic", e.value.response)
+
+    @pytest.mark.aws_validated
+    @pytest.mark.skip_snapshot_verify(paths=["$..Attributes.SubscriptionPrincipal"])
+    def test_create_subscriptions_with_attributes(
+        self, sns_create_topic, sqs_create_queue, sqs_queue_arn, snapshot, aws_client
+    ):
+        topic_arn = sns_create_topic()["TopicArn"]
+        queue_url = sqs_create_queue()
+        queue_arn = sqs_queue_arn(queue_url)
+
+        subscribe_resp = aws_client.sns.subscribe(
+            TopicArn=topic_arn,
+            Protocol="sqs",
+            Endpoint=queue_arn,
+            Attributes={
+                "RawMessageDelivery": "true",
+                "FilterPolicyScope": "MessageBody",
+            },
+            ReturnSubscriptionArn=True,
+        )
+        snapshot.match("subscribe", subscribe_resp)
+
+        get_attrs_resp = aws_client.sns.get_subscription_attributes(
+            SubscriptionArn=subscribe_resp["SubscriptionArn"]
+        )
+        snapshot.match("get-attrs", get_attrs_resp)
+
+        with pytest.raises(ClientError) as e:
+            wrong_sub_arn = f"{subscribe_resp['SubscriptionArn'][:-8]}{short_uid()}"
+            aws_client.sns.get_subscription_attributes(SubscriptionArn=wrong_sub_arn)
+
+        snapshot.match("get-attrs-nonexistent-sub", e.value.response)
+
     @pytest.mark.aws_validated
     def test_attribute_raw_subscribe(
         self, sns_create_topic, sqs_create_queue, sns_create_sqs_subscription, snapshot, aws_client
@@ -210,14 +272,10 @@ def test_attribute_raw_subscribe(
         )
         topic_arn = sns_create_topic()["TopicArn"]
         queue_url = sqs_create_queue()
-        subscription = sns_create_sqs_subscription(topic_arn=topic_arn, queue_url=queue_url)
-        subscription_arn = subscription["SubscriptionArn"]
-
-        aws_client.sns.set_subscription_attributes(
-            SubscriptionArn=subscription_arn,
-            AttributeName="RawMessageDelivery",
-            AttributeValue="true",
+        subscription = sns_create_sqs_subscription(
+            topic_arn=topic_arn, queue_url=queue_url, Attributes={"RawMessageDelivery": "true"}
         )
+        subscription_arn = subscription["SubscriptionArn"]
 
         response_attributes = aws_client.sns.get_subscription_attributes(
             SubscriptionArn=subscription_arn
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
@@ -4005,5 +4005,124 @@
         }
       }
     }
+  },
+  "tests/integration/test_sns.py::TestSNSProvider::test_create_subscriptions_with_attributes": {
+    "recorded-date": "07-06-2023, 18:27:05",
+    "recorded-content": {
+      "subscribe": {
+        "SubscriptionArn": "arn:aws:sns:<region>:111111111111:<resource:3>:<resource:1>",
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 200
+        }
+      },
+      "get-attrs": {
+        "Attributes": {
+          "ConfirmationWasAuthenticated": "true",
+          "Endpoint": "arn:aws:sqs:<region>:111111111111:<resource:2>",
+          "Owner": "111111111111",
+          "PendingConfirmation": "false",
+          "Protocol": "sqs",
+          "RawMessageDelivery": "true",
+          "SubscriptionArn": "arn:aws:sns:<region>:111111111111:<resource:3>:<resource:1>",
+          "SubscriptionPrincipal": "<sub-principal>",
+          "TopicArn": "arn:aws:sns:<region>:111111111111:<resource:3>"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 200
+        }
+      },
+      "get-attrs-nonexistent-sub": {
+        "Error": {
+          "Code": "NotFound",
+          "Message": "Subscription does not exist",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 404
+        }
+      }
+    }
+  },
+  "tests/integration/test_sns.py::TestSNSProvider::test_create_topic_with_attributes": {
+    "recorded-date": "07-06-2023, 18:34:51",
+    "recorded-content": {
+      "get-topic-attrs": {
+        "Attributes": {
+          "ContentBasedDeduplication": "false",
+          "DisplayName": "TestTopic",
+          "EffectiveDeliveryPolicy": {
+            "http": {
+              "defaultHealthyRetryPolicy": {
+                "minDelayTarget": 20,
+                "maxDelayTarget": 20,
+                "numRetries": 3,
+                "numMaxDelayRetries": 0,
+                "numNoDelayRetries": 0,
+                "numMinDelayRetries": 0,
+                "backoffFunction": "linear"
+              },
+              "disableSubscriptionOverrides": false,
+              "defaultRequestPolicy": {
+                "headerContentType": "text/plain; charset=UTF-8"
+              }
+            }
+          },
+          "FifoTopic": "true",
+          "Owner": "111111111111",
+          "Policy": {
+            "Version": "2008-10-17",
+            "Id": "__default_policy_ID",
+            "Statement": [
+              {
+                "Sid": "__default_statement_ID",
+                "Effect": "Allow",
+                "Principal": {
+                  "AWS": "*"
+                },
+                "Action": [
+                  "SNS:GetTopicAttributes",
+                  "SNS:SetTopicAttributes",
+                  "SNS:AddPermission",
+                  "SNS:RemovePermission",
+                  "SNS:DeleteTopic",
+                  "SNS:Subscribe",
+                  "SNS:ListSubscriptionsByTopic",
+                  "SNS:Publish"
+                ],
+                "Resource": "arn:aws:sns:<region>:111111111111:<resource:1>",
+                "Condition": {
+                  "StringEquals": {
+                    "AWS:SourceOwner": "111111111111"
+                  }
+                }
+              }
+            ]
+          },
+          "SignatureVersion": "2",
+          "SubscriptionsConfirmed": "0",
+          "SubscriptionsDeleted": "0",
+          "SubscriptionsPending": "0",
+          "TopicArn": "arn:aws:sns:<region>:111111111111:<resource:1>"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 200
+        }
+      },
+      "get-attrs-nonexistent-topic": {
+        "Error": {
+          "Code": "NotFound",
+          "Message": "Topic does not exist",
+          "Type": "Sender"
+        },
+        "ResponseMetadata": {
+          "HTTPHeaders": {},
+          "HTTPStatusCode": 404
+        }
+      }
+    }
   }
 }