Repository: getmoto/moto (python)

Issue:
implementation_coverage.py script misses covered API calls
I saw that the implementation coverage for KMS reports that some alias methods are not supported:

```
## kms
<details>
<summary>56% implemented</summary>
# ...
- [ ] create_alias
# ...
- [X] delete_alias
# ...
- [ ] list_aliases
# ...
- [ ] update_alias
```

When I checked the actual implementation, those methods all seem to be supported, so I looked into `scripts/implementation_coverage.py`. It appears that the method names on the service backend are used to determine if a given method exists.

```python

from moto.backends import get_backend

# ...

def get_moto_implementation(service_name):
    try:
        backends = get_backend(service_name)
        backend = (
            backends[0]["us-east-1"]
            if "us-east-1" in backends[0]
            else backends[0]["global"]
        )
        return backend, service_name
    except ModuleNotFoundError:
        return None, service_name

def calculate_extended_implementation_coverage():
        # ...
        moto_client, mock_name = get_moto_implementation(service_name)
        if not moto_client:
            continue
        real_client = boto3.client(service_name, region_name="us-east-1")
        implemented = dict()
        not_implemented = []

        operation_names = [
            xform_name(op) for op in real_client.meta.service_model.operation_names
        ]
        # Not part of the spec - but very much part of S3
        if service_name == "s3":
            operation_names.extend(["upload_file", "upload_fileobj"])

        for op in operation_names:
            if moto_client and op in dir(moto_client):
                implemented[op] = getattr(moto_client, op)
            else:
                not_implemented.append(op)
```

In case of KMS the issue is, that the method names on the `KmsBackend` don't match the API names, i.e.:

|**boto3 Name**|**KmsBackend method**|**KmsResponse method**
|---|---|---|
|create_alias|add_alias|create_alias|
|delete_alias|delete_alias|delete_alias|
|list_aliases|get_all_aliases|list_aliases|
|update_alias| - |update_alias|

It seems like the `KmsResponse` is more reliable in this case.

Maybe similar to: https://github.com/getmoto/moto/issues/3599

I'd be willing to try and fix it, but I'm not sure how to proceed...
(I could just update the method names and refactor KmsBackend a bit, but this may be a more widespread issue)


Pull request:
KMS: Update ImplementationCoverage; rename internal alias methods
Fixes #7937 

Patch:
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index a621e2919..b42e4b90b 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -4773,11 +4773,11 @@
 
 ## kms
 <details>
-<summary>56% implemented</summary>
+<summary>64% implemented</summary>
 
 - [X] cancel_key_deletion
 - [ ] connect_custom_key_store
-- [ ] create_alias
+- [X] create_alias
 - [ ] create_custom_key_store
 - [X] create_grant
 - [X] create_key
@@ -4797,7 +4797,7 @@
 - [X] generate_data_key
 - [ ] generate_data_key_pair
 - [ ] generate_data_key_pair_without_plaintext
-- [ ] generate_data_key_without_plaintext
+- [X] generate_data_key_without_plaintext
 - [ ] generate_mac
 - [ ] generate_random
 - [X] get_key_policy
@@ -4805,7 +4805,7 @@
 - [ ] get_parameters_for_import
 - [X] get_public_key
 - [ ] import_key_material
-- [ ] list_aliases
+- [X] list_aliases
 - [X] list_grants
 - [ ] list_key_policies
 - [ ] list_key_rotations
@@ -4822,7 +4822,7 @@
 - [X] sign
 - [X] tag_resource
 - [X] untag_resource
-- [ ] update_alias
+- [X] update_alias
 - [ ] update_custom_key_store
 - [X] update_key_description
 - [ ] update_primary_region
diff --git a/docs/docs/services/kms.rst b/docs/docs/services/kms.rst
index d1a774a34..4294fddcd 100644
--- a/docs/docs/services/kms.rst
+++ b/docs/docs/services/kms.rst
@@ -16,7 +16,7 @@ kms
 
 - [X] cancel_key_deletion
 - [ ] connect_custom_key_store
-- [ ] create_alias
+- [X] create_alias
 - [ ] create_custom_key_store
 - [X] create_grant
 - [X] create_key
@@ -47,7 +47,7 @@ kms
 - [X] generate_data_key
 - [ ] generate_data_key_pair
 - [ ] generate_data_key_pair_without_plaintext
