Skip to content

Commit

Permalink
revisit-simplify scenario
Browse files Browse the repository at this point in the history
  • Loading branch information
mickmis committed Dec 5, 2024
1 parent f8757c2 commit cc967b6
Show file tree
Hide file tree
Showing 15 changed files with 84 additions and 197 deletions.
2 changes: 0 additions & 2 deletions monitoring/uss_qualifier/configurations/dev/dss_probing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ v1:
kentland_service_area: { $ref: 'library/resources.yaml#/kentland_service_area' }
kentland_planning_area: { $ref: 'library/resources.yaml#/kentland_planning_area' }
kentland_problematically_big_area: { $ref: 'library/resources.yaml#/kentland_problematically_big_area' }
kentland_acceptable_search_area: { $ref: 'library/resources.yaml#/kentland_acceptable_search_area' }
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' }
Expand Down Expand Up @@ -35,7 +34,6 @@ v1:
service_area: kentland_service_area
planning_area: kentland_planning_area
problematically_big_area: kentland_problematically_big_area
acceptable_search_area: kentland_acceptable_search_area
second_utm_auth: second_utm_auth
flight_intents: che_non_conflicting_flights
test_exclusions: test_exclusions
Expand Down
14 changes: 0 additions & 14 deletions monitoring/uss_qualifier/configurations/dev/library/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,6 @@ kentland_service_area:
time_start: '2023-01-10T00:00:01.123456+00:00'
time_end: '2023-01-10T01:00:01.123456+00:00'

kentland_acceptable_search_area:
dependencies: { }
resource_type: resources.VerticesResource
specification:
vertices:
- lat: 37.1853
lng: -80.614
- lat: 37.2148
lng: -80.614
- lat: 37.2148
lng: -80.544
- lat: 37.1853
lng: -80.544

kentland_planning_area:
$content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json
resource_type: resources.astm.f3548.v21.PlanningAreaResource
Expand Down
2 changes: 0 additions & 2 deletions monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ v1:
utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'}
id_generator: {$ref: 'library/resources.yaml#/id_generator'}
kentland_service_area: {$ref: 'library/resources.yaml#/kentland_service_area'}
kentland_acceptable_search_area: {$ref: 'library/resources.yaml#/kentland_acceptable_search_area'}
au_problematically_big_area: {$ref: 'library/resources.yaml#/au_problematically_big_area'}

utm_auth: {$ref: 'library/environment.yaml#/utm_auth'}
Expand All @@ -35,7 +34,6 @@ v1:
id_generator: id_generator
service_area: kentland_service_area
problematically_big_area: au_problematically_big_area
acceptable_search_area: kentland_acceptable_search_area
test_exclusions: test_exclusions
execution:
stop_fast: true
Expand Down
2 changes: 0 additions & 2 deletions monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ v1:
utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'}
id_generator: {$ref: 'library/resources.yaml#/id_generator'}
kentland_service_area: {$ref: 'library/resources.yaml#/kentland_service_area'}
kentland_acceptable_search_area: {$ref: 'library/resources.yaml#/kentland_acceptable_search_area'}
au_problematically_big_area: {$ref: 'library/resources.yaml#/au_problematically_big_area'}

utm_auth: {$ref: 'library/environment.yaml#/utm_auth'}
Expand All @@ -35,7 +34,6 @@ v1:
id_generator: id_generator
service_area: kentland_service_area
problematically_big_area: au_problematically_big_area
acceptable_search_area: kentland_acceptable_search_area
test_exclusions: test_exclusions
execution:
stop_fast: true
Expand Down
3 changes: 0 additions & 3 deletions monitoring/uss_qualifier/configurations/dev/uspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ v1:
utm_client_identity: {$ref: 'library/resources.yaml#/utm_client_identity'}
id_generator: {$ref: 'library/resources.yaml#/id_generator'}
kentland_service_area: {$ref: 'library/resources.yaml#/kentland_service_area'}
kentland_acceptable_search_area: {$ref: 'library/resources.yaml#/kentland_acceptable_search_area'}
au_problematically_big_area: {$ref: 'library/resources.yaml#/au_problematically_big_area'}

utm_auth: {$ref: 'library/environment.yaml#/utm_auth'}
Expand Down Expand Up @@ -74,7 +73,6 @@ v1:
service_area: kentland_service_area
planning_area: che_planning_area
problematically_big_area: au_problematically_big_area
acceptable_search_area: kentland_acceptable_search_area
test_exclusions: test_exclusions
specification:
mock_uss_instances_source: mock_uss_instances
Expand Down Expand Up @@ -107,7 +105,6 @@ v1:
service_area: service_area
planning_area: planning_area
problematically_big_area: problematically_big_area
acceptable_search_area: acceptable_search_area
test_exclusions: test_exclusions
execution:
stop_fast: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import errno
import requests
import socket
import uas_standards.astm.f3411.v19.api
import uas_standards.astm.f3411.v22a.api
from uas_standards.astm.f3411 import v19, v22a
from urllib.parse import urlparse

import requests
from future.backports.datetime import datetime

