Skip to content

Commit

Permalink
[uss_qualifier] netrid: DSS0020 - check DSS endpoints are encrypted
Browse files Browse the repository at this point in the history
  • Loading branch information
Shastick committed Nov 21, 2024
1 parent 8321189 commit ef4f9cc
Show file tree
Hide file tree
Showing 26 changed files with 394 additions and 27 deletions.
3 changes: 2 additions & 1 deletion monitoring/uss_qualifier/configurations/dev/dss_probing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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,6 +35,7 @@ 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: 14 additions & 0 deletions monitoring/uss_qualifier/configurations/dev/library/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ 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: 2 additions & 0 deletions monitoring/uss_qualifier/configurations/dev/netrid_v19.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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 @@ -34,6 +35,7 @@ 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: 2 additions & 0 deletions monitoring/uss_qualifier/configurations/dev/netrid_v22a.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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 @@ -34,6 +35,7 @@ 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
5 changes: 3 additions & 2 deletions monitoring/uss_qualifier/configurations/dev/uspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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 @@ -73,7 +74,7 @@ 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 @@ -106,7 +107,7 @@ 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
@@ -0,0 +1,169 @@
import errno
import socket
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.uss_qualifier.resources.astm.f3411.dss import DSSInstanceResource
from monitoring.uss_qualifier.scenarios.scenario import GenericTestScenario
from monitoring.uss_qualifier.suites.suite import ExecutionContext


class EndpointEncryption(GenericTestScenario):
"""
Ensures that the endpoints of a DSS are not accessible unencrypted:
- HTTP access should be impossible or redirect to HTTPS
- HTTPS access should be possible
TODO: add a check for minimal cipher strength to a 128bit AES equivalent or more.
"""

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

self._http_base_url = f"http://{self._hostname}/{self._parsed_url.path}"

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",
"Cannot check encryption requirement when DSS endpoint is specified with an http:// base URL",
)
self.end_test_scenario()
return

self._case_http_unavailable_or_redirect()
self._case_https_works()

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")

with self.check(
"Connection to HTTP port fails or redirects to HTTPS port",
self._dss.participant_id,
) as check:
try:
response = requests.get(
self._http_base_url,
timeout=10,
allow_redirects=False,
)
_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.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",
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,
)
_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:
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 @@ -7,3 +7,4 @@
from .token_validation import TokenValidation
from .crdb_access import CRDBAccess
from .heavy_traffic_concurrent import HeavyTrafficConcurrent
from .endpoint_encryption import EndpointEncryption
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# ASTM NetRID DSS: Endpoint encryption test scenario

## Overview

Ensures that a DSS only exposes its endpoints via HTTPS.

## Resources

### dss

[`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

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)**.

### Attempt GET on a known valid path via HTTP test step

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

#### 🛑 A request can be sent over HTTPS check

If the DSS instance cannot be reached over HTTPS, it is in violation of **[astm.f3411.v19.DSS0020](../../../../../requirements/astm/f3411/v19.md)**.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from monitoring.uss_qualifier.scenarios.astm.netrid.common.dss.endpoint_encryption import (
EndpointEncryption as CommonEndpointEncryption,
)
from monitoring.uss_qualifier.scenarios.scenario import TestScenario


class EndpointEncryption(TestScenario, CommonEndpointEncryption):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from .token_validation import TokenValidation
from .crdb_access import CRDBAccess
from .heavy_traffic_concurrent import HeavyTrafficConcurrent
from .endpoint_encryption import EndpointEncryption
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ASTM NetRID DSS: Endpoint encryption test scenario

## Overview

Ensures that a DSS only exposes its endpoints via HTTPS.

## Resources

### dss

[`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

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 to connect to the DSS instance on the HTTP port test step

#### 🛑 Connection to HTTP port fails or redirects to HTTPS port 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.v22a.DSS0020](../../../../../requirements/astm/f3411/v22a.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

#### 🛑 A request can be sent over HTTPS check

If the DSS instance cannot be reached over HTTPS, it is in violation of **[astm.f3411.v22a.DSS0020](../../../../../requirements/astm/f3411/v22a.md)**.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from monitoring.uss_qualifier.scenarios.astm.netrid.common.dss.endpoint_encryption import (
EndpointEncryption as CommonEndpointEncryption,
)
from monitoring.uss_qualifier.scenarios.scenario import TestScenario


class EndpointEncryption(TestScenario, CommonEndpointEncryption):
pass
5 changes: 5 additions & 0 deletions monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<td>Implemented</td>
<td><a href="../../../scenarios/astm/netrid/v19/dss/token_validation.md">ASTM NetRID DSS: Token Validation</a></td>
</tr>
<tr>
<td><a href="../../../requirements/astm/f3411/v19.md">DSS0020</a></td>
<td>Implemented</td>
<td><a href="../../../scenarios/astm/netrid/v19/dss/endpoint_encryption.md">ASTM NetRID DSS: Endpoint encryption</a></td>
</tr>
<tr>
<td><a href="../../../requirements/astm/f3411/v19.md">DSS0030,a</a></td>
<td>Implemented</td>
Expand Down
Loading

0 comments on commit ef4f9cc

Please sign in to comment.