-- [ ] generate_data_key_without_plaintext
+- [X] generate_data_key_without_plaintext
 - [ ] generate_mac
 - [ ] generate_random
 - [X] get_key_policy
@@ -55,7 +55,7 @@ kms
 - [ ] get_parameters_for_import
 - [X] get_public_key
 - [ ] import_key_material
-- [ ] list_aliases
+- [X] list_aliases
 - [X] list_grants
 - [ ] list_key_policies
 - [ ] list_key_rotations
@@ -78,7 +78,7 @@ kms
 
 - [X] tag_resource
 - [X] untag_resource
-- [ ] update_alias
+- [X] update_alias
 - [ ] update_custom_key_store
 - [X] update_key_description
 - [ ] update_primary_region
diff --git a/moto/dynamodb/models/table.py b/moto/dynamodb/models/table.py
index 244fb2fa4..8e8d9f217 100644
--- a/moto/dynamodb/models/table.py
+++ b/moto/dynamodb/models/table.py
@@ -324,7 +324,7 @@ class Table(CloudFormationModel):
                 description="Default master key that protects my DynamoDB table storage",
                 tags=None,
             )
-            kms.add_alias(key.id, ddb_alias)
+            kms.create_alias(key.id, ddb_alias)
         ebs_key = kms.describe_key(ddb_alias)
         return ebs_key.arn
 
diff --git a/moto/ec2/models/elastic_block_store.py b/moto/ec2/models/elastic_block_store.py
index d820401f9..10b87bf26 100644
--- a/moto/ec2/models/elastic_block_store.py
+++ b/moto/ec2/models/elastic_block_store.py
@@ -547,6 +547,6 @@ class EBSBackend:
                 description="Default master key that protects my EBS volumes when no other key is defined",
                 tags=None,
             )
-            kms.add_alias(key.id, ebs_alias)
+            kms.create_alias(key.id, ebs_alias)
         ebs_key = kms.describe_key(ebs_alias)
         return ebs_key.arn
diff --git a/moto/ec2/models/security_groups.py b/moto/ec2/models/security_groups.py
index 869c47d98..eac8cf5f2 100644
--- a/moto/ec2/models/security_groups.py
+++ b/moto/ec2/models/security_groups.py
@@ -517,6 +517,49 @@ class SecurityGroup(TaggedEC2Resource, CloudFormationModel):
             len(rule.ip_ranges) + len(rule.source_groups) for rule in self.egress_rules
         )
 
+    @property
+    def flattened_ingress_rules(self) -> List[SecurityRule]:
+        return self._flattened_rules(copy.copy(self.ingress_rules))
+
+    @property
+    def flattened_egress_rules(self) -> List[SecurityRule]:
+        return self._flattened_rules(copy.copy(self.egress_rules))
+
+    def _flattened_rules(self, rules: List[SecurityRule]) -> List[SecurityRule]:
+        rules_to_return: List[SecurityRule] = []
+        for rule in rules:
+            for already_added in rules_to_return:
+                if (
+                    already_added.from_port == rule.from_port
+                    and already_added.to_port == rule.to_port
+                    and already_added.ip_protocol == rule.ip_protocol
+                ):
+                    already_added.ip_ranges.extend(
+                        [
+                            item
+                            for item in rule.ip_ranges
+                            if item not in already_added.ip_ranges
+                        ]
+                    )
+                    already_added.source_groups.extend(
+                        [
+                            item
+                            for item in rule.source_groups
+                            if item not in already_added.source_groups
+                        ]
+                    )
+                    already_added.prefix_list_ids.extend(
+                        [
+                            item
+                            for item in rule.prefix_list_ids
+                            if item not in already_added.prefix_list_ids
+                        ]
+                    )
+                    break
+            else:
+                rules_to_return.append(rule)
+        return rules_to_return
+
 
 class SecurityGroupBackend:
     def __init__(self) -> None:
@@ -767,6 +810,8 @@ class SecurityGroupBackend:
                 security_rule.from_port == rule.from_port
                 and security_rule.to_port == rule.to_port
                 and security_rule.ip_protocol == rule.ip_protocol