from monitoring.monitorlib import infrastructure
from monitoring.monitorlib.fetch import rid as fetch
from monitoring.uss_qualifier.resources import VerticesResource
from monitoring.monitorlib.rid import RIDVersion
from monitoring.uss_qualifier.resources.astm.f3411.dss import DSSInstanceResource
from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario
from monitoring.uss_qualifier.scenarios.scenario import (
GenericTestScenario,
)
from monitoring.uss_qualifier.suites.suite import ExecutionContext


Expand All @@ -25,30 +26,31 @@ class EndpointEncryption(GenericTestScenario):
def __init__(
self,
dss: DSSInstanceResource,
test_search_area: VerticesResource,
):
super().__init__()
self._dss = dss.dss_instance
self._search_area = [
v.as_s2sphere() for v in test_search_area.specification.vertices
]

self._parsed_url = urlparse(self._dss.base_url)
self._hostname = self._parsed_url.hostname
if self._dss.rid_version == RIDVersion.f3411_19:
op = v19.api.OPERATIONS[v19.api.OperationID.GetIdentificationServiceArea]
elif self._dss.rid_version == RIDVersion.f3411_22a:
op = v22a.api.OPERATIONS[v22a.api.OperationID.GetIdentificationServiceArea]
else:
raise NotImplementedError(
f"Scenario does not support RID version {self._dss.rid_version}"
)

self._http_base_url = f"http://{self._hostname}/{self._parsed_url.path}"
non_existing_id = "00000000-0000-0000-0000-000000000000"
http_base_url = urlparse(self._dss.base_url)._replace(scheme="http")
self._http_get_url = f"{http_base_url.geturl()}{op.path}".replace(
"{id}", non_existing_id
)
self._https_get_url = f"{self._dss.base_url}{op.path}".replace(
"{id}", non_existing_id
)

def run(self, context: ExecutionContext):
self.begin_test_scenario(context)

if self._hostname is None:
self.record_note(
"hostname",
"Cannot check encryption requirement when DSS hostname is unspecified",
)
self.end_test_scenario()
return

