Repository: getmoto/moto
Pull request #7232 (2024-01-19 19:11:57)

Feature: RDS Snapshot Attributes
Adds `describe_db_snapshot_attributes `, `modify_db_snapshot_attributes `, `describe_db_cluster_snapshot_attributes `, `modify_db_cluster_snapshot_attributes `, methods for RDS, as well as adding/fixing the `storage_encryted` and `global_write_forwarding_requested` parameters for RDS clusters.


Patch:
diff --git a/moto/acm/models.py b/moto/acm/models.py
index 8d7bcf4c8..95435983c 100644
--- a/moto/acm/models.py
+++ b/moto/acm/models.py
@@ -343,24 +343,22 @@ class CertBundle(BaseModel):
         domain_names = set(sans + [self.common_name])
         validation_options = []
 
-        if self.status == "PENDING_VALIDATION":
-            for san in domain_names:
-                resource_record = {
-                    "Name": f"_d930b28be6c5927595552b219965053e.{san}.",
-                    "Type": "CNAME",
-                    "Value": "_c9edd76ee4a0e2a74388032f3861cc50.ykybfrwcxw.acm-validations.aws.",
+        for san in domain_names:
+            resource_record = {
+                "Name": f"_d930b28be6c5927595552b219965053e.{san}.",
+                "Type": "CNAME",
+                "Value": "_c9edd76ee4a0e2a74388032f3861cc50.ykybfrwcxw.acm-validations.aws.",
+            }
+            validation_options.append(
+                {
+                    "DomainName": san,
+                    "ValidationDomain": san,
+                    "ValidationStatus": self.status,
+                    "ValidationMethod": "DNS",
+                    "ResourceRecord": resource_record,
                 }
-                validation_options.append(
-                    {
-                        "DomainName": san,
-                        "ValidationDomain": san,
-                        "ValidationStatus": self.status,
-                        "ValidationMethod": "DNS",
-                        "ResourceRecord": resource_record,
-                    }
-                )
-        else:
-            validation_options = [{"DomainName": name} for name in domain_names]
+            )
+
         result["Certificate"]["DomainValidationOptions"] = validation_options
 
         if self.type == "IMPORTED":
diff --git a/moto/events/notifications.py b/moto/events/notifications.py
index 67e13049d..3dd94fcad 100644
--- a/moto/events/notifications.py
+++ b/moto/events/notifications.py
@@ -59,10 +59,10 @@ def _send_safe_notification(
 def _invoke_lambda(account_id: str, fn_arn: str, event: Any) -> None:
     from moto.awslambda import lambda_backends
 
-    lmbda_region = fn_arn.split(":")[3]
+    lambda_region = fn_arn.split(":")[3]
 
     body = json.dumps(event)
-    lambda_backends[account_id][lmbda_region].invoke(
+    lambda_backends[account_id][lambda_region].invoke(
         function_name=fn_arn,
         qualifier=None,  # type: ignore[arg-type]
         body=body,
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 0bfc1b259..1ac46d05a 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -229,6 +229,15 @@ class Cluster:
         self.replication_source_identifier = kwargs.get("replication_source_identifier")
         self.read_replica_identifiers: List[str] = list()
         self.is_writer: bool = False
+        self.storage_encrypted = kwargs.get("storage_encrypted", False)
+        if self.storage_encrypted:
+            self.kms_key_id = kwargs.get("kms_key_id", "default_kms_key_id")
+        else:
+            self.kms_key_id = kwargs.get("kms_key_id")
+        if self.engine == "aurora-mysql" or self.engine == "aurora-postgresql":
+            self.global_write_forwarding_requested = kwargs.get(
+                "enable_global_write_forwarding"
+            )
 
     @property
     def is_multi_az(self) -> bool:
@@ -361,7 +370,8 @@ class Cluster:
               {% endfor %}
               </VpcSecurityGroups>
               <HostedZoneId>{{ cluster.hosted_zone_id }}</HostedZoneId>
-              <StorageEncrypted>false</StorageEncrypted>
+              <StorageEncrypted>{{ 'true' if cluster.storage_encrypted else 'false' }}</StorageEncrypted>
+              <GlobalWriteForwardingRequested>{{ cluster.global_write_forwarding_requested }}</GlobalWriteForwardingRequested>
               <DbClusterResourceId>{{ cluster.resource_id }}</DbClusterResourceId>
               <DBClusterArn>{{ cluster.db_cluster_arn }}</DBClusterArn>
               <AssociatedRoles></AssociatedRoles>
@@ -485,6 +495,7 @@ class ClusterSnapshot(BaseModel):
         self.tags = tags
         self.status = "available"
         self.created_at = iso_8601_datetime_with_milliseconds()
+        self.attributes: List[Dict[str, Any]] = []
 
     @property
     def arn(self) -> str:
@@ -1122,6 +1133,7 @@ class DatabaseSnapshot(BaseModel):
         self.tags = tags
         self.status = "available"
         self.created_at = iso_8601_datetime_with_milliseconds()
+        self.attributes: List[Dict[str, Any]] = []
 
     @property
     def arn(self) -> str:
@@ -2803,6 +2815,92 @@ class RDSBackend(BaseBackend):
             pass
         return None
 
+    def describe_db_snapshot_attributes(
+        self, db_snapshot_identifier: str
+    ) -> List[Dict[str, Any]]:
+        snapshot = self.describe_db_snapshots(
+            db_instance_identifier=None, db_snapshot_identifier=db_snapshot_identifier
+        )[0]
+        return snapshot.attributes
+
+    def modify_db_snapshot_attribute(
+        self,
+        db_snapshot_identifier: str,
+        attribute_name: str,
+        values_to_add: Optional[Dict[str, Dict[str, str]]] = None,
+        values_to_remove: Optional[Dict[str, Dict[str, str]]] = None,
+    ) -> List[Dict[str, Any]]:
+        snapshot = self.describe_db_snapshots(
+            db_instance_identifier=None, db_snapshot_identifier=db_snapshot_identifier
+        )[0]
+        attribute_present = False
+        for attribute in snapshot.attributes:
+            if attribute["AttributeName"] == attribute_name:
+                attribute_present = True
+                if values_to_add:
+                    attribute["AttributeValues"] = (
+                        values_to_add["AttributeValue"].values()
+                        + attribute["AttributeValues"]
+                    )
+                if values_to_remove:
+                    attribute["AttributeValues"] = [
+                        i
+                        for i in attribute["AttributeValues"]
+                        if i not in values_to_remove["AttributeValue"].values()
+                    ]
+        if not attribute_present and values_to_add:
+            snapshot.attributes.append(
+                {
+                    "AttributeName": attribute_name,
+                    "AttributeValues": values_to_add["AttributeValue"].values(),
+                }
+            )
+        return snapshot.attributes
+
+    def describe_db_cluster_snapshot_attributes(
+        self, db_cluster_snapshot_identifier: str
+    ) -> List[Dict[str, Any]]:
+        snapshot = self.describe_db_cluster_snapshots(
+            db_cluster_identifier=None,
+            db_snapshot_identifier=db_cluster_snapshot_identifier,
+        )[0]
+        return snapshot.attributes
+
+    def modify_db_cluster_snapshot_attribute(
+        self,
+        db_cluster_snapshot_identifier: str,
+        attribute_name: str,
+        values_to_add: Optional[Dict[str, Dict[str, str]]] = None,
+        values_to_remove: Optional[Dict[str, Dict[str, str]]] = None,
+    ) -> List[Dict[str, Any]]:
+        snapshot = self.describe_db_cluster_snapshots(
+            db_cluster_identifier=None,
+            db_snapshot_identifier=db_cluster_snapshot_identifier,
+        )[0]
+        attribute_present = False
+        for attribute in snapshot.attributes:
+            if attribute["AttributeName"] == attribute_name:
+                attribute_present = True
+                if values_to_add:
+                    attribute["AttributeValues"] = (
+                        values_to_add["AttributeValue"].values()
+                        + attribute["AttributeValues"]
+                    )
+                if values_to_remove:
+                    attribute["AttributeValues"] = [
+                        i
+                        for i in attribute["AttributeValues"]
+                        if i not in values_to_remove["AttributeValue"].values()
+                    ]
+        if not attribute_present and values_to_add:
+            snapshot.attributes.append(
+                {
+                    "AttributeName": attribute_name,
+                    "AttributeValues": values_to_add["AttributeValue"].values(),
+                }
+            )
+        return snapshot.attributes
+
 
 class OptionGroup:
     def __init__(
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index a68484f57..e3dbbe1cc 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -195,6 +195,10 @@ class RDSResponse(BaseResponse):
             "allocated_storage": self._get_param("AllocatedStorage"),
             "global_cluster_identifier": self._get_param("GlobalClusterIdentifier"),
             "iops": self._get_param("Iops"),
+            "storage_encrypted": self._get_param("StorageEncrypted"),
+            "enable_global_write_forwarding": self._get_param(
+                "EnableGlobalWriteForwarding"
+            ),
             "storage_type": self._get_param("StorageType"),
             "kms_key_id": self._get_param("KmsKeyId"),
             "master_username": self._get_param("MasterUsername"),
@@ -818,6 +822,66 @@ class RDSResponse(BaseResponse):
         template = self.response_template(PROMOTE_READ_REPLICA_DB_CLUSTER_TEMPLATE)
         return template.render(cluster=cluster)
 
+    def describe_db_snapshot_attributes(self) -> str:
+        params = self._get_params()
+        db_snapshot_identifier = params["DBSnapshotIdentifier"]
+        db_snapshot_attributes_result = self.backend.describe_db_snapshot_attributes(
+            db_snapshot_identifier=db_snapshot_identifier,
+        )
+        template = self.response_template(DESCRIBE_DB_SNAPSHOT_ATTRIBUTES_TEMPLATE)
+        return template.render(
+            db_snapshot_attributes_result=db_snapshot_attributes_result,
+            db_snapshot_identifier=db_snapshot_identifier,
+        )
+
+    def modify_db_snapshot_attribute(self) -> str:
+        params = self._get_params()
+        db_snapshot_identifier = params["DBSnapshotIdentifier"]
+        db_snapshot_attributes_result = self.backend.modify_db_snapshot_attribute(
+            db_snapshot_identifier=db_snapshot_identifier,
+            attribute_name=params["AttributeName"],
+            values_to_add=params.get("ValuesToAdd"),
+            values_to_remove=params.get("ValuesToRemove"),
+        )
+        template = self.response_template(MODIFY_DB_SNAPSHOT_ATTRIBUTE_TEMPLATE)
+        return template.render(
+            db_snapshot_attributes_result=db_snapshot_attributes_result,
+            db_snapshot_identifier=db_snapshot_identifier,
+        )
+
+    def describe_db_cluster_snapshot_attributes(self) -> str:
+        params = self._get_params()
+        db_cluster_snapshot_identifier = params["DBClusterSnapshotIdentifier"]
+        db_cluster_snapshot_attributes_result = (
+            self.backend.describe_db_cluster_snapshot_attributes(
+                db_cluster_snapshot_identifier=db_cluster_snapshot_identifier,
+            )
+        )
+        template = self.response_template(
+            DESCRIBE_DB_CLUSTER_SNAPSHOT_ATTRIBUTES_TEMPLATE
+        )
+        return template.render(
+            db_cluster_snapshot_attributes_result=db_cluster_snapshot_attributes_result,
+            db_cluster_snapshot_identifier=db_cluster_snapshot_identifier,
+        )
+
+    def modify_db_cluster_snapshot_attribute(self) -> str:
+        params = self._get_params()
+        db_cluster_snapshot_identifier = params["DBClusterSnapshotIdentifier"]
+        db_cluster_snapshot_attributes_result = (
+            self.backend.modify_db_cluster_snapshot_attribute(
+                db_cluster_snapshot_identifier=db_cluster_snapshot_identifier,
+                attribute_name=params["AttributeName"],
+                values_to_add=params.get("ValuesToAdd"),
+                values_to_remove=params.get("ValuesToRemove"),
+            )
+        )
+        template = self.response_template(MODIFY_DB_CLUSTER_SNAPSHOT_ATTRIBUTE_TEMPLATE)
+        return template.render(
+            db_cluster_snapshot_attributes_result=db_cluster_snapshot_attributes_result,
+            db_cluster_snapshot_identifier=db_cluster_snapshot_identifier,
+        )
+
 
 CREATE_DATABASE_TEMPLATE = """<CreateDBInstanceResponse xmlns="http://rds.amazonaws.com/doc/2014-09-01/">
   <CreateDBInstanceResult>
@@ -1474,3 +1538,95 @@ PROMOTE_READ_REPLICA_DB_CLUSTER_TEMPLATE = """<PromoteReadReplicaDBClusterRespon
     <RequestId>7369556f-b70d-11c3-faca-6ba18376ea1b</RequestId>
   </ResponseMetadata>
 </PromoteReadReplicaDBClusterResponse>"""
+
+DESCRIBE_DB_SNAPSHOT_ATTRIBUTES_TEMPLATE = """<DescribeDBSnapshotAttributesResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+  <DescribeDBSnapshotAttributesResult>
+    <DBSnapshotAttributesResult>
+      <DBSnapshotAttributes>
+        {%- for attribute in db_snapshot_attributes_result -%}
+          <DBSnapshotAttribute>
+            <AttributeName>{{ attribute["AttributeName"] }}</AttributeName>
+            <AttributeValues>
+              {%- for value in attribute["AttributeValues"] -%}
+                <AttributeValue>{{ value }}</AttributeValue>
+              {%- endfor -%}
+            </AttributeValues>
+          </DBSnapshotAttribute>
+        {%- endfor -%}
+      </DBSnapshotAttributes>
+      <DBSnapshotIdentifier>{{ db_snapshot_identifier }}</DBSnapshotIdentifier>
+    </DBSnapshotAttributesResult>
+  </DescribeDBSnapshotAttributesResult>
+  <ResponseMetadata>
+    <RequestId>1549581b-12b7-11e3-895e-1334a</RequestId>
+  </ResponseMetadata>
+</DescribeDBSnapshotAttributesResponse>"""
+
+MODIFY_DB_SNAPSHOT_ATTRIBUTE_TEMPLATE = """<ModifyDBSnapshotAttributeResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+  <ModifyDBSnapshotAttributeResult>
+    <DBSnapshotAttributesResult>
+      <DBSnapshotAttributes>
+        {%- for attribute in db_snapshot_attributes_result -%}
+          <DBSnapshotAttribute>
+            <AttributeName>{{ attribute["AttributeName"] }}</AttributeName>
+            <AttributeValues>
+              {%- for value in attribute["AttributeValues"] -%}
+                <AttributeValue>{{ value }}</AttributeValue>
+              {%- endfor -%}
+            </AttributeValues>
+          </DBSnapshotAttribute>
+        {%- endfor -%}
+      </DBSnapshotAttributes>
+      <DBSnapshotIdentifier>{{ db_snapshot_identifier }}</DBSnapshotIdentifier>
+    </DBSnapshotAttributesResult>
+  </ModifyDBSnapshotAttributeResult>
+  <ResponseMetadata>
+    <RequestId>1549581b-12b7-11e3-895e-1334aEXAMPLE</RequestId>
+  </ResponseMetadata>
+</ModifyDBSnapshotAttributeResponse>"""
+
+MODIFY_DB_CLUSTER_SNAPSHOT_ATTRIBUTE_TEMPLATE = """<ModifyDBClusterSnapshotAttributeResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+  <ModifyDBClusterSnapshotAttributeResult>
+    <DBClusterSnapshotAttributesResult>
+      <DBClusterSnapshotAttributes>
+        {%- for attribute in db_cluster_snapshot_attributes_result -%}
+          <DBClusterSnapshotAttribute>
+            <AttributeName>{{ attribute["AttributeName"] }}</AttributeName>
+            <AttributeValues>
+              {%- for value in attribute["AttributeValues"] -%}
+                <AttributeValue>{{ value }}</AttributeValue>
+              {%- endfor -%}
+            </AttributeValues>
+          </DBClusterSnapshotAttribute>
+        {%- endfor -%}
+      </DBClusterSnapshotAttributes>
+      <DBClusterSnapshotIdentifier>{{ db_cluster_snapshot_identifier }}</DBClusterSnapshotIdentifier>
+    </DBClusterSnapshotAttributesResult>
+  </ModifyDBClusterSnapshotAttributeResult>
+  <ResponseMetadata>
+    <RequestId>1549581b-12b7-11e3-895e-1334a</RequestId>
+  </ResponseMetadata>
+</ModifyDBClusterSnapshotAttributeResponse>"""
+
+DESCRIBE_DB_CLUSTER_SNAPSHOT_ATTRIBUTES_TEMPLATE = """<DescribeDBClusterSnapshotAttributesResponse xmlns="http://rds.amazonaws.com/doc/2014-10-31/">
+  <DescribeDBClusterSnapshotAttributesResult>
+    <DBClusterSnapshotAttributesResult>
+      <DBClusterSnapshotAttributes>
+        {%- for attribute in db_cluster_snapshot_attributes_result -%}
+          <DBClusterSnapshotAttribute>
+            <AttributeName>{{ attribute["AttributeName"] }}</AttributeName>
+            <AttributeValues>
+              {%- for value in attribute["AttributeValues"] -%}
+                <AttributeValue>{{ value }}</AttributeValue>
+              {%- endfor -%}
+            </AttributeValues>
+          </DBClusterSnapshotAttribute> 
+        {%- endfor -%}
+      </DBClusterSnapshotAttributes>
+      <DBClusterSnapshotIdentifier>{{ db_cluster_snapshot_identifier }}</DBClusterSnapshotIdentifier>
+    </DBClusterSnapshotAttributesResult>
+  </DescribeDBClusterSnapshotAttributesResult>
+  <ResponseMetadata>
+    <RequestId>1549581b-12b7-11e3-895e-1334a</RequestId>
+  </ResponseMetadata>
+</DescribeDBClusterSnapshotAttributesResponse>"""
diff --git a/moto/route53/models.py b/moto/route53/models.py
index 409d992ff..ccfd5dc4d 100644
--- a/moto/route53/models.py
+++ b/moto/route53/models.py
@@ -208,6 +208,7 @@ class RecordSet(CloudFormationModel):
         self.alias_target = kwargs.get("AliasTarget", [])
         self.failover = kwargs.get("Failover", [])
         self.geo_location = kwargs.get("GeoLocation", [])
+        self.multi_value = kwargs.get("MultiValueAnswer")
 
     @staticmethod
     def cloudformation_name_type() -> str:
diff --git a/moto/route53/responses.py b/moto/route53/responses.py
index 7227f85f5..6350c71bc 100644
--- a/moto/route53/responses.py
+++ b/moto/route53/responses.py
@@ -562,6 +562,9 @@ LIST_RRSET_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
            {% if record.failover %}
                <Failover>{{ record.failover }}</Failover>
            {% endif %}
+           {% if record.multi_value %}
+               <MultiValueAnswer>{{ record.multi_value }}</MultiValueAnswer>
+           {% endif %}
            {% if record.geo_location %}
            <GeoLocation>
            {% for geo_key in ['ContinentCode','CountryCode','SubdivisionCode'] %}


Test patch:
diff --git a/.github/workflows/tests_terraform_examples.yml b/.github/workflows/tests_terraform_examples.yml
index b1a5f7054..12790048e 100644
--- a/.github/workflows/tests_terraform_examples.yml
+++ b/.github/workflows/tests_terraform_examples.yml
@@ -33,4 +33,9 @@ jobs:
         cd other_langs/terraform/${{ matrix.service }}
         terraform init
         terraform apply --auto-approve
+        echo "Verify nothing changes when ACM certificates are validated"
+        sleep 30
+        terraform plan -detailed-exitcode
+        sleep 30
+        terraform plan -detailed-exitcode
         terraform apply -destroy --auto-approve
diff --git a/tests/test_acm/test_acm.py b/tests/test_acm/test_acm.py
index d2943d801..c6704c5ed 100644
--- a/tests/test_acm/test_acm.py
+++ b/tests/test_acm/test_acm.py
@@ -1,5 +1,6 @@
 import os
 import uuid
+from time import sleep
 from unittest import SkipTest, mock
 
 import boto3
@@ -180,7 +181,6 @@ def test_describe_certificate():
     assert len(resp["Certificate"]["DomainValidationOptions"]) == 1
     validation_option = resp["Certificate"]["DomainValidationOptions"][0]
     assert validation_option["DomainName"] == SERVER_COMMON_NAME
-    assert "ValidationDomain" not in validation_option
 
 
 @mock_acm
@@ -388,7 +388,10 @@ def test_request_certificate():
 
 
 @mock_acm
+@mock.patch("moto.settings.ACM_VALIDATION_WAIT", 1)
 def test_request_certificate_with_optional_arguments():
+    if not settings.TEST_DECORATOR_MODE:
+        raise SkipTest("Can only change setting in DecoratorMode")
     client = boto3.client("acm", region_name="eu-central-1")
 
     token = str(uuid.uuid4())
@@ -406,12 +409,21 @@ def test_request_certificate_with_optional_arguments():
     arn_1 = resp["CertificateArn"]
 
     cert = client.describe_certificate(CertificateArn=arn_1)["Certificate"]
+    assert cert["Status"] == "PENDING_VALIDATION"
     assert len(cert["SubjectAlternativeNames"]) == 3
-    assert len(cert["DomainValidationOptions"]) == 3
-    assert {option["DomainName"] for option in cert["DomainValidationOptions"]} == set(
+    validation_options = cert["DomainValidationOptions"].copy()
+    assert len(validation_options) == 3
+    assert {option["DomainName"] for option in validation_options} == set(
         cert["SubjectAlternativeNames"]
     )
 
+    # Verify SAN's are still the same, even after the Certificate is validated
+    sleep(2)
+    for opt in validation_options:
+        opt["ValidationStatus"] = "ISSUED"
+    cert = client.describe_certificate(CertificateArn=arn_1)["Certificate"]
+    assert cert["DomainValidationOptions"] == validation_options
+
     resp = client.list_tags_for_certificate(CertificateArn=arn_1)
     tags = {item["Key"]: item.get("Value", "__NONE__") for item in resp["Tags"]}
     assert len(tags) == 2
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index 5b3d75ef0..fd2551550 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -2694,6 +2694,106 @@ def test_createdb_instance_engine_with_invalid_value():
     )
 
 
+@mock_rds
+def test_describe_db_snapshot_attributes_default():
+    client = boto3.client("rds", region_name="us-east-2")
+    client.create_db_instance(
+        DBInstanceIdentifier="db-primary-1",
+        AllocatedStorage=10,
+        Engine="postgres",
+        DBName="staging-postgres",
+        DBInstanceClass="db.m1.small",
+        MasterUsername="root",
+        MasterUserPassword="hunter2",
+        Port=1234,
+        DBSecurityGroups=["my_sg"],
+    )
+
+    client.create_db_snapshot(
+        DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
+    ).get("DBSnapshot")
+
+    resp = client.describe_db_snapshot_attributes(DBSnapshotIdentifier="snapshot-1")
+
+    assert resp["DBSnapshotAttributesResult"]["DBSnapshotIdentifier"] == "snapshot-1"
+    assert resp["DBSnapshotAttributesResult"]["DBSnapshotAttributes"] == []
+
+
+@mock_rds
+def test_describe_db_snapshot_attributes():
+    client = boto3.client("rds", region_name="us-east-2")
+    client.create_db_instance(
+        DBInstanceIdentifier="db-primary-1",
+        AllocatedStorage=10,
+        Engine="postgres",
+        DBName="staging-postgres",
+        DBInstanceClass="db.m1.small",
+        MasterUsername="root",
+        MasterUserPassword="hunter2",
+        Port=1234,
+        DBSecurityGroups=["my_sg"],
+    )
+
+    client.create_db_snapshot(
+        DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
+    ).get("DBSnapshot")
+
+    resp = client.modify_db_snapshot_attribute(
+        DBSnapshotIdentifier="snapshot-1",
+        AttributeName="restore",
+        ValuesToAdd=["Test", "Test2"],
+    )
+
+    resp = client.describe_db_snapshot_attributes(DBSnapshotIdentifier="snapshot-1")
+
+    assert (
+        resp["DBSnapshotAttributesResult"]["DBSnapshotAttributes"][0]["AttributeName"]
+        == "restore"
+    )
+    assert resp["DBSnapshotAttributesResult"]["DBSnapshotAttributes"][0][
+        "AttributeValues"
+    ] == ["Test", "Test2"]
+
+
+@mock_rds
+def test_modify_db_snapshot_attribute():
+    client = boto3.client("rds", region_name="us-east-2")
+    client.create_db_instance(
+        DBInstanceIdentifier="db-primary-1",
+        AllocatedStorage=10,
+        Engine="postgres",
+        DBName="staging-postgres",
+        DBInstanceClass="db.m1.small",
+        MasterUsername="root",
+        MasterUserPassword="hunter2",
+        Port=1234,
+        DBSecurityGroups=["my_sg"],
+    )
+
+    client.create_db_snapshot(
+        DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
+    ).get("DBSnapshot")
+
+    resp = client.modify_db_snapshot_attribute(
+        DBSnapshotIdentifier="snapshot-1",
+        AttributeName="restore",
+        ValuesToAdd=["Test", "Test2"],
+    )
+    resp = client.modify_db_snapshot_attribute(
+        DBSnapshotIdentifier="snapshot-1",
+        AttributeName="restore",
+        ValuesToRemove=["Test"],
+    )
+
+    assert (
+        resp["DBSnapshotAttributesResult"]["DBSnapshotAttributes"][0]["AttributeName"]
+        == "restore"
+    )
+    assert resp["DBSnapshotAttributesResult"]["DBSnapshotAttributes"][0][
+        "AttributeValues"
+    ] == ["Test2"]
+
+
 def validation_helper(exc):
     err = exc.value.response["Error"]
     assert err["Code"] == "InvalidParameterValue"
diff --git a/tests/test_rds/test_rds_clusters.py b/tests/test_rds/test_rds_clusters.py
index d1f6a702c..14dde421d 100644
--- a/tests/test_rds/test_rds_clusters.py
+++ b/tests/test_rds/test_rds_clusters.py
@@ -215,6 +215,8 @@ def test_create_db_cluster__verify_default_properties():
     assert cluster["TagList"] == []
     assert "ClusterCreateTime" in cluster
     assert cluster["EarliestRestorableTime"] >= cluster["ClusterCreateTime"]
+    assert cluster["StorageEncrypted"] is False
+    assert cluster["GlobalWriteForwardingRequested"] is False
 
 
 @mock_rds
@@ -236,6 +238,8 @@ def test_create_db_cluster_additional_parameters():
         KmsKeyId="some:kms:arn",
         NetworkType="IPV4",
         DBSubnetGroupName="subnetgroupname",
+        StorageEncrypted=True,
+        EnableGlobalWriteForwarding=True,
         ScalingConfiguration={
             "MinCapacity": 5,
             "AutoPause": True,
@@ -260,6 +264,8 @@ def test_create_db_cluster_additional_parameters():
     assert cluster["KmsKeyId"] == "some:kms:arn"
     assert cluster["NetworkType"] == "IPV4"
     assert cluster["DBSubnetGroup"] == "subnetgroupname"
+    assert cluster["StorageEncrypted"] is True
+    assert cluster["GlobalWriteForwardingRequested"] is True
     assert cluster["ScalingConfigurationInfo"] == {"MinCapacity": 5, "AutoPause": True}
     assert cluster["ServerlessV2ScalingConfiguration"] == {
         "MaxCapacity": 4.0,
@@ -977,3 +983,120 @@ def test_createdb_instance_engine_mismatch_fail():
         == "The engine name requested for your DB instance (mysql) doesn't match "
         "the engine name of your DB cluster (aurora-postgresql)."
     )
+
+
+@mock_rds
+def test_describe_db_cluster_snapshot_attributes_default():
+    conn = boto3.client("rds", region_name="us-west-2")
+    conn.create_db_cluster(
+        DBClusterIdentifier="db-primary-1",
+        AllocatedStorage=10,
+        Engine="postgres",
+        DatabaseName="staging-postgres",
+        DBClusterInstanceClass="db.m1.small",
+        MasterUsername="root",
+        MasterUserPassword="hunter2000",
+        Port=1234,
+    )
+
+    conn.create_db_cluster_snapshot(
+        DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="g-1"
+    ).get("DBClusterSnapshot")
+
+    resp = conn.describe_db_cluster_snapshot_attributes(
+        DBClusterSnapshotIdentifier="g-1"
+    )
+
+    assert (
+        resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotIdentifier"]
+        == "g-1"
+    )
+    assert (
+        resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotAttributes"] == []
+    )
+
+
+@mock_rds
+def test_describe_db_cluster_snapshot_attributes():
+    conn = boto3.client("rds", region_name="us-west-2")
+    conn.create_db_cluster(
+        DBClusterIdentifier="db-primary-1",
+        AllocatedStorage=10,
+        Engine="postgres",
+        DatabaseName="staging-postgres",
+        DBClusterInstanceClass="db.m1.small",
+        MasterUsername="root",
+        MasterUserPassword="hunter2000",
+        Port=1234,
+    )
+
+    conn.create_db_cluster_snapshot(
+        DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="g-1"
+    ).get("DBClusterSnapshot")
+
+    conn.modify_db_cluster_snapshot_attribute(
+        DBClusterSnapshotIdentifier="g-1",
+        AttributeName="restore",
+        ValuesToAdd=["test", "test2"],
+    )
+
+    resp = conn.describe_db_cluster_snapshot_attributes(
+        DBClusterSnapshotIdentifier="g-1"
+    )
+
+    assert (
+        resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotIdentifier"]
+        == "g-1"
+    )
+    assert (
+        resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotAttributes"][0][
+            "AttributeName"
+        ]
+        == "restore"
+    )
+    assert resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotAttributes"][0][
+        "AttributeValues"
+    ] == ["test", "test2"]
+
+
+@mock_rds
+def test_modify_db_cluster_snapshot_attribute():
+    conn = boto3.client("rds", region_name="us-west-2")
+    conn.create_db_cluster(
+        DBClusterIdentifier="db-primary-1",
+        AllocatedStorage=10,
+        Engine="postgres",
+        DatabaseName="staging-postgres",
+        DBClusterInstanceClass="db.m1.small",
+        MasterUsername="root",
+        MasterUserPassword="hunter2000",
+        Port=1234,
+    )
+
+    conn.create_db_cluster_snapshot(
+        DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="g-1"
+    ).get("DBClusterSnapshot")
+
+    resp = conn.modify_db_cluster_snapshot_attribute(
+        DBClusterSnapshotIdentifier="g-1",
+        AttributeName="restore",
+        ValuesToAdd=["test", "test2"],
+    )
+    resp = conn.modify_db_cluster_snapshot_attribute(
+        DBClusterSnapshotIdentifier="g-1",
+        AttributeName="restore",
+        ValuesToRemove=["test"],
+    )
+    assert (
+        resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotIdentifier"]
+        == "g-1"
+    )
+    assert (
+        resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotAttributes"][0][
+            "AttributeName"
+        ]
+        == "restore"
+    )
+    assert resp["DBClusterSnapshotAttributesResult"]["DBClusterSnapshotAttributes"][0][
+        "AttributeValues"
+    ] == ["test2"]
diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py
index 5838cc5a1..149e34958 100644
--- a/tests/test_route53/test_route53.py
+++ b/tests/test_route53/test_route53.py
@@ -809,6 +809,7 @@ def test_change_resource_record_sets_crud_valid_with_special_xml_chars(
     assert cname_record_detail["ResourceRecords"] == [
         {"Value": "SomeInitialValue&NewValue"}
     ]
+    assert cname_record_detail.get("MultiValueAnswer") == multi_value_answer
 
     # Delete record.
     delete_payload = {