Repository: getmoto/moto (python)

Issue:
KMS get_public_key fails with alias
Hi, 

The following test, trying to use `get_public_key` with an alias is failing:

```python
import boto3
from moto import mock_aws

@mock_aws
def test():
    kms = boto3.client("kms", region_name="eu-west-1")
    key = kms.create_key(
        KeyUsage="SIGN_VERIFY",
        KeySpec="ECC_NIST_P256",
    )
    key_id = key["KeyMetadata"]["KeyId"]
    kms.create_alias(
        AliasName="alias/foo",
        TargetKeyId=key_id,
    )
    kms.get_public_key(KeyId="alias/foo")
```
raises the following exception:
```
/Users/xxx/Library/Caches/pypoetry/virtualenvs/motoreport-c38uzrXm-py3.11/bin/python /Users/xxx/PycharmProjects/cebidhem/motoReport/report/main.py 
Traceback (most recent call last):
  File "/Users/xxx/PycharmProjects/cebidhem/motoReport/report/main.py", line 18, in main
    kms.get_public_key(KeyId="alias/foo")
  File "/Users/xxx/Library/Caches/pypoetry/virtualenvs/motoreport-c38uzrXm-py3.11/lib/python3.11/site-packages/botocore/client.py", line 569, in _api_call
    return self._make_api_call(operation_name, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxx/Library/Caches/pypoetry/virtualenvs/motoreport-c38uzrXm-py3.11/lib/python3.11/site-packages/botocore/client.py", line 1023, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.NotFoundException: An error occurred (NotFoundException) when calling the GetPublicKey operation: Invalid keyId alias/foo
```

Somehow, the alias isn't recognized as an alias or it doesn't get translated as the actual keyId.

The only place where I can find the error `Invalid keyId ` is in the `_validate_cmk_id(self, key_id: str)`, which seems weird considering I try to use an alias.

I'm using: 
- Python 3.11.1
- moto 5.0.22
- boto3 1.35.75

I can also confirm that it works normally outside of moto with real AWS calls.


Pull request:
fix(kms): get_public_key now works with alias (name and arn)
Fixes #8364 

The error provided was coming from the function `_validate_cmk_id()` which obviously raises an error when used with an alias, and if I understood correctly especially since `describe_key()` is using ` any_id_to_key_id()`.

Tests are passing and none seem to be broken.

Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 1ed94cd6e..650962035 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -1956,7 +1956,7 @@
 
 ## directconnect
 <details>
-<summary>6% implemented</summary>
+<summary>12% implemented</summary>
 
 - [ ] accept_direct_connect_gateway_association_proposal
 - [ ] allocate_connection_on_interconnect
@@ -1966,7 +1966,7 @@
 - [ ] allocate_transit_virtual_interface
 - [ ] associate_connection_with_lag
 - [ ] associate_hosted_connection
-- [ ] associate_mac_sec_key
+- [x] associate_mac_sec_key
 - [ ] associate_virtual_interface
 - [ ] confirm_connection
 - [ ] confirm_customer_agreement
@@ -1979,7 +1979,7 @@
 - [ ] create_direct_connect_gateway_association
 - [ ] create_direct_connect_gateway_association_proposal
 - [ ] create_interconnect
-- [ ] create_lag
+- [x] create_lag
 - [ ] create_private_virtual_interface
 - [ ] create_public_virtual_interface
 - [ ] create_transit_virtual_interface
@@ -2002,7 +2002,7 @@
 - [ ] describe_hosted_connections
 - [ ] describe_interconnect_loa
 - [ ] describe_interconnects
-- [ ] describe_lags
+- [x] describe_lags
 - [ ] describe_loa
 - [ ] describe_locations
 - [ ] describe_router_configuration
@@ -2010,7 +2010,7 @@
 - [ ] describe_virtual_gateways
 - [ ] describe_virtual_interfaces
 - [ ] disassociate_connection_from_lag
-- [ ] disassociate_mac_sec_key
+- [x] disassociate_mac_sec_key
 - [ ] list_virtual_interface_test_history
 - [ ] start_bgp_failover_test
 - [ ] stop_bgp_failover_test
diff --git a/docs/docs/services/directconnect.rst b/docs/docs/services/directconnect.rst
index 5339e3942..b0f2318e0 100644
--- a/docs/docs/services/directconnect.rst
+++ b/docs/docs/services/directconnect.rst
@@ -24,7 +24,7 @@ directconnect
 - [ ] allocate_transit_virtual_interface
 - [ ] associate_connection_with_lag
 - [ ] associate_hosted_connection
