diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 8d3867af11..27655758d6 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -2,6 +2,7 @@ import datetime from typing import Dict, List, Optional, Any, Union +import loguru from implicitdict import ImplicitDict, StringBasedDateTime import s2sphere from uas_standards.astm.f3411 import v19, v22a @@ -1163,8 +1164,13 @@ def all_flights( session: UTMClientSession, dss_base_url: str = "", enhanced_details: bool = False, - server_id: Optional[str] = None, + dss_server_id: Optional[str] = None, + flights_urls_to_server_id: Optional[Dict[str, str]] = None, ) -> FetchedFlights: + + if flights_urls_to_server_id is None: + flights_urls_to_server_id = {} + t = datetime.datetime.utcnow() isa_list = isas( geo.get_latlngrect_vertices(area), @@ -1173,7 +1179,7 @@ def all_flights( rid_version, session, dss_base_url, - server_id=server_id, + server_id=dss_server_id, ) uss_flight_queries: Dict[str, FetchedUSSFlights] = {} @@ -1185,7 +1191,9 @@ def all_flights( include_recent_positions, rid_version, session, - server_id=server_id, + # TODO we need an exact match of the flight url for this to work here, + # check if this is a reasonable expectation + server_id=flights_urls_to_server_id.get(flights_url), ) uss_flight_queries[flights_url] = flights_for_url @@ -1197,7 +1205,7 @@ def all_flights( enhanced_details, rid_version, session, - server_id=server_id, + server_id=flights_urls_to_server_id.get(flights_url), ) uss_flight_details_queries[flight.id] = details diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment.yaml index 8835e64f9d..5dffe32d69 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment.yaml @@ -34,6 +34,7 @@ net_rid: service_providers: - participant_id: uss2 injection_base_url: http://v19.ridsp.uss2.localutm/ridsp/injection + flights_url: http://v19.ridsp.uss2.localutm/mock/ridsp/v1/uss/flights local_debug: true netrid_service_providers_v22a: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -44,6 +45,7 @@ net_rid: service_providers: - participant_id: uss1 injection_base_url: http://v22a.ridsp.uss1.localutm/ridsp/injection + flights_url: http://v22a.ridsp.uss1.localutm/mock/ridsp/v2/uss/flights local_debug: true netrid_observers_v19: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -54,6 +56,7 @@ net_rid: observers: - participant_id: uss3 observation_base_url: http://v19.riddp.uss3.localutm/riddp/observation + flights_url: http://v19.ridsp.uss2.localutm/mock/ridsp/v1/uss/flights local_debug: true netrid_observers_v22a: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json @@ -64,6 +67,7 @@ net_rid: observers: - participant_id: uss1 observation_base_url: http://v22a.riddp.uss1.localutm/riddp/observation + flights_url: http://v22a.ridsp.uss1.localutm/mock/ridsp/v2/uss/flights local_debug: true netrid_dss_instances_v19: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json diff --git a/monitoring/uss_qualifier/resources/netrid/observers.py b/monitoring/uss_qualifier/resources/netrid/observers.py index 2448df2c79..10ca660046 100644 --- a/monitoring/uss_qualifier/resources/netrid/observers.py +++ b/monitoring/uss_qualifier/resources/netrid/observers.py @@ -18,6 +18,7 @@ class RIDSystemObserver(object): participant_id: str base_url: str + flights_url: str session: infrastructure.UTMClientSession local_debug: bool @@ -25,12 +26,14 @@ def __init__( self, participant_id: str, base_url: str, + flights_url: str, auth_adapter: infrastructure.AuthAdapter, local_debug: bool, ): self.session = UTMClientSession(base_url, auth_adapter) self.participant_id = participant_id self.base_url = base_url + self.flights_url = flights_url self.local_debug = local_debug def observe_system( @@ -100,6 +103,9 @@ class ObserverConfiguration(ImplicitDict): observation_base_url: str """Base URL for the observer's implementation of the interfaces/automated-testing/rid/observation.yaml API""" + flights_url: str + """URL that the SP to which this observer belongs to will register at the DSS for exposing flights information.""" + local_debug: Optional[bool] """Whether this Observer instance is running locally for debugging or development purposes. Mostly used for relaxing constraints around encryption. @@ -123,6 +129,7 @@ def __init__( RIDSystemObserver( o.participant_id, o.observation_base_url, + o.flights_url, auth_adapter.adapter, o.local_debug, ) diff --git a/monitoring/uss_qualifier/resources/netrid/service_providers.py b/monitoring/uss_qualifier/resources/netrid/service_providers.py index 76c02a981f..8f20eafb7d 100644 --- a/monitoring/uss_qualifier/resources/netrid/service_providers.py +++ b/monitoring/uss_qualifier/resources/netrid/service_providers.py @@ -20,6 +20,12 @@ class ServiceProviderConfiguration(ImplicitDict): injection_base_url: str """Base URL for the Service Provider's implementation of the interfaces/automated-testing/rid/injection.yaml API""" + # TODO confirm that: + # 1, this is fine to have in the configuration? + # 2, we should instaed use a list of flight urls here? SPs could in theory use multiple ones + flights_url: str + """URL the Service Provider will register with the DSS for providing flights information.""" + local_debug: Optional[bool] """Whether this Service Provider instance is running locally for debugging or development purposes. Mostly used for relaxing constraints around encryption. @@ -41,25 +47,30 @@ class NetRIDServiceProvidersSpecification(ImplicitDict): class NetRIDServiceProvider(object): participant_id: str - base_url: str - client: infrastructure.UTMClientSession + injection_base_url: str + flights_url: str + flights_injection_client: infrastructure.UTMClientSession local_debug: bool def __init__( self, participant_id: str, - base_url: str, + injection_base_url: str, + flights_url: str, auth_adapter: infrastructure.AuthAdapter, local_debug: bool, ): self.participant_id = participant_id - self.base_url = base_url - self.client = infrastructure.UTMClientSession(base_url, auth_adapter) + self.injection_base_url = injection_base_url + self.flights_url = flights_url + self.flights_injection_client = infrastructure.UTMClientSession( + injection_base_url, auth_adapter + ) self.local_debug = local_debug def submit_test(self, request: CreateTestParameters, test_id: str) -> fetch.Query: return fetch.query_and_describe( - self.client, + self.flights_injection_client, "PUT", url=f"/tests/{test_id}", json=request, @@ -69,7 +80,7 @@ def submit_test(self, request: CreateTestParameters, test_id: str) -> fetch.Quer def delete_test(self, test_id: str, version: str) -> fetch.Query: return fetch.query_and_describe( - self.client, + self.flights_injection_client, "DELETE", url=f"/tests/{test_id}/{version}", scope=SCOPE_RID_QUALIFIER_INJECT, @@ -87,10 +98,11 @@ def __init__( ): self.service_providers = [ NetRIDServiceProvider( - s.participant_id, - s.injection_base_url, - auth_adapter.adapter, - s.get("local_debug", False), + participant_id=s.participant_id, + injection_base_url=s.injection_base_url, + flights_url=s.flights_url, + auth_adapter=auth_adapter.adapter, + local_debug=s.get("local_debug", False), ) for s in specification.service_providers ] diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py index 9edd3b416e..b61f0d9f1e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/aggregate_checks.py @@ -51,7 +51,10 @@ def __init__( # identify SPs and observers by their base URL self._participants_by_base_url.update( - {sp.base_url: sp.participant_id for sp in self._service_providers} + {sp.injection_base_url: sp.participant_id for sp in self._service_providers} + ) + self._participants_by_base_url.update( + {sp.flights_url: sp.participant_id for sp in self._service_providers} ) self._participants_by_base_url.update( {dp.base_url: dp.participant_id for dp in self._observers} @@ -101,7 +104,7 @@ def run(self): for sp in self._service_providers: self.record_note( "service_providers", - f"configured service providers: {sp.participant_id} - {sp.base_url}", + f"configured service providers: {sp.participant_id} - {sp.injection_base_url}", ) for o in self._observers: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py index a6fada8caf..504f58becb 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common/misbehavior.py @@ -1,23 +1,16 @@ import time import traceback -import uuid -from typing import List +from typing import List, Dict import arrow import s2sphere -from implicitdict import ImplicitDict from loguru import logger from requests.exceptions import RequestException -from uas_standards.interuss.automated_testing.rid.v1.injection import ChangeTestResponse from monitoring.monitorlib import fetch from monitoring.monitorlib.fetch import rid from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.rid import RIDVersion -from monitoring.monitorlib.rid_automated_testing.injection_api import ( - CreateTestParameters, -) -from monitoring.monitorlib.rid_automated_testing.injection_api import TestFlight from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstancesResource from monitoring.uss_qualifier.resources.netrid import ( @@ -25,18 +18,11 @@ NetRIDServiceProviders, EvaluationConfigurationResource, ) -from monitoring.uss_qualifier.scenarios.astm.netrid import display_data_evaluator from monitoring.uss_qualifier.scenarios.astm.netrid.common import nominal_behavior -from monitoring.uss_qualifier.scenarios.astm.netrid.injected_flight_collection import ( - InjectedFlightCollection, -) from monitoring.uss_qualifier.scenarios.astm.netrid.injection import ( InjectedFlight, InjectedTest, ) -from monitoring.uss_qualifier.scenarios.astm.netrid.virtual_observer import ( - VirtualObserver, -) from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario @@ -50,6 +36,7 @@ class Misbehavior(GenericTestScenario): _evaluation_configuration: EvaluationConfigurationResource _injected_flights: List[InjectedFlight] _injected_tests: List[InjectedTest] + _flights_urls_to_participant_id: Dict[str, str] def __init__( self, @@ -70,6 +57,11 @@ def __init__( ) self._dss = dss_pool.dss_instances[0] + self._flights_urls_to_participant_id = { + sp.flights_url: sp.participant_id + for sp in self._service_providers.service_providers + } + @property def _rid_version(self) -> RIDVersion: raise NotImplementedError( @@ -167,52 +159,56 @@ def _evaluate_and_test_authentication( no flights were yet returned by the authenticated queries. """ - with self.check("Missing credentials") as check: - # We grab all flights from the SP's. This is authenticated - # and is expected to succeed - sp_observation = rid.all_flights( - rect, - include_recent_positions=True, - get_details=True, - rid_version=self._rid_version, - session=self._dss.client, - server_id=self._dss.participant_id, - ) - # We fish out the queries that were used to grab the flights from the SP, - # and attempt to re-query without credentials. This should fail. + # We grab all flights from the SP's (which we know how to reach by first querying the DSS). + # This is authenticated and is expected to succeed + sp_observation = rid.all_flights( + rect, + include_recent_positions=True, + get_details=True, + rid_version=self._rid_version, + session=self._dss.client, + dss_server_id=self._dss.participant_id, + flights_urls_to_server_id=self._flights_urls_to_participant_id, + ) - unauthenticated_session = UTMClientSession( - prefix_url=self._dss.client.get_prefix_url(), - auth_adapter=None, - timeout_seconds=self._dss.client.timeout_seconds, - ) + # We fish out the queries that were used to grab the flights from the SP, + # and attempt to re-query without credentials. This should fail. + unauthenticated_session = UTMClientSession( + prefix_url=self._dss.client.get_prefix_url(), + auth_adapter=None, + timeout_seconds=self._dss.client.timeout_seconds, + ) - queries_to_repeat = list(sp_observation.uss_flight_queries.values()) + list( - sp_observation.uss_flight_details_queries.values() - ) + queries_to_repeat = list(sp_observation.uss_flight_queries.values()) + list( + sp_observation.uss_flight_details_queries.values() + ) - if len(queries_to_repeat) == 0: - logger.debug("no flights queries to repeat at this point.") - return False + if len(queries_to_repeat) == 0: + logger.debug("no flights queries to repeat at this point.") + return False - logger.debug( - f"about to repeat {len(queries_to_repeat)} flights queries without credentials" - ) + logger.debug( + f"about to repeat {len(queries_to_repeat)} flights queries without credentials" + ) - # Attempt to re-query the flights and flight details URLs: - for fq in queries_to_repeat: - failed_q = fetch.query_and_describe( - client=unauthenticated_session, - verb=fq.query.request.method, - url=fq.query.request.url, - json=fq.query.request.json, - data=fq.query.request.body, - server_id=self._dss.participant_id, - ) - logger.info( - f"Repeating query to {fq.query.request.url} without credentials" - ) - server_id = fq.query.get("server_id", "unknown") + # Attempt to re-query the flights and flight details URLs: + for fq in queries_to_repeat: + sp_server_id = fq.query.get("server_id", "unknown") + if sp_server_id == "unknown": + logger.warning(f"got unattributed SP query to: {fq.query.request.url}") + + failed_q = fetch.query_and_describe( + client=unauthenticated_session, + verb=fq.query.request.method, + url=fq.query.request.url, + json=fq.query.request.json, + data=fq.query.request.body, + server_id=sp_server_id, + ) + logger.info( + f"Repeating query to {fq.query.request.url} without credentials" + ) + with self.check("Missing credentials", [sp_server_id]) as check: if failed_q.response.code not in [401, 403]: check.record_failed( "unauthenticated request was fulfilled", @@ -227,7 +223,7 @@ def _evaluate_and_test_authentication( # Keep track of the failed queries, too self.record_query(failed_q) - return True + return True def cleanup(self): self.begin_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index c64257741f..b788784b5b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -231,7 +231,9 @@ def evaluate_system_instantaneously( ) -> None: if self._dss: self._test_scenario.begin_test_step("Service Provider polling") - + flights_urls_to_participant_id = { + observer.flights_url: observer.participant_id for observer in observers + } # Observe Service Provider with uss_qualifier acting as a Display Provider sp_observation = all_flights( rect, @@ -239,7 +241,8 @@ def evaluate_system_instantaneously( get_details=True, rid_version=self._rid_version, session=self._dss.client, - server_id=self._dss.participant_id, + dss_server_id=self._dss.participant_id, + flights_urls_to_server_id=flights_urls_to_participant_id, ) for q in sp_observation.queries: self._test_scenario.record_query(q) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md index 0602647f31..537364b92f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/misbehavior.md @@ -48,7 +48,7 @@ It then repeats the exact same request while omitting the credentials, and expec #### Missing credentials check -This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v19.NET0500](../../../../requirements/astm/f3411/v19.md)**, +This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v19.NET0210](../../../../requirements/astm/f3411/v19.md)**, and that requests for existing flights that are executed with missing or incorrect credentials fail. ## Cleanup diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md index d94a021a73..5089d9d23c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/misbehavior.md @@ -48,7 +48,7 @@ It then repeats the exact same request while omitting the credentials, and expec #### Missing credentials check -This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v22a.NET0500](../../../../requirements/astm/f3411/v22a.md)**, +This check ensures that all requests are properly authenticated, as required by **[astm.f3411.v22a.NET0210](../../../../requirements/astm/f3411/v22a.md)**, and that requests for existing flights that are executed with missing or incorrect credentials fail. ## Cleanup diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md index 4a00bf06f7..c354bb768e 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md @@ -21,7 +21,7 @@