Route53: Validate deletion of RRsets (#5743)

This commit is contained in:
Bert Blommers 2022-12-10 10:07:30 -01:00 committed by GitHub
parent 0588db704a
commit b2300f1eae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 154 additions and 19 deletions

View File

@ -20,6 +20,7 @@ from moto.route53.exceptions import (
QueryLoggingConfigAlreadyExists,
DnsNameInvalidForZone,
ChangeSetAlreadyExists,
InvalidInput,
)
from moto.core import BaseBackend, BackendDict, BaseModel, CloudFormationModel
from moto.moto_api._internal import mock_random as random
@ -262,6 +263,20 @@ def reverse_domain_name(domain_name):
return ".".join(reversed(domain_name.split(".")))
class ChangeList(list):
"""
Contains a 'clean' list of ResourceRecordChangeSets
"""
def append(self, item) -> None:
item["ResourceRecordSet"]["Name"] = item["ResourceRecordSet"]["Name"].strip(".")
super().append(item)
def __contains__(self, item):
item["ResourceRecordSet"]["Name"] = item["ResourceRecordSet"]["Name"].strip(".")
return super().__contains__(item)
class FakeZone(CloudFormationModel):
def __init__(
self,
@ -279,7 +294,7 @@ class FakeZone(CloudFormationModel):
self.private_zone = private_zone
self.rrsets = []
self.delegation_set = delegation_set
self.rr_changes = []
self.rr_changes = ChangeList()
def add_rrset(self, record_set):
record_set = RecordSet(record_set)
@ -540,6 +555,20 @@ class Route53Backend(BaseBackend):
action=rr["Action"], name=name, _type=_type
)
for value in change_list:
if value["Action"] == "DELETE":
# To delete a resource record set, you must specify all the same values that you specified when you created it.
corresponding_create = copy.deepcopy(value)
corresponding_create["Action"] = "CREATE"
corresponding_upsert = copy.deepcopy(value)
corresponding_upsert["Action"] = "UPSERT"
if (
corresponding_create not in the_zone.rr_changes
and corresponding_upsert not in the_zone.rr_changes
):
msg = f"Invalid request: Expected exactly one of [AliasTarget, all of [TTL, and ResourceRecords], or TrafficPolicyInstanceId], but found none in Change with [Action=DELETE, Name={value['ResourceRecordSet']['Name']}, Type={value['ResourceRecordSet']['Type']}, SetIdentifier={value['ResourceRecordSet'].get('SetIdentifier', 'null')}]"
raise InvalidInput(msg)
for value in change_list:
original_change = copy.deepcopy(value)
action = value["Action"]

View File

@ -0,0 +1,58 @@
import copy
import sure # noqa # pylint: disable=unused-import
from moto.route53.models import ChangeList
def test_last_dot_in_name_is_ignored():
change1 = {
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "test.without.dot",
"Type": "CNAME",
"TTL": 600,
"ResourceRecords": [{"Value": "0.0.0.0"}],
},
}
change1_alt = copy.deepcopy(change1)
change1_alt["ResourceRecordSet"]["Name"] = "test.without.dot."
change2 = copy.deepcopy(change1)
change2["ResourceRecordSet"]["Name"] = "test.with.dot."
change2_alt = copy.deepcopy(change1)
change2_alt["ResourceRecordSet"]["Name"] = "test.with.dot"
change_list = ChangeList()
change_list.append(change1)
change_list.append(change2)
change_list.should.contain(change1)
change_list.should.contain(change1_alt)
change_list.should.contain(change2)
change_list.should.contain(change2_alt)
def test_last_dot_is_not_stored():
change1 = {
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "test.google.com.",
"Type": "CNAME",
"TTL": 600,
"ResourceRecords": [{"Value": "0.0.0.0"}],
},
}
change_list = ChangeList()
change_list.append(change1)
change_list[0]["ResourceRecordSet"]["Name"].should.equal("test.google.com")
def test_optional_fields():
change = {
"Action": "UPSERT",
"ResourceRecordSet": {"Name": "test.google.com.", "Type": "CNAME"},
}
change_list = ChangeList()
change_list.append(change)
change_list.should.equal([change])

