From c88372fc2003e6af842a2c32c9195f471aa0fb16 Mon Sep 17 00:00:00 2001 From: Sasha Romijn Date: Thu, 17 Jun 2021 22:14:20 +0200 Subject: [PATCH] Ref #499 - Add support for integer AS in ROA file --- irrd/rpki/importer.py | 14 +++++++------- irrd/rpki/tests/test_importer.py | 16 ++++++++++++++-- irrd/rpki/tests/test_validators.py | 14 +++++++------- irrd/utils/tests/test_validators.py | 1 + irrd/utils/validators.py | 10 ++++++---- 5 files changed, 35 insertions(+), 20 deletions(-) diff --git a/irrd/rpki/importer.py b/irrd/rpki/importer.py index 83299789b..09a54d15a 100644 --- a/irrd/rpki/importer.py +++ b/irrd/rpki/importer.py @@ -39,9 +39,9 @@ class ROADataImporter: def __init__(self, rpki_json_str: str, slurm_json_str: Optional[str], database_handler: DatabaseHandler): self.roa_objs: List[ROA] = [] - self._filtered_asns: Set[str] = set() + self._filtered_asns: Set[int] = set() self._filtered_prefixes: IPSet = IPSet() - self._filtered_combined: Dict[str, IPSet] = defaultdict(IPSet) + self._filtered_combined: Dict[int, IPSet] = defaultdict(IPSet) self._load_roa_dicts(rpki_json_str) if slurm_json_str: @@ -51,7 +51,7 @@ def __init__(self, rpki_json_str: str, slurm_json_str: Optional[str], for roa_dict in self._roa_dicts: try: - asn = roa_dict['asn'] + _, asn = parse_as_number(roa_dict['asn'], permit_plain=True) prefix = IP(roa_dict['prefix']) ta = roa_dict['ta'] if ta != SLURM_TRUST_ANCHOR: @@ -116,11 +116,11 @@ def _load_slurm(self, slurm_json_str: str): filters = slurm.get('validationOutputFilters', {}).get('prefixFilters', []) for item in filters: if 'asn' in item and 'prefix' not in item: - self._filtered_asns.add('AS' + str(item['asn'])) + self._filtered_asns.add(int(item['asn'])) if 'asn' not in item and 'prefix' in item: self._filtered_prefixes.add(IP(item['prefix'])) if 'asn' in item and 'prefix' in item: - self._filtered_combined['AS' + str(item['asn'])].add(IP(item['prefix'])) + self._filtered_combined[int(item['asn'])].add(IP(item['prefix'])) assertions = slurm.get('locallyAddedAssertions', {}).get('prefixAssertions', []) for assertion in assertions: @@ -142,11 +142,11 @@ class ROA: This is used when (re-)importing all ROAs, to save the data to the DB, and by the BulkRouteROAValidator when validating all existing routes. """ - def __init__(self, prefix: IP, asn: str, max_length: str, trust_anchor: str): + def __init__(self, prefix: IP, asn: int, max_length: str, trust_anchor: str): try: self.prefix = prefix self.prefix_str = str(prefix) - _, self.asn = parse_as_number(asn) + self.asn = asn self.max_length = int(max_length) self.trust_anchor = trust_anchor except ValueError as ve: diff --git a/irrd/rpki/tests/test_importer.py b/irrd/rpki/tests/test_importer.py index 0310a0f9f..0364e1f08 100644 --- a/irrd/rpki/tests/test_importer.py +++ b/irrd/rpki/tests/test_importer.py @@ -29,7 +29,7 @@ def test_valid_process(self, monkeypatch, mock_scopefilter): rpki_data = ujson.dumps({ "roas": [{ - "asn": "AS64496", + "asn": "64496", "prefix": "192.0.2.0/24", "maxLength": 26, "ta": "APNIC RPKI Root" @@ -40,7 +40,7 @@ def test_valid_process(self, monkeypatch, mock_scopefilter): "ta": "RIPE NCC RPKI Root" }, { # Filtered out by SLURM due to origin - "asn": "AS64498", + "asn": "64498", "prefix": "192.0.2.0/24", "maxLength": 32, "ta": "APNIC RPKI Root" @@ -225,6 +225,18 @@ def test_invalid_data_in_roa(self, monkeypatch, mock_scopefilter): ROADataImporter(data, None, mock_dh) assert 'Invalid ROA: prefix size 24 is smaller than max length 22' in str(rpe.value) + data = ujson.dumps({ + "roas": [{ + "asn": "AS64496", + "prefix": "192.0.2.0/24", + "maxLength": 'xx', + "ta": "APNIC RPKI Root" + }] + }) + with pytest.raises(ROAParserException) as rpe: + ROADataImporter(data, None, mock_dh) + assert 'xx' in str(rpe.value) + assert flatten_mock_calls(mock_dh) == [] def test_invalid_slurm_version(self, monkeypatch, mock_scopefilter): diff --git a/irrd/rpki/tests/test_validators.py b/irrd/rpki/tests/test_validators.py index 9ce260efa..bce957a91 100644 --- a/irrd/rpki/tests/test_validators.py +++ b/irrd/rpki/tests/test_validators.py @@ -124,20 +124,20 @@ def test_validate_routes_from_roa_objs(self, monkeypatch, config_override): roas = [ # Valid for pk_route_v4_d0_l25 and pk_route_v4_d0_l24 # - the others have incorrect origin or are too small. - ROA(IP('192.0.2.0/24'), 'AS65546', '28', 'TEST TA'), + ROA(IP('192.0.2.0/24'), 65546, '28', 'TEST TA'), # Matches the origin of pk_route_v4_d128_l25, # but not max_length. - ROA(IP('192.0.2.0/24'), 'AS65547', '24', 'TEST TA'), + ROA(IP('192.0.2.0/24'), 65547, '24', 'TEST TA'), # Matches pk_route_v6, but not max_length. - ROA(IP('2001:db8::/30'), 'AS65547', '30', 'TEST TA'), + ROA(IP('2001:db8::/30'), 65547, '30', 'TEST TA'), # Matches pk_route_v6, but not on origin. - ROA(IP('2001:db8::/32'), 'AS65548', '32', 'TEST TA'), + ROA(IP('2001:db8::/32'), 65548, '32', 'TEST TA'), # Matches pk_route_v6 - ROA(IP('2001:db8::/32'), 'AS65547', '64', 'TEST TA'), + ROA(IP('2001:db8::/32'), 65547, '64', 'TEST TA'), # Matches no routes, no effect - ROA(IP('203.0.113.0/32'), 'AS65547', '32', 'TEST TA'), + ROA(IP('203.0.113.0/32'), 65547, '32', 'TEST TA'), # AS0 can not match - ROA(IP('203.0.113.1/32'), 'AS0', '32', 'TEST TA'), + ROA(IP('203.0.113.1/32'), 0, '32', 'TEST TA'), ] result = BulkRouteROAValidator(mock_dh, roas).validate_all_routes(sources=['TEST1']) new_valid_objs, new_invalid_objs, new_unknown_objs = result diff --git a/irrd/utils/tests/test_validators.py b/irrd/utils/tests/test_validators.py index d8071c161..4cf18d810 100644 --- a/irrd/utils/tests/test_validators.py +++ b/irrd/utils/tests/test_validators.py @@ -8,6 +8,7 @@ def test_validate_as_number(): assert parse_as_number('AS012345') == ('AS12345', 12345) assert parse_as_number('as4294967290') == ('AS4294967290', 4294967290) + assert parse_as_number('012345', permit_plain=True) == ('AS12345', 12345) with raises(ValidationError) as ve: parse_as_number('12345') diff --git a/irrd/utils/validators.py b/irrd/utils/validators.py index bc84c0623..e5ef06c29 100644 --- a/irrd/utils/validators.py +++ b/irrd/utils/validators.py @@ -3,16 +3,18 @@ import pydantic -def parse_as_number(value: str) -> Tuple[str, int]: +def parse_as_number(value: str, permit_plain=False) -> Tuple[str, int]: """Validate and clean an AS number. Returns it in ASxxxx and numeric format.""" value = value.upper() - if not value.startswith('AS'): + if not permit_plain and not value.startswith('AS'): raise ValidationError(f'Invalid AS number {value}: must start with "AS"') - if not value[2:].isnumeric(): + start_index = 2 if value.startswith('AS') else 0 + + if not value[start_index:].isnumeric(): raise ValidationError(f'Invalid AS number {value}: number part is not numeric') - value_int = int(value[2:]) + value_int = int(value[start_index:]) if value_int > 4294967295: raise ValidationError(f'Invalid AS number {value}: maximum value is 4294967295')