-- [ ] associate_mac_sec_key
+- [x] associate_mac_sec_key
 - [ ] associate_virtual_interface
 - [ ] confirm_connection
 - [ ] confirm_customer_agreement
@@ -37,7 +37,7 @@ directconnect
 - [ ] create_direct_connect_gateway_association
 - [ ] create_direct_connect_gateway_association_proposal
 - [ ] create_interconnect
-- [ ] create_lag
+- [x] create_lag
 - [ ] create_private_virtual_interface
 - [ ] create_public_virtual_interface
 - [ ] create_transit_virtual_interface
@@ -60,7 +60,7 @@ directconnect
 - [ ] describe_hosted_connections
 - [ ] describe_interconnect_loa
 - [ ] describe_interconnects
-- [ ] describe_lags
+- [x] describe_lags
 - [ ] describe_loa
 - [ ] describe_locations
 - [ ] describe_router_configuration
@@ -68,7 +68,7 @@ directconnect
 - [ ] describe_virtual_gateways
 - [ ] describe_virtual_interfaces
 - [ ] disassociate_connection_from_lag
-- [ ] disassociate_mac_sec_key
+- [x] disassociate_mac_sec_key
 - [ ] list_virtual_interface_test_history
 - [ ] start_bgp_failover_test
 - [ ] stop_bgp_failover_test
diff --git a/moto/kms/responses.py b/moto/kms/responses.py
index b68ec20b0..7f5b9b674 100644
--- a/moto/kms/responses.py
+++ b/moto/kms/responses.py
@@ -707,7 +707,6 @@ class KmsResponse(BaseResponse):
         key_id = self._get_param("KeyId")
 
         self._validate_key_id(key_id)
