Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modifying Firewall rules to provide Internet Access to T0/T1 #2327

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions data_safe_haven/config/config_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class ConfigSectionSRE(BaseModel, validate_assignment=True):
# https://docs.pydantic.dev/latest/concepts/models/#fields-with-non-hashable-default-values
admin_email_address: EmailAddress
admin_ip_addresses: list[IpAddress] = []
allow_workspace_internet: bool = False
databases: UniqueList[DatabaseSystem] = []
data_provider_ip_addresses: list[IpAddress] | AzureServiceTag = []
remote_desktop: ConfigSubsectionRemoteDesktopOpts
Expand Down
2 changes: 2 additions & 0 deletions data_safe_haven/infrastructure/programs/declarative_sre.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def __call__(self) -> None:
"sre_dns_server",
self.stack_name,
SREDnsServerProps(
allow_workspace_internet=self.config.sre.allow_workspace_internet,
dockerhub_credentials=dockerhub_credentials,
location=self.config.azure.location,
resource_group_name=resource_group.name,
Expand Down Expand Up @@ -182,6 +183,7 @@ def __call__(self) -> None:
"sre_firewall",
self.stack_name,
SREFirewallProps(
allow_workspace_internet=self.config.sre.allow_workspace_internet,
location=self.config.azure.location,
log_analytics_workspace=monitoring.log_analytics,
resource_group_name=resource_group.name,
Expand Down
10 changes: 8 additions & 2 deletions data_safe_haven/infrastructure/programs/sre/dns_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ class SREDnsServerProps:

def __init__(
self,
*,
allow_workspace_internet: bool,
dockerhub_credentials: DockerHubCredentials,
location: Input[str],
resource_group_name: Input[str],
shm_fqdn: Input[str],
) -> None:
self.admin_username = "dshadmin"
self.allow_workspace_internet: bool = allow_workspace_internet
self.dockerhub_credentials = dockerhub_credentials
self.location = location
self.resource_group_name = resource_group_name
Expand Down Expand Up @@ -69,6 +72,9 @@ def __init__(
)

# Expand AdGuardHome YAML configuration
mustache_values: dict[str, object] = {
"allow_workspace_internet": props.allow_workspace_internet
}
adguard_adguardhome_yaml_contents = Output.all(
admin_username=props.admin_username,
# Only the first 72 bytes of the generated random string will be used but a
Expand All @@ -85,8 +91,8 @@ def __init__(
]
),
).apply(
lambda mustache_values: adguard_adguardhome_yaml_reader.file_contents(
mustache_values
lambda mustache_config: adguard_adguardhome_yaml_reader.file_contents(
mustache_config | mustache_values
)
)

Expand Down
414 changes: 226 additions & 188 deletions data_safe_haven/infrastructure/programs/sre/firewall.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ dns:
querylog:
enabled: true
filters:
{{#allow_workspace_internet}}
user_rules: []
{{/allow_workspace_internet}}
{{^allow_workspace_internet}}
user_rules:
# https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#adblock-style-syntax
- "*.*"
{{#filter_allow}}
- "@@||{{.}}"
{{/filter_allow}}
{{/allow_workspace_internet}}
log:
verbose: true
# Note that because we are only providing a partial config file we need the
Expand Down
1 change: 1 addition & 0 deletions tests/config/test_config_sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def test_constructor_defaults(
)
assert sre_config.admin_email_address == "[email protected]"
assert sre_config.admin_ip_addresses == []
assert not sre_config.allow_workspace_internet
assert sre_config.databases == []
assert sre_config.data_provider_ip_addresses == []
assert sre_config.remote_desktop == config_subsection_remote_desktop
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ def sre_config_yaml(request):
admin_email_address: [email protected]
admin_ip_addresses:
- 1.2.3.4/32
allow_workspace_internet: false
data_provider_ip_addresses: []
databases: []
remote_desktop:
Expand Down
83 changes: 83 additions & 0 deletions tests/infrastructure/programs/sre/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
import pulumi
import pulumi.runtime
from pulumi_azure_native import managedidentity, network, resources
from pytest import fixture

from data_safe_haven.infrastructure.common import SREIpRanges

# Mock configuration.


class DataSafeHavenMocks(pulumi.runtime.Mocks):
def new_resource(self, args: pulumi.runtime.MockResourceArgs):
resources = [args.name + "_id", args.inputs]
return resources

def call(self, _: pulumi.runtime.MockCallArgs):
return {}


pulumi.runtime.set_mocks(
DataSafeHavenMocks(),
preview=False,
)


#
# Constants
Expand Down Expand Up @@ -75,3 +94,67 @@ def subnet_guacamole_containers() -> network.GetSubnetResult:
address_prefix=SREIpRanges.guacamole_containers.prefix,
id="subnet_guacamole_containers_id",
)


@fixture
def subnet_apt_proxy_server() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.apt_proxy_server.prefix,
id="subnet_apt_proxy_server_id",
)


@fixture
def subnet_clamav_mirror() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.clamav_mirror.prefix,
id="subnet_clamav_mirror_id",
)


@fixture
def subnet_firewall() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.firewall.prefix,
id="subnet_firewall_id",
)


@fixture
def subnet_firewall_management() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.firewall_management.prefix,
id="subnet_firewall_management_id",
)


@fixture
def subnet_identity_containers() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.identity_containers.prefix,
id="subnet_identity_containers_id",
)