if not self._dss.base_url.startswith("https://"):
self.record_note(
"encrypted_endpoints",
Expand All @@ -57,113 +59,63 @@ def run(self, context: ExecutionContext):
self.end_test_scenario()
return

self._case_http_unavailable_or_redirect()
self._case_https_works()
self.begin_test_case("Validate endpoint encryption")
self._step_http_unavailable_or_redirect()
self._step_https_works()
self.end_test_case()

self.end_test_scenario()

def _case_http_unavailable_or_redirect(self):
self.begin_test_case("Connect to HTTP port")
self.begin_test_step("Attempt GET on root path via HTTP")
def _step_http_unavailable_or_redirect(self):
self.begin_test_step("Attempt GET on a known valid path via HTTP")

with self.check(
"Connection to HTTP port fails or redirects to HTTPS port",
"HTTP GET fails or redirects to HTTPS",
self._dss.participant_id,
) as check:
try:
response = requests.get(
self._http_base_url,
self._http_get_url,
timeout=10,
allow_redirects=False,
)
_check_is_redirect(self._parsed_url, check, response)
if not response.url.startswith("https://"):
# response.url contains the url of the final request after all redirects have been followed, if any
check.record_failed(
"HTTP GET request did not redirect to HTTPS",
details=f"Made an http GET request and obtained status code {response.status_code} with response {str(response.content)} that was not redirected to https",
)

except socket.error as e:
if e.errno not in [errno.ECONNREFUSED, errno.ETIMEDOUT]:
check.record_failed(
"Connection to HTTP port failed for the unexpected reason",
"Connection to HTTP port failed for an unexpected reason",
details=f"Encountered socket error: {e}, while the expectation is to either run into a straight up connection refusal or a timeout.",
)

self.begin_test_step("Attempt GET on a known valid path via HTTP")
self.end_test_step()

def _step_https_works(self):
self.begin_test_step("Attempt GET on a known valid path via HTTPS")

with self.check(
"Connection to HTTP port fails or redirects to HTTPS port",
"HTTPS GET succeeds",
self._dss.participant_id,
) as check:
try:
response = fetch.isas(
area=self._search_area,
start_time=datetime.now(),
end_time=datetime.now() + datetime.timedelta(days=1),
rid_version=self._dss.rid_version,
session=infrastructure.UTMClientSession(
self._http_base_url, self._dss.client.auth_adapter
),
participant_id=self._dss.participant_id,
response = requests.get(
self._https_get_url,
timeout=10,
)
_check_is_redirect(self._parsed_url, check, response)
except socket.error as e:
if e.errno not in [errno.ECONNREFUSED, errno.ETIMEDOUT]:
check.record_failed(
"Connection to HTTP port failed for the unexpected reason",
details=f"Encountered socket error: {e}, while the expectation is to either run into a straight up connection refusal or a timeout.",
)

self.end_test_step()
self.end_test_case()

def _case_https_works(self):
parsed_url = urlparse(self._dss.base_url)
hostname = parsed_url.hostname

self.begin_test_case("Connect to HTTPS port")
self.begin_test_step("Attempt GET on root path via HTTP test")

if hostname is not None:
with self.check(
"Connection fails or response redirects to HTTPS endpoint",
self._dss.participant_id,
) as check:
try:
requests.get(
f"https://{hostname}/{parsed_url.path}",
timeout=10,
allow_redirects=False,
)
# We don't care about the response details, just that the connection was successful
# (a 404 would still indicate that HTTPS is working well)
except requests.RequestException as e:
if not response.url.startswith("https://"):
# response.url contains the url of the final request after all redirects have been followed, if any
check.record_failed(
"Connection to HTTPS port failed",
details=f"Encountered exception while attempting HTTPS request: {e}",
"HTTPS GET request redirected to HTTP",
details=f"Made an https GET request and obtained status code {response.status_code} with response {str(response.content)} that was redirected to http",
)
except requests.RequestException as e:
check.record_failed(
"Connection to HTTPS port failed",
details=f"Encountered exception while attempting HTTPS request: {e}",
)

self.end_test_step()
self.end_test_case()


def _check_is_redirect(parsed_url, check, response):
# If we can connect, we want to check that we are being redirected:
# (a 4XX response is already a form of communication that we don't want in cleartext)
if response.status_code not in [301, 302, 307, 308]:
check.record_failed(
"Connection to HTTP port did not redirect",
details=f"Was expecting a 301 or 308 response, but obtained status code: {response.status_code}",
)
if "Location" not in response.headers:
check.record_failed(
"Location header missing in redirect response",
details="Was expecting a Location header in the response, but it was not present",
)
if response.headers.get("Location").startswith("http://"):
check.record_failed(
"Connection to HTTP port redirected to HTTP",
details=f"Was expecting a redirection to an https:// URL. Location header: {response.headers.get('Location')}",
)
if not response.headers.get("Location").startswith(
f"https://{parsed_url.hostname}/{parsed_url.path}"
):
check.record_failed(
"Redirect to unexpected destination",
details=f"Was expecting a redirection to https://{parsed_url.hostname}/{parsed_url.path}, was {response.headers.get('Location')}",
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,25 @@ Ensures that a DSS only exposes its endpoints via HTTPS.

[`DSSInstanceResource`](../../../../../resources/astm/f3411/dss.py) to be tested in this scenario.

### test_search_area

[`VerticesResource`](../../../../../resources/vertices.py) to be used in this scenario for a search query.

## Connect to HTTP port test case
## Validate endpoint encryption test case

Tries to connect to the http port (80) of the DSS instance, and expects either a refusal of the connection,
or a redirection to the https port (443).

Note: this test case will be skipped if the DSS instance is configured to use HTTP.

### Attempt GET on root path via HTTP test step

This test step attempts an HTTP GET request on the root path of the DSS instance, using plain HTTP,
and expects either a connection refusal or a redirection to the equivalent HTTPS URL.

#### 🛑 Connection fails or response redirects to HTTPS endpoint check

If the DSS instance accepts the connection on the HTTP port and does not immediately redirect to the HTTPS port
upon reception of an HTTP request, it is in violation of **[astm.f3411.v19.DSS0020](../../../../../requirements/astm/f3411/v19.md)**.
Note that this test case will be skipped if the DSS instance is configured to use HTTP.
Note that the requests made in this case are made without any form of authentication, as the completion of any form
of communication over an unencrypted channel, even a 40X status response, is considered a failure.

### Attempt GET on a known valid path via HTTP test step
Attempts the operation `GetIdentificationServiceArea` on the DSS through HTTP with a non-existing ID.

This test step attempts an HTTP GET request on a known valid path by searching for ISAs in the configured planning area.

#### 🛑 Connection fails or response redirects to HTTPS endpoint check

If the DSS instance accepts the connection on the HTTP port and does not immediately redirect to the HTTPS port
upon reception of an HTTP request, it is in violation of **[astm.f3411.v19.DSS0020](../../../../../requirements/astm/f3411/v19.md)**.

## Connect to HTTPS port test case

Try to connect to the DSS instance over HTTPS.

### Attempt to connect to the DSS instance on the HTTPS port test step
#### 🛑 HTTP GET fails or redirects to HTTPS check
If the DSS instance serves the request through unencrypted HTTP, it is in violation of **[astm.f3411.v19.DSS0020](../../../../../requirements/astm/f3411/v19.md)**.
Only the last request after all redirections are followed is considered.

#### 🛑 A request can be sent over HTTPS check
### Attempt GET on a known valid path via HTTPS test step
Attempts the operation `GetIdentificationServiceArea` on the DSS through HTTPS with a non-existing ID.

If the DSS instance cannot be reached over HTTPS, it is in violation of **[astm.f3411.v19.DSS0020](../../../../../requirements/astm/f3411/v19.md)**.
#### 🛑 HTTPS GET succeeds check
If the DSS instance does not serve the request through encrypted HTTPS, or redirects it to HTTP, it is in violation of **[astm.f3411.v19.DSS0020](../../../../../requirements/astm/f3411/v19.md)**.
Only the last request after all redirections are followed is considered.
Loading

0 comments on commit cc967b6

Please sign in to comment.