View File

@ -308,6 +308,7 @@ def test_deleting_weighted_route():
"Name": "cname.testdns.aws.com",
"Type": "CNAME",
"SetIdentifier": "success-test-foo",
"Weight": 50,
},
}
]
@ -374,6 +375,8 @@ def test_deleting_latency_route():
"Name": "cname.testdns.aws.com",
"Type": "CNAME",
"SetIdentifier": "success-test-foo",
"Region": "us-west-2",
"ResourceRecords": [{"Value": "example.com"}],
},
}
]
@ -708,29 +711,18 @@ def test_change_resource_record_sets_crud_valid():
)
cname_alias_record_detail.should_not.contain("ResourceRecords")
# Delete record with wrong type.
delete_payload = {
"Comment": "delete prod.redis.db",
"Changes": [
{
"Action": "DELETE",
"ResourceRecordSet": {"Name": "prod.redis.db", "Type": "CNAME"},
}
],
}
conn.change_resource_record_sets(
HostedZoneId=hosted_zone_id, ChangeBatch=delete_payload
)
response = conn.list_resource_record_sets(HostedZoneId=hosted_zone_id)
len(response["ResourceRecordSets"]).should.equal(2)
# Delete record.
delete_payload = {
"Comment": "delete prod.redis.db",
"Changes": [
{
"Action": "DELETE",
"ResourceRecordSet": {"Name": "prod.redis.db", "Type": "A"},
"ResourceRecordSet": {
"Name": "prod.redis.db.",
"Type": "A",
"TTL": 60,
"ResourceRecords": [{"Value": "192.168.1.1"}],
},
}
],
}
@ -818,7 +810,12 @@ def test_change_resource_record_sets_crud_valid_with_special_xml_chars():
"Changes": [
{
"Action": "DELETE",
"ResourceRecordSet": {"Name": "prod.redis.db", "Type": "TXT"},
"ResourceRecordSet": {
"Name": "prod.redis.db.",
"Type": "TXT",
"TTL": 10,
"ResourceRecords": [{"Value": "SomeInitialValue"}],
},
}
],
}
@ -829,6 +826,57 @@ def test_change_resource_record_sets_crud_valid_with_special_xml_chars():
len(response["ResourceRecordSets"]).should.equal(1)
@mock_route53
def test_change_resource_record_set__delete_should_match_create():
# To delete a resource record set, you must specify all the same values that you specified when you created it.
client = boto3.client("route53", region_name="us-east-1")
name = "example.com"
hosted_zone_id = client.create_hosted_zone(Name=name, CallerReference=name)[
"HostedZone"
]["Id"]
create_call = client.change_resource_record_sets(
HostedZoneId=hosted_zone_id,
ChangeBatch={
"Changes": [
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": name,
"Type": "A",
"TTL": 300,
"ResourceRecords": [{"Value": "192.168.0.1"}],
},
}
]
},
)
waiter = client.get_waiter("resource_record_sets_changed")
waiter.wait(Id=create_call["ChangeInfo"]["Id"])
with pytest.raises(ClientError) as exc:
client.change_resource_record_sets(
HostedZoneId=hosted_zone_id,
ChangeBatch={
"Changes": [
{
"Action": "DELETE",
"ResourceRecordSet": {
"Name": name,
"Type": "A"
# Missing TTL and ResourceRecords
},
}
]
},
)
err = exc.value.response["Error"]
err["Code"].should.equal("InvalidInput")
err["Message"].should.equal(
"Invalid request: Expected exactly one of [AliasTarget, all of [TTL, and ResourceRecords], or TrafficPolicyInstanceId], but found none in Change with [Action=DELETE, Name=example.com, Type=A, SetIdentifier=null]"
)
@mock_route53
def test_change_weighted_resource_record_sets():
conn = boto3.client("route53", region_name="us-east-2")