+                and security_rule.ip_ranges == rule.ip_ranges
+                and security_rule.prefix_list_ids == rule.prefix_list_ids
             ):
                 rule.ip_ranges.extend(
                     [
@@ -941,6 +986,8 @@ class SecurityGroupBackend:
                 security_rule.from_port == rule.from_port
                 and security_rule.to_port == rule.to_port
                 and security_rule.ip_protocol == rule.ip_protocol
+                and security_rule.ip_ranges == rule.ip_ranges
+                and security_rule.prefix_list_ids == rule.prefix_list_ids
             ):
                 rule.ip_ranges.extend(
                     [
diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py
index cf1db0b2b..d71b88b9d 100644
--- a/moto/ec2/responses/security_groups.py
+++ b/moto/ec2/responses/security_groups.py
@@ -280,13 +280,19 @@ DESCRIBE_SECURITY_GROUP_RULES_RESPONSE = """
             <item>
                 <fromPort>{{ rule.from_port if rule.from_port is not none else -1 }}</fromPort>
                 <toPort>{{ rule.to_port if rule.to_port is not none else -1 }}</toPort>
-                <cidrIpv4>{{ ip_range['CidrIp'] }}</cidrIpv4>
+                {% if ip_range['CidrIp'] %}<cidrIpv4>{{ ip_range['CidrIp'] }}</cidrIpv4>{% endif %}
+                {% if ip_range['CidrIpv6'] %}<cidrIpv6>{{ ip_range['CidrIpv6'] }}</cidrIpv6>{% endif %}
                 <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
                 <groupId>{{ rule.group_id }}</groupId>
                 <groupOwnerId>{{ rule.owner_id }}</groupOwnerId>
-                <description>{{ ip_range['Description'] }}</description>
+                {% if ip_range['Description'] %}<description>{{ ip_range['Description'] }}</description>{% endif %}
                 <isEgress>{{ 'true' if rule.is_egress else 'false' }}</isEgress>
                 <securityGroupRuleId>{{ rule.id }}</securityGroupRuleId>
+                {% if (rule.source_groups | length) > 0 %}
+                <referencedGroupInfo>
+                  <groupId>{{ rule.source_groups[0]['GroupId'] }}</groupId><userId>{{ rule.source_groups[0]['OwnerId'] }}</userId>
+                </referencedGroupInfo>
+                {% endif %}
                 <tagSet>
                 {% for tag in rule.get_tags() %}
                     <item>
@@ -297,6 +303,31 @@ DESCRIBE_SECURITY_GROUP_RULES_RESPONSE = """
                 </tagSet> 
             </item>
           {% endfor %}
+          {% if rule.ip_ranges | length == 0 %}
+            <item>
+                <fromPort>{{ rule.from_port if rule.from_port is not none else -1 }}</fromPort>
+                <toPort>{{ rule.to_port if rule.to_port is not none else -1 }}</toPort>
+                <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
+                {% if rule.prefix_list_ids | length > 0 %}<prefixListId>{{ rule.prefix_list_ids[0]['PrefixListId'] }}</prefixListId>{% endif %}
+                <groupId>{{ rule.group_id }}</groupId>
+                <groupOwnerId>{{ rule.owner_id }}</groupOwnerId>
+                <isEgress>{{ 'true' if rule.is_egress else 'false' }}</isEgress>
+                <securityGroupRuleId>{{ rule.id }}</securityGroupRuleId>
+                {% if (rule.source_groups | length) > 0 %}
+                <referencedGroupInfo>
+                  <groupId>{{ rule.source_groups[0]['GroupId'] }}</groupId><userId>{{ rule.source_groups[0]['OwnerId'] }}</userId>
+                </referencedGroupInfo>
+                {% endif %}
+                <tagSet>
+                {% for tag in rule.get_tags() %}
+                    <item>
+                      <key>{{ tag.key }}</key>
+                      <value>{{ tag.value }}</value>
+                    </item>
+                {% endfor %}
+                </tagSet> 
+            </item>
+          {% endif %}
         {% endfor %}
   </securityGroupRuleSet>
 </DescribeSecurityGroupRulesResponse>"""
@@ -319,7 +350,7 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="ht
              <vpcId>{{ group.vpc_id }}</vpcId>
              {% endif %}
              <ipPermissions>
-               {% for rule in group.ingress_rules %}
+               {% for rule in group.flattened_ingress_rules %}
                     <item>
                        <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
                        {% if rule.from_port is not none %}
@@ -384,7 +415,7 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="ht
                 {% endfor %}
              </ipPermissions>
              <ipPermissionsEgress>
-               {% for rule in group.egress_rules %}
+               {% for rule in group.flattened_egress_rules %}
                     <item>
                        <ipProtocol>{{ rule.ip_protocol }}</ipProtocol>
                        {% if rule.from_port is not none %}
diff --git a/moto/kms/models.py b/moto/kms/models.py
index aed7cec72..9f35cd893 100644
--- a/moto/kms/models.py
+++ b/moto/kms/models.py
@@ -288,7 +288,7 @@ class KmsBackend(BaseBackend):
                 "Default key",
                 None,
             )
-            self.add_alias(key.id, alias_name)
+            self.create_alias(key.id, alias_name)
             return key.id
         return None
 
@@ -430,17 +430,20 @@ class KmsBackend(BaseBackend):
 
         return False
 
-    def add_alias(self, target_key_id: str, alias_name: str) -> None:
+    def create_alias(self, target_key_id: str, alias_name: str) -> None:
         raw_key_id = self.get_key_id(target_key_id)
         self.key_to_aliases[raw_key_id].add(alias_name)
 
+    def update_alias(self, target_key_id: str, alias_name: str) -> None:
+        self.create_alias(target_key_id, alias_name)
+
     def delete_alias(self, alias_name: str) -> None:
         """Delete the alias."""
         for aliases in self.key_to_aliases.values():
             if alias_name in aliases:
                 aliases.remove(alias_name)
 
-    def get_all_aliases(self) -> Dict[str, Set[str]]:
+    def list_aliases(self) -> Dict[str, Set[str]]:
         return self.key_to_aliases
 
     def get_key_id_from_alias(self, alias_name: str) -> Optional[str]:
@@ -558,6 +561,11 @@ class KmsBackend(BaseBackend):
 
         return plaintext, ciphertext_blob, arn
 
+    def generate_data_key_without_plaintext(self) -> None:
+        # Marker to indicate this is implemented
+        # Responses uses 'generate_data_key'
+        pass
+
     def list_resource_tags(self, key_id_or_arn: str) -> Dict[str, List[Dict[str, str]]]:
         key_id = self.get_key_id(key_id_or_arn)
         if key_id in self.keys:
diff --git a/moto/kms/responses.py b/moto/kms/responses.py
index 506a9404f..b68ec20b0 100644
--- a/moto/kms/responses.py
+++ b/moto/kms/responses.py
@@ -246,7 +246,10 @@ class KmsResponse(BaseResponse):
             )
 
         self._validate_cmk_id(target_key_id)
-        self.kms_backend.add_alias(target_key_id, alias_name)
+        if update:
+            self.kms_backend.update_alias(target_key_id, alias_name)
+        else:
+            self.kms_backend.create_alias(target_key_id, alias_name)
 
         return json.dumps(None)
 
@@ -273,7 +276,7 @@ class KmsResponse(BaseResponse):
 
         response_aliases = []
 
-        backend_aliases = self.kms_backend.get_all_aliases()
+        backend_aliases = self.kms_backend.list_aliases()
         for target_key_id, aliases in backend_aliases.items():
             for alias_name in aliases:
                 # TODO: add creation date and last updated in response_aliases


Test patch:
diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py
index 73a371e1b..519b5c2c6 100644
--- a/tests/test_ec2/test_security_groups.py
+++ b/tests/test_ec2/test_security_groups.py
@@ -12,6 +12,7 @@ from botocore.exceptions import ClientError
 from moto import mock_aws, settings
 from moto.core import DEFAULT_ACCOUNT_ID
 from moto.ec2 import ec2_backends
+from tests import aws_verified
 
 REGION = "us-east-1"
 
@@ -132,17 +133,30 @@ def test_create_two_security_groups_with_same_name_in_different_vpc():
     assert name in group_names
 
 
-@mock_aws
+@aws_verified
+@pytest.mark.aws_verified
 def test_create_two_security_groups_in_vpc_with_ipv6_enabled():
     ec2 = boto3.resource("ec2", region_name=REGION)
     vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16", AmazonProvidedIpv6CidrBlock=True)
+    security_group = None
 
-    security_group = ec2.create_security_group(
-        GroupName=str(uuid4()), Description="Test security group sg01", VpcId=vpc.id
-    )
+    try:
+        security_group = ec2.create_security_group(
+            GroupName=str(uuid4()), Description="Test", VpcId=vpc.id
+        )
 
-    # The security group must have two defaul egress rules (one for ipv4 and aonther for ipv6)
-    assert len(security_group.ip_permissions_egress) == 2
+        # The security group must have one egress rule with an IP range for ipv4 and ipv6
+        assert len(security_group.ip_permissions_egress) == 1
+        assert security_group.ip_permissions_egress[0]["IpRanges"] == [
+            {"CidrIp": "0.0.0.0/0"}
+        ]
+        assert security_group.ip_permissions_egress[0]["Ipv6Ranges"] == [
+            {"CidrIpv6": "::/0"}
+        ]
+    finally:
+        if security_group:
+            security_group.delete()
+        vpc.delete()
 
 
 @mock_aws
@@ -868,7 +882,6 @@ def test_description_in_ip_permissions():
     assert result["SecurityGroupRules"][1]["FromPort"] == 27017
     assert result["SecurityGroupRules"][1]["ToPort"] == 27018
 
-    assert result["SecurityGroupRules"][2]["Description"] == ""
     assert result["SecurityGroupRules"][2]["IsEgress"] is True
     assert result["SecurityGroupRules"][2]["FromPort"] == -1
     assert result["SecurityGroupRules"][2]["ToPort"] == -1
@@ -2025,3 +2038,284 @@ def test_invalid_security_group_id_in_rules_search():
         Filters=[{"Name": "group-id", "Values": [group_id]}]
     )
     assert len(response["SecurityGroupRules"]) == 1
+
+
+@aws_verified
+@pytest.mark.aws_verified
+@pytest.mark.parametrize("is_ingress", [True, False], ids=["ingress", "egress"])
+def test_authorize_security_group_rules_with_different_ipranges_or_prefixes(is_ingress):
+    is_egress = not is_ingress
+    sts = boto3.client("sts", "us-east-1")
+    account_id = sts.get_caller_identity()["Account"]
+
+    ec2_client = boto3.client("ec2", "us-east-1")
+    vpc = ec2_client.create_vpc(CidrBlock="10.0.0.0/24")
+    vpc_id = vpc["Vpc"]["VpcId"]
+    sg_id = pl_id = None
+
+    try:
+        sg = ec2_client.create_security_group(
+            Description="test", GroupName=f"test_{str(uuid4())[0:6]}", VpcId=vpc_id
+        )
+        sg_id = sg["GroupId"]
+
+        prefix_list = ec2_client.create_managed_prefix_list(
+            PrefixListName=f"test{str(uuid4())[0:6]}",
+            AddressFamily="IPv4",
+            MaxEntries=1,
+        )
+        pl_id = prefix_list["PrefixList"]["PrefixListId"]
+
+        rules = [
+            # starting rule with tcp/22
+            (22, "tcp", {"IpRanges": [{"CidrIp": "10.0.0.0/24"}]}),
+            # keeping the port/protocol the same but changing the CIDR will NOT show up in describe_security_group_rules
+            (22, "tcp", {"IpRanges": [{"CidrIp": "10.0.1.0/24"}]}),
+            # changing just the port will show up in describe_security_group_rules
+            (80, "tcp", {"IpRanges": [{"CidrIp": "10.0.0.0/24"}]}),
+            # changing just the protocol will show up in describe_security_group_rules
+            (22, "udp", {"IpRanges": [{"CidrIp": "10.0.0.0/24"}]}),
+            # changing the port and protocol will show up in describe_security_group_rules
+            (-1, "-1", {"IpRanges": [{"CidrIp": "10.0.0.0/24"}]}),
+            #
+            # The same issues are present with IPv6 in that, with matching port/protocol to the original rule, it will be omitted from describe_security_group_rules
+            (22, "tcp", {"Ipv6Ranges": [{"CidrIpv6": "2001:db8::/32"}]}),
+            # changing the port to a new port will cause it to show up in describe_security_group_rules
+            # however, a the value cannot be seen. An empty `CidrIpv4` key is returned and the `CidrIpv6` key is not returned.
+            (443, "tcp", {"Ipv6Ranges": [{"CidrIpv6": "2001:db8::/64"}]}),
+            #
+            # The same issues are present with PrefixListIds in that, with matching port/protocol to the original rule, it will be omitted from describe_security_group_rules
+            (22, "tcp", {"PrefixListIds": [{"PrefixListId": pl_id}]}),
+            # changing the port to a new port will cause it to show up in describe_security_group_rules
+            # however there is no indication that the rule is for a prefix list.
+            (8080, "tcp", {"PrefixListIds": [{"PrefixListId": pl_id}]}),
+            #
+            # the same issues are present with security group references in that, with matching port/protocol to the original rule, it will be omitted from describe_security_group_rules
+            (22, "tcp", {"UserIdGroupPairs": [{"GroupId": sg_id}]}),
+            # changing the port to a new port will cause it to show up in describe_security_group_rules
+            # however there is no indication of this pointing to itself as a target.
+            (25, "tcp", {"UserIdGroupPairs": [{"GroupId": sg_id}]}),
+        ]
+        method = (
+            ec2_client.authorize_security_group_ingress
+            if is_ingress
+            else ec2_client.authorize_security_group_egress
+        )
+        for rule in rules:
+            method(
+                GroupId=sg_id,
+                IpPermissions=[
+                    {
+                        "FromPort": rule[0],
+                        "IpProtocol": rule[1],
+                        "ToPort": rule[0],
+                        **rule[2],
+                    }
+                ],
+            )
+        created_rules = ec2_client.describe_security_group_rules(
+            Filters=[{"Name": "group-id", "Values": [sg_id]}]
+        )["SecurityGroupRules"]
+        for rule in created_rules:
+            rule.pop("GroupOwnerId", None)
+            rule.pop("Tags", None)
+            rule.pop("SecurityGroupRuleId", None)
+            del rule["GroupId"]
+
+        assert {
+            "CidrIpv6": "2001:db8::/64",
+            "FromPort": 443,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ToPort": 443,
+        } in created_rules
+        assert {
+            "FromPort": 22,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ReferencedGroupInfo": {"GroupId": sg_id, "UserId": account_id},
+            "ToPort": 22,
+        } in created_rules
+        assert {
+            "FromPort": 25,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ReferencedGroupInfo": {"GroupId": sg_id, "UserId": account_id},
+            "ToPort": 25,
+        } in created_rules
+        assert {
+            "CidrIpv4": "10.0.0.0/24",
+            "FromPort": 22,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ToPort": 22,
+        } in created_rules
+        assert {
+            "CidrIpv4": "10.0.0.0/24",
+            "FromPort": -1,
+            "IpProtocol": "-1",
+            "IsEgress": is_egress,
+            "ToPort": -1,
+        } in created_rules
+        assert {
+            "FromPort": 8080,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "PrefixListId": pl_id,
+            "ToPort": 8080,
+        } in created_rules
+        assert {
+            "CidrIpv6": "2001:db8::/32",
+            "FromPort": 22,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ToPort": 22,
+        } in created_rules
+        assert {
+            "FromPort": 22,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "PrefixListId": pl_id,
+            "ToPort": 22,
+        } in created_rules
+        assert {
+            "CidrIpv4": "10.0.0.0/24",
+            "FromPort": 80,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ToPort": 80,
+        } in created_rules
+        assert {
+            "CidrIpv4": "10.0.1.0/24",
+            "FromPort": 22,
+            "IpProtocol": "tcp",
+            "IsEgress": is_egress,
+            "ToPort": 22,
+        } in created_rules
+        assert {
+            "CidrIpv4": "0.0.0.0/0",
+            "FromPort": -1,
+            "IpProtocol": "-1",
+            "IsEgress": True,
+            "ToPort": -1,
+        } in created_rules
+        assert {
+            "CidrIpv4": "10.0.0.0/24",
+            "FromPort": 22,
+            "IpProtocol": "udp",
+            "IsEgress": is_egress,
+            "ToPort": 22,
+        } in created_rules
+
+        groups = ec2_client.describe_security_groups(GroupIds=[sg_id])[
+            "SecurityGroups"
+        ][0]
+        permissions = groups["IpPermissions"]
+        permissions_egress = groups["IpPermissionsEgress"]
+        # Drop empty fields
+        permissions = [{k: p[k] for k in p if p[k]} for p in permissions]
+        permissions_egress = [{k: p[k] for k in p if p[k]} for p in permissions_egress]
+
+        if is_ingress:
+            assert len(permissions) == 7
+            assert {
+                "FromPort": 80,
+                "IpProtocol": "tcp",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}],
+                "ToPort": 80,
+            } in permissions
+            assert {
+                "FromPort": 8080,
+                "IpProtocol": "tcp",
+                "PrefixListIds": [{"PrefixListId": pl_id}],
+                "ToPort": 8080,
+            } in permissions
+            assert {
+                "IpProtocol": "-1",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}],
+            } in permissions
+            assert {
+                "FromPort": 22,
+                "IpProtocol": "udp",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}],
+                "ToPort": 22,
+            } in permissions
+            assert {
+                "FromPort": 22,
+                "IpProtocol": "tcp",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}, {"CidrIp": "10.0.1.0/24"}],
+                "Ipv6Ranges": [{"CidrIpv6": "2001:db8::/32"}],
+                "PrefixListIds": [{"PrefixListId": pl_id}],
+                "ToPort": 22,
+                "UserIdGroupPairs": [{"GroupId": sg_id, "UserId": account_id}],
+            } in permissions
+            assert {
+                "FromPort": 25,
+                "IpProtocol": "tcp",
+                "ToPort": 25,
+                "UserIdGroupPairs": [{"GroupId": sg_id, "UserId": account_id}],
+            } in permissions
+            assert {
+                "FromPort": 443,
+                "IpProtocol": "tcp",
+                "Ipv6Ranges": [{"CidrIpv6": "2001:db8::/64"}],
+                "ToPort": 443,
+            } in permissions
+
+            assert permissions_egress == [
+                {"IpProtocol": "-1", "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}
+            ]
+        if is_egress:
+            assert len(permissions) == 0
+            assert len(permissions_egress) == 7
+
+            assert {
+                "FromPort": 80,
+                "IpProtocol": "tcp",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}],
+                "ToPort": 80,
+            } in permissions_egress
+            assert {
+                "FromPort": 8080,
+                "IpProtocol": "tcp",
+                "PrefixListIds": [{"PrefixListId": pl_id}],
+                "ToPort": 8080,
+            } in permissions_egress
+            assert {
+                "IpProtocol": "-1",
+                "IpRanges": [{"CidrIp": "0.0.0.0/0"}, {"CidrIp": "10.0.0.0/24"}],
+            } in permissions_egress
+            assert {
+                "FromPort": 22,
+                "IpProtocol": "udp",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}],
+                "ToPort": 22,
+            } in permissions_egress
+            assert {
+                "FromPort": 22,
+                "IpProtocol": "tcp",
+                "IpRanges": [{"CidrIp": "10.0.0.0/24"}, {"CidrIp": "10.0.1.0/24"}],
+                "Ipv6Ranges": [{"CidrIpv6": "2001:db8::/32"}],
+                "PrefixListIds": [{"PrefixListId": pl_id}],
+                "ToPort": 22,
+                "UserIdGroupPairs": [{"GroupId": sg_id, "UserId": account_id}],
+            } in permissions_egress
+            assert {
+                "FromPort": 25,
+                "IpProtocol": "tcp",
+                "ToPort": 25,
+                "UserIdGroupPairs": [{"GroupId": sg_id, "UserId": account_id}],
+            } in permissions_egress
+            assert {
+                "FromPort": 443,
+                "IpProtocol": "tcp",
+                "Ipv6Ranges": [{"CidrIpv6": "2001:db8::/64"}],
+                "ToPort": 443,
+            } in permissions_egress
+
+    finally:
+        if pl_id:
+            ec2_client.delete_managed_prefix_list(PrefixListId=pl_id)
+        if sg_id:
+            ec2_client.delete_security_group(GroupId=sg_id)
+        ec2_client.delete_vpc(VpcId=vpc_id)
diff --git a/tests/test_kms/test_model.py b/tests/test_kms/test_model.py
index 523b1d748..f6532ff65 100644
--- a/tests/test_kms/test_model.py
+++ b/tests/test_kms/test_model.py
@@ -33,7 +33,7 @@ def test_encrypt_key_arn(backend, key):
 
 
 def test_encrypt_alias_name(backend, key):
-    backend.add_alias(key.id, "alias/test/test")
+    backend.create_alias(key.id, "alias/test/test")
 
     ciphertext, arn = backend.encrypt("alias/test/test", PLAINTEXT, {})
 
@@ -42,7 +42,7 @@ def test_encrypt_alias_name(backend, key):
 
 
 def test_encrypt_alias_arn(backend, key):
-    backend.add_alias(key.id, "alias/test/test")
+    backend.create_alias(key.id, "alias/test/test")
 
     ciphertext, arn = backend.encrypt(
         f"arn:aws:kms:{REGION}:{key.account_id}:alias/test/test", PLAINTEXT, {}