Ensure we do not pass invalid data for A records
DNSPython does not allow IPs to have leading zeros for A records [1] and eventlet agrees [2], so we should ensure we do not create a situation where a project can DOS itself. This patch uses the DNSPython method to ensure it is kept in sync 1 - https://github.com/rthalley/dnspython/blob/v1.15.0/dns/ipv4.py#L52-L54 2 - https://github.com/eventlet/eventlet/blob/v0.20.0/eventlet/support/dns/ipv4.py#L52-L54 Partial-Bug: 1760833 Change-Id: I975b18d390647de9fe11c105cd421b761f88be6c
This commit is contained in:
parent
7a2adab56a
commit
d72c652013
127
contrib/fixleadingzeros.py
Executable file
127
contrib/fixleadingzeros.py
Executable file
@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright (C) 2018 Verizon
|
||||||
|
#
|
||||||
|
# Author: Graham Hayes <gr@ham.ie>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import dns.exception
|
||||||
|
from dns.ipv4 import inet_aton
|
||||||
|
import netaddr
|
||||||
|
|
||||||
|
from designateclient.v2 import client
|
||||||
|
from designateclient import shell
|
||||||
|
|
||||||
|
from keystoneauth1.identity import generic
|
||||||
|
from keystoneauth1 import session as keystone_session
|
||||||
|
|
||||||
|
|
||||||
|
auth = generic.Password(
|
||||||
|
auth_url=shell.env('OS_AUTH_URL'),
|
||||||
|
username=shell.env('OS_USERNAME'),
|
||||||
|
password=shell.env('OS_PASSWORD'),
|
||||||
|
project_name=shell.env('OS_PROJECT_NAME'),
|
||||||
|
project_domain_id=shell.env('OS_PROJECT_DOMAIN_ID'),
|
||||||
|
user_domain_id=shell.env('OS_USER_DOMAIN_ID'))
|
||||||
|
|
||||||
|
session = keystone_session.Session(auth=auth)
|
||||||
|
|
||||||
|
client = client.Client(session=session)
|
||||||
|
|
||||||
|
logging.basicConfig()
|
||||||
|
LOG = logging.getLogger('fixleadingzeros')
|
||||||
|
|
||||||
|
|
||||||
|
def find_bad_recordsets():
|
||||||
|
bad_recordsets = {}
|
||||||
|
LOG.debug("Looking for all A recordsets")
|
||||||
|
recordsets = client.recordsets.list_all_zones(criterion={'type': 'A', })
|
||||||
|
LOG.debug("Found %d A recordsets", len(recordsets))
|
||||||
|
LOG.debug("Filtering recordsets")
|
||||||
|
for recordset in recordsets:
|
||||||
|
for record in recordset['records']:
|
||||||
|
try:
|
||||||
|
inet_aton(record)
|
||||||
|
except dns.exception.SyntaxError:
|
||||||
|
bad_recordsets[recordset['id']] = recordset
|
||||||
|
LOG.debug("Found %d A invaild recordsets", len(bad_recordsets))
|
||||||
|
return bad_recordsets
|
||||||
|
|
||||||
|
|
||||||
|
def show_recordsets(recordsets):
|
||||||
|
for rs in recordsets:
|
||||||
|
LOG.info(
|
||||||
|
("%(name)s - %(records)s - Zone ID: %(zone_id)s - "
|
||||||
|
"Project ID: %(project_id)s ") % recordsets[rs])
|
||||||
|
|
||||||
|
|
||||||
|
def fix_bad_recordsets(bad_recordsets):
|
||||||
|
LOG.debug("Removing leading zeros in IPv4 addresses")
|
||||||
|
for rs in bad_recordsets:
|
||||||
|
new_records = []
|
||||||
|
for ip in bad_recordsets[rs]['records']:
|
||||||
|
new_records.append(
|
||||||
|
str(netaddr.IPAddress(ip, flags=netaddr.ZEROFILL).ipv4())
|
||||||
|
)
|
||||||
|
bad_recordsets[rs]['records'] = new_records
|
||||||
|
return bad_recordsets
|
||||||
|
|
||||||
|
|
||||||
|
def update_recordsets(recordsets):
|
||||||
|
LOG.info("Updating recordsets")
|
||||||
|
for rs in recordsets:
|
||||||
|
LOG.debug(("Updating %(name)s - %(records)s - Zone ID: %(zone_id)s - "
|
||||||
|
"Project ID: %(project_id)s ") % recordsets[rs])
|
||||||
|
client.recordsets.update(
|
||||||
|
recordsets[rs]['zone_id'],
|
||||||
|
recordsets[rs]['id'],
|
||||||
|
{'records': recordsets[rs]['records']}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Fix any recordsets that have leading zeros in A records')
|
||||||
|
parser.add_argument('-v', '--verbose', action='store_true',
|
||||||
|
help='verbose output')
|
||||||
|
parser.add_argument('-d', '--dry-run', action='store_true',
|
||||||
|
help='do not modify records, just log bad records')
|
||||||
|
parser.add_argument('-a', '--all-projects', action='store_true',
|
||||||
|
help="Run on all projects")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.verbose:
|
||||||
|
LOG.setLevel(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
LOG.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
if args.all_projects:
|
||||||
|
client.session.all_projects = True
|
||||||
|
|
||||||
|
bad_recordsets = find_bad_recordsets()
|
||||||
|
|
||||||
|
LOG.info("Bad recordsets")
|
||||||
|
show_recordsets(bad_recordsets)
|
||||||
|
|
||||||
|
fixed_recordsets = fix_bad_recordsets(bad_recordsets)
|
||||||
|
LOG.info("Fixed recordsets")
|
||||||
|
show_recordsets(fixed_recordsets)
|
||||||
|
|
||||||
|
if not args.dry_run:
|
||||||
|
update_recordsets(fixed_recordsets)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
@ -12,7 +12,8 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
from dns import ipv4
|
||||||
|
import dns.exception
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -141,6 +142,11 @@ class ObjectField(ovoo_fields.ObjectField):
|
|||||||
class IPV4AddressField(ovoo_fields.IPV4AddressField):
|
class IPV4AddressField(ovoo_fields.IPV4AddressField):
|
||||||
|
|
||||||
def coerce(self, obj, attr, value):
|
def coerce(self, obj, attr, value):
|
||||||
|
try:
|
||||||
|
# make sure that DNS Python agrees that it is a valid IP address
|
||||||
|
ipv4.inet_aton(str(value))
|
||||||
|
except dns.exception.SyntaxError:
|
||||||
|
raise ValueError()
|
||||||
value = super(IPV4AddressField, self).coerce(obj, attr, value)
|
value = super(IPV4AddressField, self).coerce(obj, attr, value)
|
||||||
# we use this field as a string, not need a netaddr.IPAdress
|
# we use this field as a string, not need a netaddr.IPAdress
|
||||||
# as oslo.versionedobjects is using
|
# as oslo.versionedobjects is using
|
||||||
|
32
designate/tests/unit/test_objects/test_rrdata_a.py
Normal file
32
designate/tests/unit/test_objects/test_rrdata_a.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Copyright 2018 Verizon Wireless
|
||||||
|
#
|
||||||
|
# Author: Graham Hayes <gr@ham.ie>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import oslotest.base
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from designate import exceptions
|
||||||
|
|
||||||
|
from designate import objects
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class RRDataATest(oslotest.base.BaseTestCase):
|
||||||
|
|
||||||
|
def test_reject_leading_zeros(self):
|
||||||
|
record = objects.A(data='10.0.001.1')
|
||||||
|
with testtools.ExpectedException(exceptions.InvalidObject):
|
||||||
|
record.validate()
|
Loading…
Reference in New Issue
Block a user