def _prepare_query(self) -> RPSLDatabaseQuery: """Prepare an RPSLDatabaseQuery by applying relevant sources/class filters.""" query = RPSLDatabaseQuery() if self.sources: query.sources(self.sources) else: default = get_setting('sources_default') if default: query.sources(list(default)) if self.object_classes: query.object_classes(self.object_classes) return query
def _prepare_query(self, column_names=None, ordered_by_sources=True) -> RPSLDatabaseQuery: """Prepare an RPSLDatabaseQuery by applying relevant sources/class filters.""" query = RPSLDatabaseQuery(column_names, ordered_by_sources) if self.sources: query.sources(self.sources) else: default = get_setting('sources_default') if default: query.sources(list(default)) if self.object_classes: query.object_classes(self.object_classes) return query
def _prepare_query(self, column_names=None, ordered_by_sources=True) -> RPSLDatabaseQuery: """Prepare an RPSLDatabaseQuery by applying relevant sources/class filters.""" query = RPSLDatabaseQuery(column_names, ordered_by_sources) if self.sources: query.sources(self.sources) if self.object_class_filter: query.object_classes(self.object_class_filter) if self.rpki_invalid_filter_enabled: query.rpki_status([RPKIStatus.not_found, RPKIStatus.valid]) if self.out_scope_filter_enabled: query.scopefilter_status([ScopeFilterStatus.in_scope]) self.object_class_filter = [] return query
def _check_mntners(self, mntner_pk_list: List[str], source: str) -> Tuple[bool, List[RPSLMntner]]: """ Check whether authentication passes for a list of maintainers. Returns True if at least one of the mntners in mntner_list passes authentication, given self.passwords and self.keycert_obj_pk. Updates and checks self.passed_mntner_cache to prevent double checking of maintainers. """ mntner_pk_set = set(mntner_pk_list) mntner_objs: List[RPSLMntner] = [m for m in self._mntner_db_cache if m.pk() in mntner_pk_set and m.source() == source] mntner_pks_to_resolve: Set[str] = mntner_pk_set - {m.pk() for m in mntner_objs} if mntner_pks_to_resolve: query = RPSLDatabaseQuery().sources([source]) query = query.object_classes(['mntner']).rpsl_pks(mntner_pks_to_resolve) results = list(self.database_handler.execute_query(query)) retrieved_mntner_objs: List[RPSLMntner] = [rpsl_object_from_text(r['object_text']) for r in results] # type: ignore self._mntner_db_cache.update(retrieved_mntner_objs) mntner_objs += retrieved_mntner_objs for mntner_name in mntner_pk_list: if mntner_name in self._pre_approved: return True, mntner_objs for mntner_obj in mntner_objs: if mntner_obj.verify_auth(self.passwords, self.keycert_obj_pk): return True, mntner_objs return False, mntner_objs
def _prepare_query(self, column_names=None, ordered_by_sources=True) -> RPSLDatabaseQuery: """Prepare an RPSLDatabaseQuery by applying relevant sources/class filters.""" query = RPSLDatabaseQuery(column_names, ordered_by_sources) if self.sources and self.sources != self.all_valid_sources: query.sources(self.sources) else: default = list(get_setting('sources_default', [])) if default: query.sources(list(default)) if self.object_classes: query.object_classes(self.object_classes) if self.rpki_invalid_filter_enabled: query.rpki_status([RPKIStatus.not_found, RPKIStatus.valid]) return query
def _retrieve_existing_version(self): """ Retrieve the current version of this object, if any, and store it in rpsl_obj_current. Update self.status appropriately. """ query = RPSLDatabaseQuery().sources([self.rpsl_obj_new.source()]) query = query.object_classes([self.rpsl_obj_new.rpsl_object_class ]).rpsl_pk(self.rpsl_obj_new.pk()) results = list(self.database_handler.execute_query(query)) if not results: self.request_type = UpdateRequestType.CREATE logger.debug( f'{id(self)}: Did not find existing version for object {self.rpsl_obj_new}, request is CREATE' ) elif len(results) == 1: self.request_type = UpdateRequestType.MODIFY self.rpsl_obj_current = rpsl_object_from_text( results[0]['object_text'], strict_validation=False) logger.debug(f'{id(self)}: Retrieved existing version for object ' f'{self.rpsl_obj_current}, request is MODIFY/DELETE') else: # pragma: no cover # This should not be possible, as rpsl_pk/source are a composite unique value in the database scheme. # Therefore, a query should not be able to affect more than one row. affected_pks = ', '.join([r['pk'] for r in results]) msg = f'{id(self)}: Attempted to retrieve current version of object {self.rpsl_obj_new.pk()}/' msg += f'{self.rpsl_obj_new.source()}, but multiple ' msg += f'objects were found, internal pks found: {affected_pks}' logger.error(msg) raise ValueError(msg)
def _resolve_subquery(rpsl_object, info: GraphQLResolveInfo, object_classes: List[str], pk_field: str, sticky_source=True): """ Resolve a subquery, like techCobjs, on an RPSL object, considering a number of object classes, extracting the PK from pk_field. If sticky_source is set, the referred object must be from the same source. """ pks = rpsl_object.get(pk_field) if not pks: return [] if not isinstance(pks, list): pks = [pks] query = RPSLDatabaseQuery( column_names=_columns_for_graphql_selection(info), ordered_by_sources=False, enable_ordering=False) query.object_classes(object_classes).rpsl_pks(pks) if sticky_source: query.sources([rpsl_object['source']]) return _rpsl_db_query_to_graphql_out(query, info)
def validate_all_rpsl_objects(self, database_handler: DatabaseHandler) -> \ Tuple[List[Dict[str, str]], List[Dict[str, str]], List[Dict[str, str]]]: """ Apply the scope filter to all relevant objects. Retrieves all routes from the DB, and aggregates the validation results. Returns a tuple of three sets: - one with routes that should be set to status in_scope, but are not now - one with routes that should be set to status out_scope_as, but are not now - one with routes that should be set to status out_scope_prefix, but are not now Each object is recorded as a dict, which has the fields shown in "columns" below. Objects where their current status in the DB matches the new validation result, are not included in the return value. """ columns = [ 'rpsl_pk', 'ip_first', 'prefix_length', 'asn_first', 'source', 'object_class', 'object_text', 'scopefilter_status' ] objs_changed: Dict[ScopeFilterStatus, List[Dict[str, str]]] = defaultdict(list) q = RPSLDatabaseQuery(column_names=columns, enable_ordering=False) q = q.object_classes(['route', 'route6']) results = database_handler.execute_query(q) for result in results: current_status = result['scopefilter_status'] result['old_status'] = current_status prefix = None if result['ip_first']: prefix = IP(result['ip_first'] + '/' + str(result['prefix_length'])) new_status, _ = self._validate_rpsl_data( result['source'], result['object_class'], prefix, result['asn_first'], ) if new_status != current_status: result['scopefilter_status'] = new_status objs_changed[new_status].append(result) return (objs_changed[ScopeFilterStatus.in_scope], objs_changed[ScopeFilterStatus.out_scope_as], objs_changed[ScopeFilterStatus.out_scope_prefix])
def validate_all_routes(self, sources: List[str]=None) -> \ Tuple[List[Dict[str, str]], List[Dict[str, str]], List[Dict[str, str]]]: """ Validate all RPSL route/route6 objects. Retrieves all routes from the DB, and aggregates the validation results. Returns a tuple of three sets of RPSL route(6)'s: - one with routes that should be set to status VALID, but are not now - one with routes that should be set to status INVALID, but are not now - one with routes that should be set to status UNKNOWN, but are not now Each route is recorded as a dict, which has the fields shown in "columns" below. Routes where their current validation status in the DB matches the new validation result, are not included in the return value. """ columns = [ 'rpsl_pk', 'ip_first', 'prefix_length', 'asn_first', 'source', 'object_class', 'object_text', 'rpki_status' ] q = RPSLDatabaseQuery(column_names=columns, enable_ordering=False) q = q.object_classes(['route', 'route6']) if sources: q = q.sources(sources) routes = self.database_handler.execute_query(q) objs_changed: Dict[RPKIStatus, List[Dict[str, str]]] = defaultdict(list) for result in routes: # RPKI_IRR_PSEUDO_SOURCE objects are ROAs, and don't need validation. if result['source'] == RPKI_IRR_PSEUDO_SOURCE: continue current_status = result['rpki_status'] result['old_status'] = current_status new_status = self.validate_route(result['ip_first'], result['prefix_length'], result['asn_first'], result['source']) if new_status != current_status: result['rpki_status'] = new_status objs_changed[new_status].append(result) return objs_changed[RPKIStatus.valid], objs_changed[ RPKIStatus.invalid], objs_changed[RPKIStatus.not_found]
def resolve_rpsl_objects(_, info: GraphQLResolveInfo, **kwargs): """ Resolve a `rpslObjects` query. This query has a considerable number of parameters, each of which is applied to an RPSL database query. """ low_specificity_kwargs = { 'object_class', 'rpki_status', 'scope_filter_status', 'sources', 'sql_trace' } # A query is sufficiently specific if it has other fields than listed above, # except that rpki_status is sufficient if it is exclusively selecting on # valid or invalid. low_specificity = all([ not (set(kwargs.keys()) - low_specificity_kwargs), kwargs.get('rpki_status', []) not in [[RPKIStatus.valid], [RPKIStatus.invalid]], ]) if low_specificity: raise ValueError('Your query must be more specific.') if kwargs.get('sql_trace'): info.context['sql_trace'] = True query = RPSLDatabaseQuery( column_names=_columns_for_graphql_selection(info), ordered_by_sources=False, enable_ordering=False) if 'record_limit' in kwargs: query.limit(kwargs['record_limit']) if 'rpsl_pk' in kwargs: query.rpsl_pks(kwargs['rpsl_pk']) if 'object_class' in kwargs: query.object_classes(kwargs['object_class']) if 'asn' in kwargs: query.asns_first(kwargs['asn']) if 'text_search' in kwargs: query.text_search(kwargs['text_search']) if 'rpki_status' in kwargs: query.rpki_status(kwargs['rpki_status']) else: query.rpki_status([RPKIStatus.not_found, RPKIStatus.valid]) if 'scope_filter_status' in kwargs: query.scopefilter_status(kwargs['scope_filter_status']) else: query.scopefilter_status([ScopeFilterStatus.in_scope]) all_valid_sources = set(get_setting('sources', {}).keys()) if get_setting('rpki.roa_source'): all_valid_sources.add(RPKI_IRR_PSEUDO_SOURCE) sources_default = set(get_setting('sources_default', [])) if 'sources' in kwargs: query.sources(kwargs['sources']) elif sources_default and sources_default != all_valid_sources: query.sources(list(sources_default)) # All other parameters are generic lookup fields, like `members` for attr, value in kwargs.items(): attr = attr.replace('_', '-') if attr in lookup_fields: query.lookup_attrs_in([attr], value) ip_filters = [ 'ip_exact', 'ip_less_specific', 'ip_more_specific', 'ip_less_specific_one_level', 'ip_any' ] for ip_filter in ip_filters: if ip_filter in kwargs: getattr(query, ip_filter)(IP(kwargs[ip_filter])) return _rpsl_db_query_to_graphql_out(query, info)
def validate_all_rpsl_objects(self, database_handler: DatabaseHandler) -> \ Tuple[List[Dict[str, str]], List[Dict[str, str]], List[Dict[str, str]]]: """ Apply the scope filter to all relevant objects. Retrieves all routes from the DB, and aggregates the validation results. Returns a tuple of three sets: - one with routes that should be set to status in_scope, but are not now - one with routes that should be set to status out_scope_as, but are not now - one with routes that should be set to status out_scope_prefix, but are not now Each object is recorded as a dict, which has the fields shown in "columns" below. Objects where their current status in the DB matches the new validation result, are not included in the return value. """ columns = [ 'pk', 'rpsl_pk', 'prefix', 'asn_first', 'source', 'object_class', 'scopefilter_status', 'rpki_status' ] objs_changed: Dict[ScopeFilterStatus, List[Dict[str, str]]] = defaultdict(list) q = RPSLDatabaseQuery(column_names=columns, enable_ordering=False) q = q.object_classes(['route', 'route6', 'aut-num']) results = database_handler.execute_query(q) for result in results: current_status = result['scopefilter_status'] result['old_status'] = current_status prefix = None if result.get('prefix'): prefix = IP(result['prefix']) new_status, _ = self._validate_rpsl_data( result['source'], result['object_class'], prefix, result['asn_first'], ) if new_status != current_status: result['scopefilter_status'] = new_status objs_changed[new_status].append(result) # Object text is only retrieved for objects with state changes pks_to_enrich = [ obj['pk'] for objs in objs_changed.values() for obj in objs ] query = RPSLDatabaseQuery(['pk', 'object_text'], enable_ordering=False).pks(pks_to_enrich) rows_per_pk = { row['pk']: row for row in database_handler.execute_query(query) } for rpsl_objs in objs_changed.values(): for rpsl_obj in rpsl_objs: rpsl_obj.update(rows_per_pk[rpsl_obj['pk']]) return (objs_changed[ScopeFilterStatus.in_scope], objs_changed[ScopeFilterStatus.out_scope_as], objs_changed[ScopeFilterStatus.out_scope_prefix])
def _init_related_object_query(rpsl_object_class: str, rpsl_obj_new: RPSLObject) -> RPSLDatabaseQuery: query = RPSLDatabaseQuery().sources([rpsl_obj_new.source()]) query = query.object_classes([rpsl_object_class]) return query.first_only()
def init_query(rpsl_object_class: str) -> RPSLDatabaseQuery: query = RPSLDatabaseQuery().sources([rpsl_obj_new.source()]) query = query.object_classes([rpsl_object_class]) return query.first_only()