-        self._validate_cmk_id(key_id)
         key, public_key = self.kms_backend.get_public_key(key_id)
         return json.dumps(
             {
diff --git a/moto/rds/models.py b/moto/rds/models.py
index 51229c102..a65d69ad3 100644
--- a/moto/rds/models.py
+++ b/moto/rds/models.py
@@ -1838,15 +1838,19 @@ class RDSBackend(BaseBackend):
         source_snapshot_identifier: str,
         target_snapshot_identifier: str,
         tags: Optional[List[Dict[str, str]]] = None,
+        copy_tags: bool = False,
     ) -> DBSnapshot:
         if source_snapshot_identifier not in self.database_snapshots:
             raise DBSnapshotNotFoundError(source_snapshot_identifier)
 
         source_snapshot = self.database_snapshots[source_snapshot_identifier]
-        if tags is None:
-            tags = source_snapshot.tags
-        else:
-            tags = self._merge_tags(source_snapshot.tags, tags)
+
+        # When tags are passed, AWS does NOT copy/merge tags of the
+        # source snapshot, even when copy_tags=True is given.
+        # But when tags=[], AWS does honor copy_tags=True.
+        if not tags:
+            tags = source_snapshot.tags if copy_tags else []
+
         return self.create_db_snapshot(
             db_instance=source_snapshot.database,
             db_snapshot_identifier=target_snapshot_identifier,
diff --git a/moto/rds/responses.py b/moto/rds/responses.py
index 7b8911369..fabafe7fa 100644
--- a/moto/rds/responses.py
+++ b/moto/rds/responses.py
@@ -303,8 +303,9 @@ class RDSResponse(BaseResponse):
         source_snapshot_identifier = self._get_param("SourceDBSnapshotIdentifier")
         target_snapshot_identifier = self._get_param("TargetDBSnapshotIdentifier")
         tags = self.unpack_list_params("Tags", "Tag")
+        copy_tags = self._get_param("CopyTags")
         snapshot = self.backend.copy_db_snapshot(
-            source_snapshot_identifier, target_snapshot_identifier, tags
+            source_snapshot_identifier, target_snapshot_identifier, tags, copy_tags
         )
         template = self.response_template(COPY_SNAPSHOT_TEMPLATE)
         return template.render(snapshot=snapshot)


Test patch:
diff --git a/tests/test_kms/test_kms_boto3.py b/tests/test_kms/test_kms_boto3.py
index d1ccaf402..bafccb20f 100644
--- a/tests/test_kms/test_kms_boto3.py
+++ b/tests/test_kms/test_kms_boto3.py
@@ -1692,6 +1692,34 @@ def test_get_public_key():
     assert "EncryptionAlgorithms" not in public_key_response
 
 
+@mock_aws
+def test_get_public_key_with_alias():
+    client = boto3.client("kms", region_name="eu-west-1")
+    key = client.create_key(KeyUsage="SIGN_VERIFY", KeySpec="RSA_2048")
+    key_id = key["KeyMetadata"]["KeyId"]
+    client.create_alias(
+        AliasName="alias/foo",
+        TargetKeyId=key_id,
+    )
+    alias_arn = client.list_aliases()["Aliases"][0]["AliasArn"]
+
+    public_key_response_with_name = client.get_public_key(KeyId="alias/foo")
+    public_key_response_with_arn = client.get_public_key(KeyId=alias_arn)
+
+    assert "PublicKey" in public_key_response_with_name
+    assert "PublicKey" in public_key_response_with_arn
+    assert (
+        public_key_response_with_name["SigningAlgorithms"]
+        == key["KeyMetadata"]["SigningAlgorithms"]
+    )
+    assert (
+        public_key_response_with_arn["SigningAlgorithms"]
+        == key["KeyMetadata"]["SigningAlgorithms"]
+    )
+    assert "EncryptionAlgorithms" not in public_key_response_with_name
+    assert "EncryptionAlgorithms" not in public_key_response_with_arn
+
+
 def create_simple_key(client, id_or_arn="KeyId", description=None, policy=None):
     with mock.patch.object(rsa, "generate_private_key", return_value=""):
         params = {}
diff --git a/tests/test_rds/test_rds.py b/tests/test_rds/test_rds.py
index 68913255f..7da065ace 100644
--- a/tests/test_rds/test_rds.py
+++ b/tests/test_rds/test_rds.py
@@ -1076,6 +1076,55 @@ def test_copy_db_snapshots(delete_db_instance: bool):
     assert result["TagList"] == []
 
 
+original_snapshot_tags = [{"Key": "original", "Value": "snapshot tags"}]
+new_snapshot_tags = [{"Key": "new", "Value": "tag"}]
+
+
+@pytest.mark.parametrize(
+    "kwargs,expected_tags",
+    [
+        # No Tags parameter, CopyTags defaults to False -> no tags
+        ({}, []),
+        # No Tags parameter, CopyTags set to True -> use tags of original snapshot
+        ({"CopyTags": True}, original_snapshot_tags),
+        # When "Tags" are given, they become the only tags of the snapshot.
+        ({"Tags": new_snapshot_tags}, new_snapshot_tags),
+        # When "Tags" are given, they become the only tags of the snapshot. Even if CopyTags is True!
+        ({"Tags": new_snapshot_tags, "CopyTags": True}, new_snapshot_tags),
+        # When "Tags" are given but empty, CopyTags=True takes effect again!
+        ({"Tags": [], "CopyTags": True}, original_snapshot_tags),
+    ],
+    ids=(
+        "no_parameters",
+        "copytags_true",
+        "only_tags",
+        "copytags_true_and_tags",
+        "copytags_true_and_empty_tags",
+    ),
+)
+@mock_aws
+def test_copy_db_snapshots_copytags_and_tags(kwargs, expected_tags):
+    conn = boto3.client("rds", region_name=DEFAULT_REGION)
+    conn.create_db_instance(
+        DBInstanceIdentifier="db-primary-1",
+        Engine="postgres",
+        DBInstanceClass="db.m1.small",
+    )
+    conn.create_db_snapshot(
+        DBInstanceIdentifier="db-primary-1",
+        DBSnapshotIdentifier="snapshot",
+        Tags=original_snapshot_tags,
+    )
+
+    target_snapshot = conn.copy_db_snapshot(
+        SourceDBSnapshotIdentifier="snapshot",
+        TargetDBSnapshotIdentifier="snapshot-copy",
+        **kwargs,
+    ).get("DBSnapshot")
+    result = conn.list_tags_for_resource(ResourceName=target_snapshot["DBSnapshotArn"])
+    assert result["TagList"] == expected_tags
+
+
 @mock_aws
 def test_describe_db_snapshots():
     conn = boto3.client("rds", region_name=DEFAULT_REGION)