From bc27bd2047b4ba868d8e2e846cc650df2faae207 Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Fri, 8 Dec 2023 09:01:58 +0100 Subject: [PATCH] OPIN0035 access control for op intents --- monitoring/prober/infrastructure.py | 2 +- .../dev/f3548_self_contained.yaml | 26 ++ .../dev/library/environment_containers.yaml | 6 + .../dev/library/environment_localhost.yaml | 6 + .../configurations/dev/message_signing.yaml | 5 + .../configurations/dev/uspace.yaml | 3 + .../resources/astm/f3548/v21/dss.py | 37 +- .../flight_intents_resource.py | 31 +- monitoring/uss_qualifier/run_locally.sh | 2 + .../scenarios/astm/utm/__init__.py | 1 + .../astm/utm/op_intent_access_control.md | 126 ++++++ .../astm/utm/op_intent_access_control.py | 409 ++++++++++++++++++ .../suites/astm/utm/dss_probing.md | 15 +- .../suites/astm/utm/dss_probing.yaml | 10 + .../uss_qualifier/suites/astm/utm/f3548_21.md | 9 +- .../suites/astm/utm/f3548_21.yaml | 8 + .../suites/faa/uft/message_signing.md | 9 +- .../suites/faa/uft/message_signing.yaml | 4 + .../suites/uspace/flight_auth.md | 9 +- .../suites/uspace/flight_auth.yaml | 5 +- .../suites/uspace/required_services.md | 9 +- .../suites/uspace/required_services.yaml | 3 + 22 files changed, 719 insertions(+), 16 deletions(-) create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.md create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.py diff --git a/monitoring/prober/infrastructure.py b/monitoring/prober/infrastructure.py index bca87ef15d..5ee5067d3d 100644 --- a/monitoring/prober/infrastructure.py +++ b/monitoring/prober/infrastructure.py @@ -100,7 +100,7 @@ def wrapper_default_scope(*args, **kwargs): resource_type_code_descriptions: Dict[ResourceType, str] = {} -# Next code: 375 +# Next code: 377 def register_resource_type(code: int, description: str) -> ResourceType: """Register that the specified code refers to the described resource. diff --git a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml index 46eef3d548..df3d99dc50 100644 --- a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml +++ b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml @@ -13,6 +13,7 @@ v1: # Mapping of to resources: + id_generator: id_generator flight_planners: flight_planners conflicting_flights: conflicting_flights invalid_flight_intents: invalid_flight_intents @@ -20,6 +21,7 @@ v1: dss: dss dss_instances: dss_instances mock_uss: mock_uss + second_utm_auth: second_utm_auth # This block defines all the resources available in the resource pool. # Presumably all resources defined below would be used either @@ -35,6 +37,30 @@ v1: # To avoid putting secrets in configuration files, the auth spec (including sensitive information) will be read from the AUTH_SPEC environment variable environment_variable_containing_auth_spec: AUTH_SPEC + # Means by which uss_qualifier can discover which subscription ('sub' claim of its tokes) it is described by + utm_client_identity: + resource_type: resources.communications.ClientIdentityResource + dependencies: + auth_adapter: utm_auth + specification: + # Audience and scope to be used to issue a dummy query, should it be required to discover the subscription + whoami_audience: localhost + whoami_scope: rid.display_provider + + # Means by which uss_qualifier generates identifiers + id_generator: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.interuss.IDGeneratorResource + dependencies: + client_identity: utm_client_identity + + # A second auth adapter, for checks that require a second set of credentials for accessing the ecosystem. + # Note that the 'sub' claim of the tokens obtained through this adepter MUST be different from the first auth adapter. + second_utm_auth: + resource_type: resources.communications.AuthAdapterResource + specification: + environment_variable_containing_auth_spec: AUTH_SPEC_2 + # Set of USSs capable of being tested as flight planners flight_planners: resource_type: resources.flight_planning.FlightPlannersResource diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml index 4b69a3f7f4..6847d6629f 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml @@ -9,6 +9,12 @@ utm_auth: specification: environment_variable_containing_auth_spec: AUTH_SPEC +second_utm_auth: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.communications.AuthAdapterResource + specification: + environment_variable_containing_auth_spec: AUTH_SPEC_2 + utm_client_identity: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json resource_type: resources.communications.ClientIdentityResource diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml index cc3782dfb9..6af9f2cba8 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml @@ -9,6 +9,12 @@ utm_auth: specification: environment_variable_containing_auth_spec: AUTH_SPEC +second_utm_auth: + $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json + resource_type: resources.communications.AuthAdapterResource + specification: + environment_variable_containing_auth_spec: AUTH_SPEC_2 + utm_client_identity: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json resource_type: resources.communications.ClientIdentityResource diff --git a/monitoring/uss_qualifier/configurations/dev/message_signing.yaml b/monitoring/uss_qualifier/configurations/dev/message_signing.yaml index a014f2a4a8..50a8b878f0 100644 --- a/monitoring/uss_qualifier/configurations/dev/message_signing.yaml +++ b/monitoring/uss_qualifier/configurations/dev/message_signing.yaml @@ -14,6 +14,9 @@ v1: mock_uss: 1 utm_auth: {$ref: 'library/environment.yaml#/utm_auth'} + second_utm_auth: {$ref: 'library/environment.yaml#/second_utm_auth'} + utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'} + id_generator: {$ref: 'library/resources.yaml#/id_generator'} scd_dss: {$ref: 'library/environment.yaml#/scd_dss'} scd_dss_instances: {$ref: 'library/environment.yaml#/scd_dss_instances'} flight_planners: @@ -53,6 +56,8 @@ v1: priority_preemption_flights: che_conflicting_flights dss: scd_dss dss_instances: scd_dss_instances + id_generator: id_generator + second_utm_auth: second_utm_auth execution: stop_fast: true diff --git a/monitoring/uss_qualifier/configurations/dev/uspace.yaml b/monitoring/uss_qualifier/configurations/dev/uspace.yaml index 382dbf8cfc..a066f622ee 100644 --- a/monitoring/uss_qualifier/configurations/dev/uspace.yaml +++ b/monitoring/uss_qualifier/configurations/dev/uspace.yaml @@ -16,6 +16,7 @@ v1: au_problematically_big_area: {$ref: 'library/resources.yaml#/au_problematically_big_area'} utm_auth: {$ref: 'library/environment.yaml#/utm_auth'} + second_utm_auth: {$ref: 'library/environment.yaml#/second_utm_auth'} mock_uss_instances_scdsc: {$ref: 'library/environment.yaml#/mock_uss_instances_scdsc'} mock_uss_instance_uss6: {$ref: 'library/environment.yaml#/mock_uss_instance_uss6'} scd_version_providers: {$ref: 'library/environment.yaml#/scd_version_providers'} @@ -61,6 +62,7 @@ v1: evaluation_configuration: netrid_observation_evaluation_configuration netrid_dss_instances: netrid_dss_instances_v22a utm_client_identity: utm_client_identity + second_utm_auth: second_utm_auth id_generator: id_generator service_area: kentland_service_area problematically_big_area: au_problematically_big_area @@ -89,6 +91,7 @@ v1: evaluation_configuration: evaluation_configuration netrid_dss_instances: netrid_dss_instances utm_client_identity: utm_client_identity + second_utm_auth: second_utm_auth id_generator: id_generator service_area: service_area problematically_big_area: problematically_big_area diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py index 7c151a924d..f60b1a8f4f 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -28,8 +28,14 @@ SetUssAvailabilityStatusParameters, UssAvailabilityState, UssAvailabilityStatusResponse, + GetOperationalIntentReferenceResponse, ) +# A base URL for a USS that is not expected to be ever called +# Used in scenarios where we mimic the behavior of a USS and need to provide a base URL. +# As the area used for tests is cleared before the tests, there is no need to have this URL be reachable. +DUMMY_USS_BASE_URL = "https://dummy.uss" + class DSSInstanceSpecification(ImplicitDict): participant_id: str @@ -90,6 +96,30 @@ def find_op_intent( ).operational_intent_references return result, query + def get_op_intent_reference( + self, + op_intent_id: str, + ) -> Tuple[OperationalIntentReference, fetch.Query]: + """ + Retrieve an OP Intent from the DSS, using only its ID + """ + url = f"/dss/v1/operational_intent_references/{op_intent_id}" + query = fetch.query_and_describe( + self.client, + "GET", + url, + QueryType.F3548v21DSSGetOperationalIntentReference, + self.participant_id, + scope=SCOPE_SC, + ) + if query.status_code != 200: + result = None + else: + result = ImplicitDict.parse( + query.response.json, GetOperationalIntentReferenceResponse + ).operational_intent_reference + return result, query + def get_full_op_intent( self, op_intent_ref: OperationalIntentReference, @@ -125,11 +155,12 @@ def put_op_intent( Optional[List[SubscriberToNotify]], fetch.Query, ]: - if id is None: - url = f"/dss/v1/operational_intent_references/{str(uuid.uuid4())}" + oi_uuid = str(uuid.uuid4()) if id is None else id + if ovn is None: + url = f"/dss/v1/operational_intent_references/{oi_uuid}" query_type = QueryType.F3548v21DSSCreateOperationalIntentReference else: - url = f"/dss/v1/operational_intent_references/{id}/{ovn}" + url = f"/dss/v1/operational_intent_references/{oi_uuid}/{ovn}" query_type = QueryType.F3548v21DSSUpdateOperationalIntentReference req = PutOperationalIntentReferenceParameters( diff --git a/monitoring/uss_qualifier/resources/flight_planning/flight_intents_resource.py b/monitoring/uss_qualifier/resources/flight_planning/flight_intents_resource.py index bffc95ffbc..a448abe098 100644 --- a/monitoring/uss_qualifier/resources/flight_planning/flight_intents_resource.py +++ b/monitoring/uss_qualifier/resources/flight_planning/flight_intents_resource.py @@ -1,9 +1,13 @@ -from typing import Dict +from typing import Dict, List +import arrow from implicitdict import ImplicitDict +from uas_standards.astm.f3548.v21.api import OperationalIntentState + from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( FlightInfoTemplate, ) +from monitoring.monitorlib.geotemporal import Volume4DCollection from monitoring.uss_qualifier.resources.files import load_dict from monitoring.uss_qualifier.resources.resource import Resource @@ -11,6 +15,7 @@ FlightIntentCollection, FlightIntentsSpecification, FlightIntentID, + FlightIntent, ) @@ -39,3 +44,27 @@ def __init__(self, specification: FlightIntentsSpecification): def get_flight_intents(self) -> Dict[FlightIntentID, FlightInfoTemplate]: return self._intent_collection.resolve() + + +def unpack_flight_intents( + flight_intents: FlightIntentsResource, flight_identifiers: List[str] +): + """ + Extracts the specified flight identifiers from the passed FlightIntentsResource + """ + flight_intents = { + k: FlightIntent.from_flight_info_template(v) + for k, v in flight_intents.get_flight_intents().items() + if k in flight_identifiers + } + + extents = [] + for intent in flight_intents.values(): + extents.extend(intent.request.operational_intent.volumes) + extents.extend(intent.request.operational_intent.off_nominal_volumes) + + intents_extent = Volume4DCollection.from_interuss_scd_api( + extents + ).bounding_volume.to_f3548v21() + + return intents_extent, flight_intents diff --git a/monitoring/uss_qualifier/run_locally.sh b/monitoring/uss_qualifier/run_locally.sh index d6efcc3721..124260731e 100755 --- a/monitoring/uss_qualifier/run_locally.sh +++ b/monitoring/uss_qualifier/run_locally.sh @@ -43,6 +43,7 @@ echo "Running configuration(s): ${CONFIG_NAME}" CONFIG_FLAG="--config ${CONFIG_NAME}" AUTH_SPEC='DummyOAuth(http://oauth.authority.localutm:8085/token,uss_qualifier)' +AUTH_SPEC_2='DummyOAuth(http://oauth.authority.localutm:8085/token,uss_qualifier_2)' QUALIFIER_OPTIONS="$CONFIG_FLAG $OTHER_ARGS" @@ -66,6 +67,7 @@ docker run ${docker_args} --name uss_qualifier \ -u "$(id -u):$(id -g)" \ -e PYTHONBUFFERED=1 \ -e AUTH_SPEC=${AUTH_SPEC} \ + -e AUTH_SPEC_2=${AUTH_SPEC_2} \ -e MONITORING_GITHUB_ROOT=${MONITORING_GITHUB_ROOT:-} \ -v "$(pwd)/$OUTPUT_DIR:/app/$OUTPUT_DIR" \ -v "$(pwd)/$CACHE_DIR:/app/$CACHE_DIR" \ diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py index 07d2b0f5a9..3dcf1c54fe 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/__init__.py @@ -12,3 +12,4 @@ from .off_nominal_planning.down_uss_equal_priority_not_permitted import ( DownUSSEqualPriorityNotPermitted, ) +from .op_intent_access_control import OpIntentAccessControl diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.md b/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.md new file mode 100644 index 0000000000..0f61b6441e --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.md @@ -0,0 +1,126 @@ +# ASTM F3548-21 UTM DSS Operational Intent Access Control test scenario + +## Overview + +This scenario ensures that a DSS will only let the owner of an operational intent modify it. + +## Resources + +### flight_intents + +A `resources.flight_planning.FlightIntentsResource` containing the flight intents to be used in this scenario: + +This scenario expects to find at least two separate flight intents in this resource, as it will use their extent +to create two operational intents. + +### dss + +A `resources.astm.f3548.v21.DSSInstanceResource` pointing to the DSS instance to test for this scenario. + +### second_utm_auth + +A `resources.communications.AuthAdapterResource` containing a second set of valid credentials for interacting with the DSS. + +This second set of credentials is required to validate that the DSS is properly enforcing access control rules, and properly limits the actions of a client against +the resources exposed by the DSS. + +The participant under test is responsible for providing this second set of credentials along the primary ones used in most other scenarios. + +#### Credential requirements + +In general, these test credentials may be in all points equal to the ones used by the `AuthAdapterResource` that is +provided to the `dss` resources above, except for the value contained in the `sub` claim of the token. + +For the purpose of this scenario, these credentials must be allowed to create, modify and delete operational intents on the DSS, +as well as querying operational intent references. + +##### Required scope + +For the purpose of this scenario, the `second_utm_auth` resource must provide access to a token with at least the following scope: + +* `utm.strategic_coordination` + +##### Separate subscription + +Note that the subscription (or 'sub' claim) of the token that will be obtained for this resource +MUST be different from the one of the `dss` resources mentioned above: +this will be verified at runtime, and this scenario will fail if the second set of credentials belong to the same subscription as the main one. + +### id_generator + +A `resources.interuss.IDGeneratorResource` that will be used to generate the IDs of the operational intents created in this scenario. + +## Setup test case + +Makes sure that the DSS is in a clean and expected state before running the test, and that the passed resources work as required. + +The setup will create two separate operational intents: one for each set of the available credentials. + +### Ensure clean workspace test step + +#### Operational intents can be queried directly by their ID check + +If an existing operational intent cannot directly be queried by its ID, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. + +#### Operational intents can be searched using valid credentials check + +A client with valid credentials should be allowed to search for operational intents in a given area. +Otherwise, the DSS is not in compliance with **[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. + +#### Operational intents can be deleted by their owner check + +If an existing operational intent cannot be deleted when providing the proper ID and OVN, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. + +### Create operational intents with different credentials test step + +This test step ensures that an operation intent created with the main credentials is available for the main test case. + +To verify that the second credentials are valid, it will also create an operational intent with those credentials. + +#### Can create an operational intent with valid credentials check + +If the DSS does not allow the creation of operation intents when the required parameters and credentials are provided, +it is in violation of **[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. + +#### Passed sets of credentials are different check + +This scenario requires two sets of credentials that have a different 'sub' claim in order to validate that the +DSS properly controls access to operational intents. + +## Attempt unauthorized flight intent modification test case + +This test case ensures that the DSS does not allow a caller to modify or delete operational intent that they did not create. + +### Attempt unauthorized flight intent modification test step + +This test step will attempt to modify the operational intent that was created using the configured `dss` resource, +using the credentials provided in the `second_utm_auth` resource, and expect all such attempts to fail. + +#### Operational intents can be queried directly by their ID check + +If an existing operational intent cannot directly be queried by its ID, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. + +#### Non-owning credentials cannot modify operational intent check + +If an operational intent can be modified by a client which did not create it, the DSS implementation is +in violation of **[astm.f3548.v21.OPIN0035](../../../requirements/astm/f3548/v21.md)**. + +#### Non-owning credentials cannot delete operational intent check + +If an operational intent can be deleted by a client which did not create it, the DSS implementation is +in violation of **[astm.f3548.v21.OPIN0035](../../../requirements/astm/f3548/v21.md)**. + +## Cleanup + +### Operational intents can be queried directly by their ID check + +If an existing operational intent cannot directly be queried by its ID, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. + +### Operational intents can be deleted by their owner check + +If an existing operational intent cannot be deleted when providing the proper ID and OVN, the DSS implementation is in violation of +**[astm.f3548.v21.DSS0005](../../../requirements/astm/f3548/v21.md)**. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.py b/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.py new file mode 100644 index 0000000000..76b69bf214 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/op_intent_access_control.py @@ -0,0 +1,409 @@ +from typing import Optional, List + +import loguru +from uas_standards.astm.f3548.v21.api import OperationalIntentState + +from monitoring.monitorlib.geotemporal import Volume4DCollection +from uas_standards.astm.f3548.v21 import api as f3548v21 +from monitoring.prober.infrastructure import register_resource_type +from monitoring.uss_qualifier.common_data_definitions import Severity +from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( + DSSInstance, + DUMMY_USS_BASE_URL, +) +from monitoring.uss_qualifier.resources.communications import AuthAdapterResource +from monitoring.uss_qualifier.resources.flight_planning import FlightIntentsResource +from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( + FlightIntent, +) +from monitoring.uss_qualifier.resources.flight_planning.flight_intents_resource import ( + unpack_flight_intents, +) +from monitoring.uss_qualifier.resources.interuss import IDGeneratorResource +from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.suites.suite import ExecutionContext + + +class OpIntentAccessControl(TestScenario): + """ + Tests that the DSS only allows a client to edit their own flight intents, but not those of another USS. + """ + + OP_INTENT_1 = register_resource_type(375, "Operational Intent") + OP_INTENT_2 = register_resource_type(376, "Operational Intent") + + # The DSS under test + _dss: DSSInstance + _pid: List[str] + + # The same DSS, available via a separate auth adapter + _dss_separate_creds: DSSInstance + + _flight1_planned: FlightIntent + _flight2_planned: FlightIntent + + _volumes1: Volume4DCollection + _volumes2: Volume4DCollection + + _intents_extent: f3548v21.Volume4D + + _current_ref_1: f3548v21.OperationalIntentReference + _current_ref_2: f3548v21.OperationalIntentReference + + def __init__( + self, + flight_intents: FlightIntentsResource, + dss: DSSInstanceResource, + second_utm_auth: AuthAdapterResource, + id_generator: IDGeneratorResource, + ): + super().__init__() + self._dss = dss.dss + self._pid = [dss.dss.participant_id] + + self._oid_1 = id_generator.id_factory.make_id(self.OP_INTENT_1) + self._oid_2 = id_generator.id_factory.make_id(self.OP_INTENT_2) + + if second_utm_auth is not None: + # Build a second DSSWrapper identical to the first but with the other auth adapter + self._dss_separate_creds = DSSInstance( + participant_id=dss.dss.participant_id, + base_url=dss.dss.base_url, + has_private_address=dss.dss.has_private_address, + auth_adapter=second_utm_auth.adapter, + ) + + try: + (self._intents_extent, planned_flights) = unpack_flight_intents( + flight_intents, ["flight_1", "flight_2"] + ) + self._flight1_planned = planned_flights["flight_1"] + self._flight2_planned = planned_flights["flight_2"] + + self._volumes1 = Volume4DCollection.from_interuss_scd_api( + self._flight1_planned.request.operational_intent.volumes + ) + + self._volumes2 = Volume4DCollection.from_interuss_scd_api( + self._flight2_planned.request.operational_intent.volumes + ) + + except KeyError as e: + raise ValueError( + f"`{self.me()}` TestScenario requirements for flight_intents not met: missing flight intent {e}" + ) + except AssertionError as e: + raise ValueError( + f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" + ) + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + self.begin_test_case("Setup") + + self.begin_test_step("Ensure clean workspace") + self._ensure_clean_workspace() + self.end_test_step() + + self.begin_test_step("Create operational intents with different credentials") + self._create_op_intents() + self._ensure_credentials_are_different() + self.end_test_step() + + self.end_test_case() + + self.begin_test_case("Attempt unauthorized flight intent modification") + self.begin_test_step("Attempt unauthorized flight intent modification") + + self._check_mutation_on_non_owned_intent_fails() + + self.end_test_step() + self.end_test_case() + + self.end_test_scenario() + + def _clean_known_op_intents_ids(self): + (oi_ref, q) = self._dss.get_op_intent_reference(self._oid_1) + self.record_query(q) + with self.check( + "Operational intents can be queried directly by their ID", self._pid + ) as check: + # If the Op Intent does not exist, it's fine to run into a 404. + if q.response.status_code not in [200, 404]: + check.record_failed( + f"Could not access operational intent using main credentials", + Severity.High, + f"DSS responded with {q.response.status_code} to attempt to access OI {self._oid_1}", + query_timestamps=[q.request.timestamp], + ) + if q.response.status_code != 404: + (_, notifs, dq) = self._dss.delete_op_intent(self._oid_1, oi_ref.ovn) + self.record_query(dq) + if dq.response.status_code != 200: + with self.check( + "Operational intents can be deleted by their owner", self._pid + ) as check: + check.record_failed( + f"Could not delete operational intent using main credentials", + Severity.High, + f"DSS responded with {dq.response.status_code} to attempt to delete OI {self._oid_1}", + query_timestamps=[dq.request.timestamp], + ) + + (oi_ref, q) = self._dss_separate_creds.get_op_intent_reference(self._oid_2) + self.record_query(q) + with self.check( + "Operational intents can be queried directly by their ID", self._pid + ) as check: + if q.response.status_code not in [200, 404]: + check.record_failed( + f"Could not access operational intent using second credentials", + Severity.High, + f"DSS responded with {q.response.status_code} to attempt to access OI {self._oid_2}", + query_timestamps=[q.request.timestamp], + ) + if q.response.status_code != 404: + (_, notifs, dq) = self._dss_separate_creds.delete_op_intent( + self._oid_2, oi_ref.ovn + ) + self.record_query(dq) + with self.check( + "Operational intents can be deleted by their owner", self._pid + ) as check: + if dq.response.status_code != 200: + check.record_failed( + f"Could not delete operational intent using second credentials", + Severity.High, + f"DSS responded with {dq.response.status_code} to attempt to delete OI {self._oid_2}", + query_timestamps=[dq.request.timestamp], + ) + + def _ensure_clean_workspace(self): + self._clean_known_op_intents_ids() + + # Also check for any potential other op_intents and delete them + (op_intents_1, q) = self._dss.find_op_intent(self._intents_extent) + self.record_query(q) + loguru.logger.info(f"Search query: {q.response}") + with self.check( + "Operational intents can be searched using valid credentials", self._pid + ) as check: + if q.response.status_code != 200: + check.record_failed( + f"Could not search operational intents using main credentials", + Severity.High, + f"DSS responded with {q.response.status_code} to attempt to search OIs", + query_timestamps=[q.request.timestamp], + ) + + for op_intent in op_intents_1: + # We look for an op_intent where the uss_qualifier is the manager; + if op_intent.manager == self._dss.client.auth_adapter.get_sub(): + (_, _, dq) = self._dss.delete_op_intent(op_intent.id, op_intent.ovn) + self.record_query(dq) + with self.check( + "Operational intents can be deleted by their owner", self._pid + ) as check: + if dq.response.status_code != 200: + check.record_failed( + f"Could not delete operational intent using main credentials", + Severity.High, + f"DSS responded with {dq.response.status_code} to attempt to delete OI {op_intent.id}", + query_timestamps=[dq.request.timestamp], + ) + + (op_intents_2, q) = self._dss_separate_creds.find_op_intent( + self._intents_extent + ) + self.record_query(q) + with self.check( + "Operational intents can be searched using valid credentials", self._pid + ) as check: + if q.response.status_code != 200: + check.record_failed( + f"Could not search operational intents using second credentials", + Severity.High, + f"DSS responded with {q.response.status_code} to attempt to search OIs", + query_timestamps=[q.request.timestamp], + ) + + for op_intent in op_intents_2: + # We look for an op_intent where the uss_qualifier is the manager; + if ( + op_intent.manager + == self._dss_separate_creds.client.auth_adapter.get_sub() + ): + (_, _, dq) = self._dss_separate_creds.delete_op_intent( + op_intent.id, op_intent.ovn + ) + self.record_query(dq) + with self.check( + "Operational intents can be deleted by their owner", self._pid + ) as check: + if dq.response.status_code != 200: + check.record_failed( + f"Could not delete operational intent using second credentials", + Severity.High, + f"DSS responded with {dq.response.status_code} to attempt to delete OI {op_intent.id}", + query_timestamps=[dq.request.timestamp], + ) + + def _create_op_intents(self): + (self._current_ref_1, subscribers1, q1) = self._dss.put_op_intent( + id=self._oid_1, + extents=self._volumes1.to_f3548v21(), + key=[], + state=OperationalIntentState.Accepted, + base_url=DUMMY_USS_BASE_URL, + ) + self.record_query(q1) + + with self.check( + "Can create an operational intent with valid credentials", self._pid + ) as check: + if q1.response.status_code != 201: + check.record_failed( + f"Could not create operational intent using main credentials", + Severity.High, + f"DSS responded with {q1.response.status_code} to attempt to create OI {self._oid_1}", + query_timestamps=[q1.request.timestamp], + ) + + ( + self._current_ref_2, + subscribers2, + q2, + ) = self._dss_separate_creds.put_op_intent( + id=self._oid_2, + extents=self._volumes2.to_f3548v21(), + key=[self._current_ref_1.ovn], + state=OperationalIntentState.Accepted, + base_url=DUMMY_USS_BASE_URL, + ) + self.record_query(q2) + with self.check( + "Can create an operational intent with valid credentials", self._pid + ) as check: + if q2.response.status_code != 201: + check.record_failed( + f"Could not create operational intent using second credentials", + Severity.High, + f"DSS responded with {q2.response.status_code} to attempt to create OI {self._oid_2}", + query_timestamps=[q2.request.timestamp], + ) + + def _ensure_credentials_are_different(self): + """ + Checks the auth adapters for the subscription they used and raises an exception if they are the same. + Note that both adapters need to have been used at least once before this check can be performed, + otherwise they have no token available. + """ + with self.check("Passed sets of credentials are different", self._pid) as check: + if ( + self._dss_separate_creds.client.auth_adapter.get_sub() + == self._dss.client.auth_adapter.get_sub() + ): + check.record_failed( + f"Second set of credentials is not different from the first", + Severity.High, + f"The same credentials were provided for the main 'dss' and the additional 'second_utm_auth'" + f" resources ({self._dss.client.auth_adapter.get_sub()}),", + ) + + def _check_mutation_on_non_owned_intent_fails(self): + # Attempt to update the state of the intent created with the main credentials using the second credentials + (ref, notif, q) = self._dss_separate_creds.put_op_intent( + id=self._oid_1, + extents=self._volumes1.to_f3548v21(), + key=[self._current_ref_2.ovn], + state=OperationalIntentState.Accepted, + base_url=self._current_ref_1.uss_base_url, + ovn=self._current_ref_1.ovn, + ) + self.record_query(q) + with self.check( + "Non-owning credentials cannot modify operational intent", + self._pid, + ) as check: + if q.response.status_code != 403: + check.record_failed( + f"Could update operational intent using second credentials", + Severity.High, + f"DSS responded with {q.response.status_code} to attempt to update OI {self._oid_1}", + query_timestamps=[q.request.timestamp], + ) + # Attempt to update the base_url of the intent created with the main credentials using the second credentials + (ref, notif, q) = self._dss_separate_creds.put_op_intent( + id=self._oid_1, + extents=self._volumes1.to_f3548v21(), + key=[self._current_ref_2.ovn], + state=self._current_ref_1.state, + base_url="https://another-url.uss/down", + ovn=self._current_ref_1.ovn, + ) + self.record_query(q) + with self.check( + "Non-owning credentials cannot modify operational intent", + self._pid, + ) as check: + if q.response.status_code != 403: + check.record_failed( + f"Could update operational intent using second credentials", + Severity.High, + f"DSS responded with {q.response.status_code} to attempt to update OI {self._oid_1}", + query_timestamps=[q.request.timestamp], + ) + + # Try to delete + (_, _, dq) = self._dss_separate_creds.delete_op_intent( + self._oid_1, self._current_ref_1.ovn + ) + self.record_query(dq) + with self.check( + "Non-owning credentials cannot delete operational intent", + self._pid, + ) as check: + if dq.response.status_code != 403: + check.record_failed( + f"Could delete operational intent using second credentials", + Severity.High, + f"DSS responded with {dq.response.status_code} to attempt to delete OI {self._oid_1}", + query_timestamps=[dq.request.timestamp], + ) + + # Query again to confirm that the op intent has not been modified in any way: + (op_1_current, qcheck) = self._dss.get_op_intent_reference(self._oid_1) + self.record_query(qcheck) + + with self.check( + "Operational intents can be queried directly by their ID", self._pid + ) as check: + if qcheck.response.status_code != 200: + check.record_failed( + f"Could not access operational intent using main credentials", + Severity.High, + f"DSS responded with {qcheck.response.status_code} to attempt to access OI {self._oid_1} " + f"while this OI should have been available.", + query_timestamps=[qcheck.request.timestamp], + ) + + with self.check( + "Non-owning credentials cannot modify operational intent", + self._pid, + ) as check: + if op_1_current != self._current_ref_1: + check.record_failed( + f"Could update operational intent using second credentials", + Severity.High, + f"Operational intent {self._oid_1} was modified by second credentials", + query_timestamps=[q.request.timestamp, qcheck.request.timestamp], + ) + + def cleanup(self): + self.begin_cleanup() + + # We remove the op intents that were created for this scenario + self._clean_known_op_intents_ids() + + self.end_cleanup() diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md index 2cc19f3443..44fe733e30 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md @@ -4,7 +4,8 @@ ## [Actions](../../README.md#actions) -1. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py)) +1. Scenario: [ASTM F3548-21 UTM DSS Operational Intent Access Control](../../../scenarios/astm/utm/op_intent_access_control.md) ([`scenarios.astm.utm.OpIntentAccessControl`](../../../scenarios/astm/utm/op_intent_access_control.py)) +2. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -16,9 +17,19 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
+ DSS0005 + Implemented + ASTM F3548-21 UTM DSS Operational Intent Access Control + + DSS0300 Implemented ASTM F3548-21 UTM DSS interoperability + + OPIN0035 + Implemented + ASTM F3548-21 UTM DSS Operational Intent Access Control + diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml index a6a4b2d792..26c2c8b483 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml @@ -1,8 +1,18 @@ name: DSS testing for ASTM NetRID F3548-21 resources: dss: resources.astm.f3548.v21.DSSInstanceResource + second_utm_auth: resources.communications.AuthAdapterResource all_dss_instances: resources.astm.f3548.v21.DSSInstancesResource? + flight_intents: resources.flight_planning.FlightIntentsResource + id_generator: resources.interuss.IDGeneratorResource actions: + - test_scenario: + scenario_type: scenarios.astm.utm.OpIntentAccessControl + resources: + dss: dss + second_utm_auth: second_utm_auth + flight_intents: flight_intents + id_generator: id_generator - test_scenario: scenario_type: scenarios.astm.utm.DSSInteroperability resources: diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index c386c384ee..3b22fdfe11 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -31,10 +31,10 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Access Control
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0100 @@ -76,6 +76,11 @@ Implemented Validation of operational intents + + OPIN0035 + Implemented + ASTM F3548-21 UTM DSS Operational Intent Access Control + OPIN0040 Implemented diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml index 3d10ce6624..33f1acea31 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.yaml @@ -9,19 +9,27 @@ resources: non_conflicting_flights: resources.flight_planning.FlightIntentsResource nominal_planning_selector: resources.flight_planning.FlightPlannerCombinationSelectorResource? priority_planning_selector: resources.flight_planning.FlightPlannerCombinationSelectorResource? + second_utm_auth: resources.communications.AuthAdapterResource? mock_uss: resources.interuss.mock_uss.client.MockUSSResource + id_generator: resources.interuss.IDGeneratorResource actions: - action_generator: generator_type: action_generators.astm.f3548.ForEachDSS resources: dss_instances: dss_instances + second_utm_auth: second_utm_auth + flight_intents: non_conflicting_flights + id_generator: id_generator specification: action_to_repeat: test_suite: suite_type: suites.astm.utm.dss_probing resources: dss: dss + second_utm_auth: second_utm_auth all_dss_instances: dss_instances + flight_intents: flight_intents + id_generator: id_generator on_failure: Continue dss_instances_source: dss_instances dss_instance_id: dss diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index d9d4233d56..5eb1335141 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md @@ -18,10 +18,10 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Access Control
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0100 @@ -63,6 +63,11 @@ Implemented Validation of operational intents + + OPIN0035 + Implemented + ASTM F3548-21 UTM DSS Operational Intent Access Control + OPIN0040 Implemented diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.yaml b/monitoring/uss_qualifier/suites/faa/uft/message_signing.yaml index 019ae5659b..3c65cac983 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.yaml +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.yaml @@ -9,6 +9,8 @@ resources: non_conflicting_flights: resources.flight_planning.FlightIntentsResource priority_preemption_flights: resources.flight_planning.FlightIntentsResource invalid_flight_intents: resources.flight_planning.FlightIntentsResource + id_generator: resources.interuss.IDGeneratorResource + second_utm_auth: resources.communications.AuthAdapterResource? actions: - test_scenario: scenario_type: scenarios.faa.uft.StartMessageSigningReport @@ -28,6 +30,8 @@ actions: priority_planning_selector: combination_selector dss: dss dss_instances: dss_instances + id_generator: id_generator + second_utm_auth: second_utm_auth on_failure: Continue - test_scenario: scenario_type: scenarios.faa.uft.FinalizeMessageSigningReport diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index 5395733f8b..8256df3cd4 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.md +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.md @@ -19,10 +19,10 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Access Control
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0100 @@ -64,6 +64,11 @@ Implemented Validation of operational intents + + OPIN0035 + Implemented + ASTM F3548-21 UTM DSS Operational Intent Access Control + OPIN0040 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.yaml b/monitoring/uss_qualifier/suites/uspace/flight_auth.yaml index 2ecb116f87..3f59c5e436 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.yaml +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.yaml @@ -9,7 +9,8 @@ resources: mock_uss: resources.interuss.mock_uss.client.MockUSSResource dss: resources.astm.f3548.v21.DSSInstanceResource dss_instances: resources.astm.f3548.v21.DSSInstancesResource? - + id_generator: resources.interuss.IDGeneratorResource + second_utm_auth: resources.communications.AuthAdapterResource? actions: - test_suite: suite_type: suites.astm.utm.f3548_21 @@ -22,6 +23,8 @@ actions: mock_uss: mock_uss dss: dss dss_instances: dss_instances + id_generator: id_generator + second_utm_auth: second_utm_auth on_failure: Continue - test_scenario: scenario_type: scenarios.flight_planning.PrepareFlightPlanners diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index e3cac8fe8b..ccc007b6f5 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -449,10 +449,10 @@ ASTM NetRID DSS: Concurrent Requests
ASTM NetRID DSS: ISA Expiry
ASTM NetRID DSS: ISA Subscription Interactions
ASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Submitted ISA Validations
ASTM NetRID DSS: Subscription Simple
ASTM NetRID DSS: Subscription Validation
ASTM NetRID DSS: Token Validation - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005 Implemented - ASTM F3548 flight planners preparation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Access Control
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0100 @@ -494,6 +494,11 @@ Implemented Validation of operational intents + + OPIN0035 + Implemented + ASTM F3548-21 UTM DSS Operational Intent Access Control + OPIN0040 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.yaml b/monitoring/uss_qualifier/suites/uspace/required_services.yaml index 0a503764b5..74be573c82 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.yaml +++ b/monitoring/uss_qualifier/suites/uspace/required_services.yaml @@ -18,6 +18,7 @@ resources: evaluation_configuration: resources.netrid.EvaluationConfigurationResource netrid_dss_instances: resources.astm.f3411.DSSInstancesResource utm_client_identity: resources.communications.ClientIdentityResource + second_utm_auth: resources.communications.AuthAdapterResource id_generator: resources.interuss.IDGeneratorResource service_area: resources.netrid.ServiceAreaResource problematically_big_area: resources.VerticesResource @@ -45,6 +46,8 @@ actions: mock_uss: mock_uss dss: scd_dss dss_instances: scd_dss_instances + id_generator: id_generator + second_utm_auth: second_utm_auth on_failure: Continue - test_suite: suite_type: suites.uspace.network_identification