@fixture
def subnet_user_services_software_repositories() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.user_services_software_repositories.prefix,
id="subnet_user_services_software_repositories_id",
)


@fixture
def subnet_workspaces() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.workspaces.prefix,
id="subnet_workspaces_id",
)


@fixture
def subnet_monitoring() -> network.GetSubnetResult:
return network.GetSubnetResult(
address_prefix=SREIpRanges.monitoring.prefix,
id="subnet_monitoring_id",
)
76 changes: 58 additions & 18 deletions tests/infrastructure/programs/sre/test_application_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_props_resource_group_id(
self, application_gateway_props: SREApplicationGatewayProps
):
application_gateway_props.resource_group_id.apply(
partial(assert_equal, pulumi.UNKNOWN),
partial(assert_equal, "resource_group_id"),
run_with_unknowns=True,
)

Expand Down Expand Up @@ -112,7 +112,7 @@ def test_props_user_assigned_identities(
self, application_gateway_props: SREApplicationGatewayProps
):
application_gateway_props.user_assigned_identities.apply(
partial(assert_equal, pulumi.UNKNOWN),
partial(assert_equal, {"identity_key_vault_reader_id": {}}),
run_with_unknowns=True,
)

Expand Down Expand Up @@ -282,7 +282,7 @@ def test_application_gateway_frontend_ip_configurations(
"name": "appGatewayFrontendIP",
"private_ip_allocation_method": "Dynamic",
"provisioning_state": None,
"public_ip_address": {"id": None},
"public_ip_address": {"id": "ag-name_public_ip_id"},
"type": None,
}
],
Expand Down Expand Up @@ -356,8 +356,12 @@ def test_application_gateway_http_listeners(
[
{
"etag": None,
"frontend_ip_configuration": {"id": None},
"frontend_port": {"id": None},
"frontend_ip_configuration": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/frontendIPConfigurations/appGatewayFrontendIP"
},
"frontend_port": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/frontendPorts/appGatewayFrontendHttp"
},
"host_name": "sre.example.com",
"name": "GuacamoleHttpListener",
"protocol": "Http",
Expand All @@ -366,13 +370,19 @@ def test_application_gateway_http_listeners(
},
{
"etag": None,
"frontend_ip_configuration": {"id": None},
"frontend_port": {"id": None},
"frontend_ip_configuration": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/frontendIPConfigurations/appGatewayFrontendIP"
},
"frontend_port": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/frontendPorts/appGatewayFrontendHttps"
},
"host_name": "sre.example.com",
"name": "GuacamoleHttpsListener",
"protocol": "Https",
"provisioning_state": None,
"ssl_certificate": {"id": None},
"ssl_certificate": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/sslCertificates/letsencryptcertificate"
},
"type": None,
},
],
Expand All @@ -387,7 +397,17 @@ def test_application_gateway_identity(
application_gateway_component.application_gateway.identity.apply(
partial(
assert_equal_json,
{"principal_id": None, "tenant_id": None, "type": "UserAssigned"},
{
"principal_id": None,
"tenant_id": None,
"type": "UserAssigned",
"user_assigned_identities": {
"identity_key_vault_reader_id": {
"client_id": None,
"principal_id": None,
}
},
},
),
run_with_unknowns=True,
)
Expand Down Expand Up @@ -489,8 +509,14 @@ def test_application_gateway_redirect_configurations(
"include_query_string": True,
"name": "GuacamoleHttpToHttpsRedirection",
"redirect_type": "Permanent",
"request_routing_rules": [{"id": None}],
"target_listener": {"id": None},
"request_routing_rules": [
{
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/requestRoutingRules/HttpToHttpsRedirection"
}
],
"target_listener": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/httpListeners/GuacamoleHttpsListener"
},
"type": None,
}
],
Expand All @@ -508,24 +534,38 @@ def test_application_gateway_request_routing_rules(
[
{
"etag": None,
"http_listener": {"id": None},
"http_listener": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/httpListeners/GuacamoleHttpListener"
},
"name": "GuacamoleHttpRouting",
"priority": 200,
"provisioning_state": None,
"redirect_configuration": {"id": None},
"rewrite_rule_set": {"id": None},
"redirect_configuration": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/redirectConfigurations/GuacamoleHttpToHttpsRedirection"
},
"rewrite_rule_set": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/rewriteRuleSets/ResponseHeaders"
},
"rule_type": "Basic",
"type": None,
},
{
"backend_address_pool": {"id": None},
"backend_http_settings": {"id": None},
"backend_address_pool": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/backendAddressPools/appGatewayBackendGuacamole"
},
"backend_http_settings": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/backendHttpSettingsCollection/appGatewayBackendHttpSettings"
},
"etag": None,
"http_listener": {"id": None},
"http_listener": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/httpListeners/GuacamoleHttpsListener"
},
"name": "GuacamoleHttpsRouting",
"priority": 100,
"provisioning_state": None,
"rewrite_rule_set": {"id": None},
"rewrite_rule_set": {
"id": "resource_group_id/providers/Microsoft.Network/applicationGateways/stack-example-ag-entrypoint/rewriteRuleSets/ResponseHeaders"
},
"rule_type": "Basic",
"type": None,
},
Expand Down
Loading
Loading