Directory Service, Route53Resolver: Delete network interfaces created during initialization (#4629)
This commit is contained in:
parent
9a8be5ce28
commit
dd0441d36c
@ -79,26 +79,31 @@ class Directory(BaseModel): # pylint: disable=too-many-instance-attributes
|
|||||||
self.stage = "Active"
|
self.stage = "Active"
|
||||||
self.launch_time = datetime.now(timezone.utc).isoformat()
|
self.launch_time = datetime.now(timezone.utc).isoformat()
|
||||||
self.stage_last_updated_date_time = datetime.now(timezone.utc).isoformat()
|
self.stage_last_updated_date_time = datetime.now(timezone.utc).isoformat()
|
||||||
# Create a security group and ENI, returning the IPs for the ENI.
|
|
||||||
subnet_ips = self.create_eni()
|
|
||||||
|
|
||||||
if self.directory_type != "ADConnector":
|
if self.directory_type == "ADConnector":
|
||||||
self.dns_ip_addrs = subnet_ips
|
self.security_group_id = self.create_security_group(
|
||||||
else:
|
self.connect_settings["VpcId"]
|
||||||
|
)
|
||||||
|
self.eni_ids, self.subnet_ips = self.create_eni(
|
||||||
|
self.security_group_id, self.connect_settings["SubnetIds"]
|
||||||
|
)
|
||||||
|
self.connect_settings["SecurityGroupId"] = self.security_group_id
|
||||||
|
self.connect_settings["ConnectIps"] = self.subnet_ips
|
||||||
self.dns_ip_addrs = self.connect_settings["CustomerDnsIps"]
|
self.dns_ip_addrs = self.connect_settings["CustomerDnsIps"]
|
||||||
self.connect_settings["ConnectIps"] = subnet_ips
|
|
||||||
|
|
||||||
def create_eni(self):
|
|
||||||
"""Return IP addrs after creating an ENI for each subnet."""
|
|
||||||
if self.vpc_settings:
|
|
||||||
vpc_id = self.vpc_settings["VpcId"]
|
|
||||||
subnet_ids = self.vpc_settings["SubnetIds"]
|
|
||||||
else:
|
else:
|
||||||
vpc_id = self.connect_settings["VpcId"]
|
self.security_group_id = self.create_security_group(
|
||||||
subnet_ids = self.connect_settings["SubnetIds"]
|
self.vpc_settings["VpcId"]
|
||||||
|
)
|
||||||
|
self.eni_ids, self.subnet_ips = self.create_eni(
|
||||||
|
self.security_group_id, self.vpc_settings["SubnetIds"]
|
||||||
|
)
|
||||||
|
self.vpc_settings["SecurityGroupId"] = self.security_group_id
|
||||||
|
self.dns_ip_addrs = self.subnet_ips
|
||||||
|
|
||||||
# Need a security group for the ENI.
|
def create_security_group(self, vpc_id):
|
||||||
security_group = ec2_backends[self.region].create_security_group(
|
"""Create security group for the network interface."""
|
||||||
|
security_group_info = ec2_backends[self.region].create_security_group(
|
||||||
name=f"{self.directory_id}_controllers",
|
name=f"{self.directory_id}_controllers",
|
||||||
description=(
|
description=(
|
||||||
f"AWS created security group for {self.directory_id} "
|
f"AWS created security group for {self.directory_id} "
|
||||||
@ -106,17 +111,31 @@ class Directory(BaseModel): # pylint: disable=too-many-instance-attributes
|
|||||||
),
|
),
|
||||||
vpc_id=vpc_id,
|
vpc_id=vpc_id,
|
||||||
)
|
)
|
||||||
|
return security_group_info.id
|
||||||
|
|
||||||
ip_addrs = []
|
def delete_security_group(self):
|
||||||
|
"""Delete the given security group."""
|
||||||
|
ec2_backends[self.region].delete_security_group(group_id=self.security_group_id)
|
||||||
|
|
||||||
|
def create_eni(self, security_group_id, subnet_ids):
|
||||||
|
"""Return ENI ids and primary addresses created for each subnet."""
|
||||||
|
eni_ids = []
|
||||||
|
subnet_ips = []
|
||||||
for subnet_id in subnet_ids:
|
for subnet_id in subnet_ids:
|
||||||
eni_info = ec2_backends[self.region].create_network_interface(
|
eni_info = ec2_backends[self.region].create_network_interface(
|
||||||
subnet=subnet_id,
|
subnet=subnet_id,
|
||||||
private_ip_address=None,
|
private_ip_address=None,
|
||||||
group_ids=[security_group.id],
|
group_ids=[security_group_id],
|
||||||
description=f"AWS created network interface for {self.directory_id}",
|
description=f"AWS created network interface for {self.directory_id}",
|
||||||
)
|
)
|
||||||
ip_addrs.append(eni_info.private_ip_address)
|
eni_ids.append(eni_info.id)
|
||||||
return ip_addrs
|
subnet_ips.append(eni_info.private_ip_address)
|
||||||
|
return eni_ids, subnet_ips
|
||||||
|
|
||||||
|
def delete_eni(self):
|
||||||
|
"""Delete ENI for each subnet and the security group."""
|
||||||
|
for eni_id in self.eni_ids:
|
||||||
|
ec2_backends[self.region].delete_network_interface(eni_id)
|
||||||
|
|
||||||
def update_alias(self, alias):
|
def update_alias(self, alias):
|
||||||
"""Change default alias to given alias."""
|
"""Change default alias to given alias."""
|
||||||
@ -127,26 +146,37 @@ class Directory(BaseModel): # pylint: disable=too-many-instance-attributes
|
|||||||
"""Enable/disable sso based on whether new_state is True or False."""
|
"""Enable/disable sso based on whether new_state is True or False."""
|
||||||
self.sso_enabled = new_state
|
self.sso_enabled = new_state
|
||||||
|
|
||||||
def to_json(self):
|
def to_dict(self):
|
||||||
"""Convert the attributes into json with CamelCase tags."""
|
"""Create a dictionary of attributes for Directory."""
|
||||||
replacement_keys = {"directory_type": "Type"}
|
attributes = {
|
||||||
exclude_items = ["password"]
|
"AccessUrl": self.access_url,
|
||||||
|
"Alias": self.alias,
|
||||||
|
"DirectoryId": self.directory_id,
|
||||||
|
"DesiredNumberOfDomainControllers": self.desired_number_of_domain_controllers,
|
||||||
|
"DnsIpAddrs": self.dns_ip_addrs,
|
||||||
|
"LaunchTime": self.launch_time,
|
||||||
|
"Name": self.name,
|
||||||
|
"SsoEnabled": self.sso_enabled,
|
||||||
|
"Stage": self.stage,
|
||||||
|
"StageLastUpdatedDateTime": self.stage_last_updated_date_time,
|
||||||
|
"Type": self.directory_type,
|
||||||
|
}
|
||||||
|
|
||||||
json_result = {}
|
if self.edition:
|
||||||
for item, value in self.__dict__.items():
|
attributes["Edition"] = self.edition
|
||||||
# Discard empty strings, but allow values set to False or zero.
|
if self.size:
|
||||||
if value == "" or item in exclude_items:
|
attributes["Size"] = self.size
|
||||||
continue
|
if self.short_name:
|
||||||
|
attributes["ShortName"] = self.short_name
|
||||||
|
if self.description:
|
||||||
|
attributes["Description"] = self.description
|
||||||
|
|
||||||
if item in replacement_keys:
|
if self.vpc_settings:
|
||||||
json_result[replacement_keys[item]] = value
|
attributes["VpcSettings"] = self.vpc_settings
|
||||||
else:
|
else:
|
||||||
new_tag = "".join(x.title() for x in item.split("_"))
|
attributes["ConnectSettings"] = self.connect_settings
|
||||||
json_result[new_tag] = value
|
attributes["ConnectSettings"]["CustomerDnsIps"] = None
|
||||||
|
return attributes
|
||||||
if json_result["ConnectSettings"]:
|
|
||||||
json_result["ConnectSettings"]["CustomerDnsIps"] = None
|
|
||||||
return json_result
|
|
||||||
|
|
||||||
|
|
||||||
class DirectoryServiceBackend(BaseBackend):
|
class DirectoryServiceBackend(BaseBackend):
|
||||||
@ -394,6 +424,8 @@ class DirectoryServiceBackend(BaseBackend):
|
|||||||
def delete_directory(self, directory_id):
|
def delete_directory(self, directory_id):
|
||||||
"""Delete directory with the matching ID."""
|
"""Delete directory with the matching ID."""
|
||||||
self._validate_directory_id(directory_id)
|
self._validate_directory_id(directory_id)
|
||||||
|
self.directories[directory_id].delete_eni()
|
||||||
|
self.directories[directory_id].delete_security_group()
|
||||||
self.tagger.delete_all_tags_for_resource(directory_id)
|
self.tagger.delete_all_tags_for_resource(directory_id)
|
||||||
self.directories.pop(directory_id)
|
self.directories.pop(directory_id)
|
||||||
return directory_id
|
return directory_id
|
||||||
|
@ -97,13 +97,13 @@ class DirectoryServiceResponse(BaseResponse):
|
|||||||
next_token = self._get_param("NextToken")
|
next_token = self._get_param("NextToken")
|
||||||
limit = self._get_int_param("Limit")
|
limit = self._get_int_param("Limit")
|
||||||
try:
|
try:
|
||||||
(descriptions, next_token) = self.ds_backend.describe_directories(
|
(directories, next_token) = self.ds_backend.describe_directories(
|
||||||
directory_ids, next_token=next_token, limit=limit
|
directory_ids, next_token=next_token, limit=limit
|
||||||
)
|
)
|
||||||
except InvalidToken as exc:
|
except InvalidToken as exc:
|
||||||
raise InvalidNextTokenException() from exc
|
raise InvalidNextTokenException() from exc
|
||||||
|
|
||||||
response = {"DirectoryDescriptions": [x.to_json() for x in descriptions]}
|
response = {"DirectoryDescriptions": [x.to_dict() for x in directories]}
|
||||||
if next_token:
|
if next_token:
|
||||||
response["NextToken"] = next_token
|
response["NextToken"] = next_token
|
||||||
return json.dumps(response)
|
return json.dumps(response)
|
||||||
|
@ -187,6 +187,7 @@ class ResolverEndpoint(BaseModel): # pylint: disable=too-many-instance-attribut
|
|||||||
|
|
||||||
# NOTE; This currently doesn't reflect IPv6 addresses.
|
# NOTE; This currently doesn't reflect IPv6 addresses.
|
||||||
self.subnets = self._build_subnet_info()
|
self.subnets = self._build_subnet_info()
|
||||||
|
self.eni_ids = self.create_eni()
|
||||||
self.ip_address_count = len(ip_addresses)
|
self.ip_address_count = len(ip_addresses)
|
||||||
|
|
||||||
self.host_vpc_id = self._vpc_id_from_subnet()
|
self.host_vpc_id = self._vpc_id_from_subnet()
|
||||||
@ -232,9 +233,10 @@ class ResolverEndpoint(BaseModel): # pylint: disable=too-many-instance-attribut
|
|||||||
|
|
||||||
def create_eni(self):
|
def create_eni(self):
|
||||||
"""Create a VPC ENI for each combo of AZ, subnet and IP."""
|
"""Create a VPC ENI for each combo of AZ, subnet and IP."""
|
||||||
|
eni_ids = []
|
||||||
for subnet, ip_info in self.subnets.items():
|
for subnet, ip_info in self.subnets.items():
|
||||||
for ip_addr, eni_id in ip_info.items():
|
for ip_addr, eni_id in ip_info.items():
|
||||||
ec2_backends[self.region].create_network_interface(
|
eni_info = ec2_backends[self.region].create_network_interface(
|
||||||
description=f"Route 53 Resolver: {self.id}:{eni_id}",
|
description=f"Route 53 Resolver: {self.id}:{eni_id}",
|
||||||
group_ids=self.security_group_ids,
|
group_ids=self.security_group_ids,
|
||||||
interface_type="interface",
|
interface_type="interface",
|
||||||
@ -244,6 +246,13 @@ class ResolverEndpoint(BaseModel): # pylint: disable=too-many-instance-attribut
|
|||||||
],
|
],
|
||||||
subnet=subnet,
|
subnet=subnet,
|
||||||
)
|
)
|
||||||
|
eni_ids.append(eni_info.id)
|
||||||
|
return eni_ids
|
||||||
|
|
||||||
|
def delete_eni(self):
|
||||||
|
"""Delete the VPC ENI created for the subnet and IP combos."""
|
||||||
|
for eni_id in self.eni_ids:
|
||||||
|
ec2_backends[self.region].delete_network_interface(eni_id)
|
||||||
|
|
||||||
def description(self):
|
def description(self):
|
||||||
"""Return a dictionary of relevant info for this resolver endpoint."""
|
"""Return a dictionary of relevant info for this resolver endpoint."""
|
||||||
@ -466,7 +475,6 @@ class Route53ResolverBackend(BaseBackend):
|
|||||||
ip_addresses,
|
ip_addresses,
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
resolver_endpoint.create_eni()
|
|
||||||
|
|
||||||
self.resolver_endpoints[endpoint_id] = resolver_endpoint
|
self.resolver_endpoints[endpoint_id] = resolver_endpoint
|
||||||
self.tagger.tag_resource(resolver_endpoint.arn, tags or [])
|
self.tagger.tag_resource(resolver_endpoint.arn, tags or [])
|
||||||
@ -593,6 +601,7 @@ class Route53ResolverBackend(BaseBackend):
|
|||||||
|
|
||||||
self.tagger.delete_all_tags_for_resource(resolver_endpoint_id)
|
self.tagger.delete_all_tags_for_resource(resolver_endpoint_id)
|
||||||
resolver_endpoint = self.resolver_endpoints.pop(resolver_endpoint_id)
|
resolver_endpoint = self.resolver_endpoints.pop(resolver_endpoint_id)
|
||||||
|
resolver_endpoint.delete_eni()
|
||||||
resolver_endpoint.status = "DELETING"
|
resolver_endpoint.status = "DELETING"
|
||||||
resolver_endpoint.status_message = resolver_endpoint.status_message.replace(
|
resolver_endpoint.status_message = resolver_endpoint.status_message.replace(
|
||||||
"Successfully created", "Deleting"
|
"Successfully created", "Deleting"
|
||||||
|
@ -36,6 +36,16 @@ def test_ds_delete_directory():
|
|||||||
result = client.delete_directory(DirectoryId=directory_id)
|
result = client.delete_directory(DirectoryId=directory_id)
|
||||||
assert result["DirectoryId"] == directory_id
|
assert result["DirectoryId"] == directory_id
|
||||||
|
|
||||||
|
# Verify there are no dictionaries, network interfaces or associated
|
||||||
|
# security groups.
|
||||||
|
result = client.describe_directories()
|
||||||
|
assert len(result["DirectoryDescriptions"]) == 0
|
||||||
|
result = ec2_client.describe_network_interfaces()
|
||||||
|
assert len(result["NetworkInterfaces"]) == 0
|
||||||
|
result = ec2_client.describe_security_groups()
|
||||||
|
for group in result["SecurityGroups"]:
|
||||||
|
assert "directory controllers" not in group["Description"]
|
||||||
|
|
||||||
# Attempt to delete a non-existent directory.
|
# Attempt to delete a non-existent directory.
|
||||||
nonexistent_id = f"d-{get_random_hex(10)}"
|
nonexistent_id = f"d-{get_random_hex(10)}"
|
||||||
with pytest.raises(ClientError) as exc:
|
with pytest.raises(ClientError) as exc:
|
||||||
@ -116,6 +126,7 @@ def test_ds_describe_directories():
|
|||||||
assert dir_info["Type"] == "SimpleAD"
|
assert dir_info["Type"] == "SimpleAD"
|
||||||
assert dir_info["VpcSettings"]["VpcId"].startswith("vpc-")
|
assert dir_info["VpcSettings"]["VpcId"].startswith("vpc-")
|
||||||
assert len(dir_info["VpcSettings"]["SubnetIds"]) == 2
|
assert len(dir_info["VpcSettings"]["SubnetIds"]) == 2
|
||||||
|
assert dir_info["VpcSettings"]["SecurityGroupId"].startswith("sg-")
|
||||||
assert len(dir_info["DnsIpAddrs"]) == 2
|
assert len(dir_info["DnsIpAddrs"]) == 2
|
||||||
assert "NextToken" not in result
|
assert "NextToken" not in result
|
||||||
|
|
||||||
|
@ -415,6 +415,12 @@ def test_route53resolver_delete_resolver_endpoint():
|
|||||||
assert "Deleting" in endpoint["StatusMessage"]
|
assert "Deleting" in endpoint["StatusMessage"]
|
||||||
assert endpoint["CreationTime"] == created_endpoint["CreationTime"]
|
assert endpoint["CreationTime"] == created_endpoint["CreationTime"]
|
||||||
|
|
||||||
|
# Verify there are no endpoints or no network interfaces.
|
||||||
|
response = client.list_resolver_endpoints()
|
||||||
|
assert len(response["ResolverEndpoints"]) == 0
|
||||||
|
result = ec2_client.describe_network_interfaces()
|
||||||
|
assert len(result["NetworkInterfaces"]) == 0
|
||||||
|
|
||||||
|
|
||||||
@mock_ec2
|
@mock_ec2
|
||||||
@mock_route53resolver
|
@mock_route53resolver
|
||||||
|
Loading…
Reference in New Issue
Block a user