From d126daf75c0188ea998d19590a67d0246b83e62e Mon Sep 17 00:00:00 2001 From: James Robinson Date: Wed, 4 Oct 2023 14:37:48 +0100 Subject: [PATCH 01/15] :recycle: Use the pulumi-random provider to generate the database service admin password, rather than providing this through the Pulumi config --- data_safe_haven/commands/deploy_sre.py | 1 - data_safe_haven/infrastructure/stack_manager.py | 3 +++ data_safe_haven/infrastructure/stacks/sre/data.py | 15 +++++++++------ pyproject.toml | 1 + 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index c88f2df404..b5eecc51cd 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -141,7 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret("password-database-service-admin", password(20), replace=False) stack.add_secret("password-dns-server-admin", password(20), replace=False) stack.add_secret("password-gitea-database-admin", password(20), replace=False) stack.add_secret( diff --git a/data_safe_haven/infrastructure/stack_manager.py b/data_safe_haven/infrastructure/stack_manager.py index 0acc55b7e6..084a927964 100644 --- a/data_safe_haven/infrastructure/stack_manager.py +++ b/data_safe_haven/infrastructure/stack_manager.py @@ -253,6 +253,9 @@ def install_plugins(self) -> None: self.stack.workspace.install_plugin( "azure-native", metadata.version("pulumi-azure-native") ) + self.stack.workspace.install_plugin( + "random", metadata.version("pulumi-random") + ) except Exception as exc: msg = f"Installing Pulumi plugins failed.\n{exc}." raise DataSafeHavenPulumiError(msg) from exc diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index e66649aa2e..5ec92c2930 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -2,6 +2,7 @@ from collections.abc import Mapping, Sequence from typing import ClassVar +import pulumi_random from pulumi import ComponentResource, Config, Input, Output, ResourceOptions from pulumi_azure_native import ( authorization, @@ -68,9 +69,6 @@ def __init__( self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_database_service_admin = self.get_secret( - pulumi_opts, "password-database-service-admin" - ) self.password_dns_server_admin = self.get_secret( pulumi_opts, "password-dns-server-admin" ) @@ -254,11 +252,14 @@ def __init__( ), ) - # Deploy key vault secrets + # Secret: database service admin password + password_database_service_admin = pulumi_random.RandomPassword( + f"{self._name}_password_database_service_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_database_service_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_database_service_admin + value=password_database_service_admin.result, ), resource_group_name=resource_group.name, secret_name="password-database-service-admin", @@ -266,6 +267,8 @@ def __init__( opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_dns_server_admin", properties=keyvault.SecretPropertiesArgs( @@ -729,7 +732,7 @@ def __init__( self.managed_identity = identity_key_vault_reader self.password_nexus_admin = Output.secret(props.password_nexus_admin) self.password_database_service_admin = Output.secret( - props.password_database_service_admin + password_database_service_admin.result ) self.password_dns_server_admin = Output.secret(props.password_dns_server_admin) self.password_gitea_database_admin = Output.secret( diff --git a/pyproject.toml b/pyproject.toml index 45d64e4052..682be875e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dependencies = [ "psycopg~=3.1.10", "pulumi~=3.80.0", "pulumi-azure-native~=1.104.0", + "pulumi-random~=4.14.0", "pytz~=2022.7.0", "PyYAML~=6.0", "rich~=13.4.2", From 5955f54a063f0ae6a7a73216e27c7aadc13c4b28 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:18:20 +0100 Subject: [PATCH 02/15] :wrench: Ignore 'pulumi_random' in mypy --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 682be875e7..f8538fb66d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -158,6 +158,7 @@ module = [ "psycopg.*", "pulumi.*", "pulumi_azure_native.*", + "pulumi_random.*", "pymssql.*", "rich.*", "simple_acme_dns.*", From b49b583feb1212e468b5e15a72d21f91a9aaf54f Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:17:31 +0100 Subject: [PATCH 03/15] :truck: Update 'password-dns-server-admin' to use pulumi-random --- data_safe_haven/commands/deploy_sre.py | 1 - .../infrastructure/stacks/declarative_sre.py | 2 +- data_safe_haven/infrastructure/stacks/sre/data.py | 15 +++++++++------ .../infrastructure/stacks/sre/dns_server.py | 11 ++++++++--- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index b5eecc51cd..0e330fd35f 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -141,7 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret("password-dns-server-admin", password(20), replace=False) stack.add_secret("password-gitea-database-admin", password(20), replace=False) stack.add_secret( "password-hedgedoc-database-admin", password(20), replace=False diff --git a/data_safe_haven/infrastructure/stacks/declarative_sre.py b/data_safe_haven/infrastructure/stacks/declarative_sre.py index 907ebfe667..e47bf8c6c4 100644 --- a/data_safe_haven/infrastructure/stacks/declarative_sre.py +++ b/data_safe_haven/infrastructure/stacks/declarative_sre.py @@ -80,7 +80,6 @@ def run(self) -> None: "sre_dns_server", self.stack_name, SREDnsServerProps( - admin_password=self.pulumi_opts.require("password-dns-server-admin"), admin_password_salt=self.pulumi_opts.require("salt-dns-server-admin"), location=self.cfg.azure.location, shm_fqdn=self.cfg.shm.fqdn, @@ -163,6 +162,7 @@ def run(self) -> None: self.sre_name ].data_provider_ip_addresses, dns_record=networking.shm_ns_record, + dns_server_admin_password=dns.password_admin, location=self.cfg.azure.location, networking_resource_group=networking.resource_group, pulumi_opts=self.pulumi_opts, diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index 5ec92c2930..dc8808bac8 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -44,6 +44,7 @@ def __init__( admin_ip_addresses: Input[Sequence[str]], data_provider_ip_addresses: Input[Sequence[str]], dns_record: Input[network.RecordSet], + dns_server_admin_password: Input[pulumi_random.RandomPassword], location: Input[str], networking_resource_group: Input[resources.ResourceGroup], pulumi_opts: Config, @@ -65,13 +66,11 @@ def __init__( } ) self.dns_record = dns_record + self.password_dns_server_admin = dns_server_admin_password self.location = location self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_dns_server_admin = self.get_secret( - pulumi_opts, "password-dns-server-admin" - ) self.password_gitea_database_admin = self.get_secret( pulumi_opts, "password-gitea-database-admin" ) @@ -268,11 +267,11 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: database service admin password keyvault.Secret( f"{self._name}_kvs_password_dns_server_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_dns_server_admin + value=props.password_dns_server_admin.result, ), resource_group_name=resource_group.name, secret_name="password-dns-server-admin", @@ -280,6 +279,8 @@ def __init__( opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_gitea_database_admin", properties=keyvault.SecretPropertiesArgs( @@ -734,7 +735,9 @@ def __init__( self.password_database_service_admin = Output.secret( password_database_service_admin.result ) - self.password_dns_server_admin = Output.secret(props.password_dns_server_admin) + self.password_dns_server_admin = Output.secret( + props.password_dns_server_admin.result + ) self.password_gitea_database_admin = Output.secret( props.password_gitea_database_admin ) diff --git a/data_safe_haven/infrastructure/stacks/sre/dns_server.py b/data_safe_haven/infrastructure/stacks/sre/dns_server.py index ee5e08f7df..133c8f5ce2 100644 --- a/data_safe_haven/infrastructure/stacks/sre/dns_server.py +++ b/data_safe_haven/infrastructure/stacks/sre/dns_server.py @@ -1,6 +1,7 @@ """Pulumi component for SRE DNS server""" from collections.abc import Mapping +import pulumi_random from pulumi import ComponentResource, Input, Output, ResourceOptions from pulumi_azure_native import containerinstance, network, resources @@ -25,7 +26,6 @@ class SREDnsServerProps: def __init__( self, - admin_password: Input[str], admin_password_salt: Input[str], location: Input[str], shm_fqdn: Input[str], @@ -33,7 +33,6 @@ def __init__( sre_index: Input[int], ) -> None: subnet_ranges = Output.from_input(sre_index).apply(lambda idx: SREIpRanges(idx)) - self.admin_password = Output.secret(admin_password) self.admin_password_salt = Output.secret(admin_password_salt) self.admin_username = "dshadmin" self.ip_range_prefix = str(SREDnsIpRanges().vnet) @@ -67,6 +66,11 @@ def __init__( tags=child_tags, ) + # Generate admin password + password_admin = pulumi_random.RandomPassword( + f"{self._name}_password_admin", length=20, special=True + ) + # Read AdGuardHome setup files adguard_entrypoint_sh_reader = FileReader( resources_path / "dns_server" / "entrypoint.sh" @@ -79,7 +83,7 @@ def __init__( adguard_adguardhome_yaml_contents = Output.all( admin_username=props.admin_username, admin_password_encrypted=Output.all( - password=props.admin_password, salt=props.admin_password_salt + password=password_admin.result, salt=props.admin_password_salt ).apply(lambda kwargs: bcrypt_encode(kwargs["password"], kwargs["salt"])), # Use Azure virtual DNS server as upstream # https://learn.microsoft.com/en-us/azure/virtual-network/what-is-ip-address-168-63-129-16 @@ -327,5 +331,6 @@ def __init__( # Register outputs self.ip_address = get_ip_address_from_container_group(container_group) + self.password_admin = password_admin self.resource_group = resource_group self.virtual_network = virtual_network From 2372c119ced850d08bdfd911caefc75c080d0ead Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:37:03 +0100 Subject: [PATCH 04/15] :truck: Update 'password-gitea-database-admin' to use pulumi-random --- data_safe_haven/commands/deploy_sre.py | 1 - data_safe_haven/infrastructure/stacks/sre/data.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index 0e330fd35f..cec11f62a3 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -141,7 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret("password-gitea-database-admin", password(20), replace=False) stack.add_secret( "password-hedgedoc-database-admin", password(20), replace=False ) diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index dc8808bac8..e85a96644c 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -71,9 +71,6 @@ def __init__( self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_gitea_database_admin = self.get_secret( - pulumi_opts, "password-gitea-database-admin" - ) self.password_hedgedoc_database_admin = self.get_secret( pulumi_opts, "password-hedgedoc-database-admin" ) @@ -280,11 +277,14 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Gitea database admin password + password_gitea_database_admin = pulumi_random.RandomPassword( + f"{self._name}_password_gitea_database_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_gitea_database_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_gitea_database_admin + value=password_gitea_database_admin.result ), resource_group_name=resource_group.name, secret_name="password-gitea-database-admin", @@ -292,6 +292,8 @@ def __init__( opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_hedgedoc_database_admin", properties=keyvault.SecretPropertiesArgs( @@ -739,7 +741,7 @@ def __init__( props.password_dns_server_admin.result ) self.password_gitea_database_admin = Output.secret( - props.password_gitea_database_admin + password_gitea_database_admin.result ) self.password_hedgedoc_database_admin = Output.secret( props.password_hedgedoc_database_admin From b874f122195d4dab4020fbcbcb65b134f41156d4 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:38:25 +0100 Subject: [PATCH 05/15] :truck: Update 'password-hedgedoc-database-admin' to use pulumi-random --- data_safe_haven/commands/deploy_sre.py | 3 --- data_safe_haven/infrastructure/stacks/sre/data.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index cec11f62a3..a11dca3a21 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -141,9 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret( - "password-hedgedoc-database-admin", password(20), replace=False - ) stack.add_secret("password-nexus-admin", password(20), replace=False) stack.add_secret("password-user-database-admin", password(20), replace=False) stack.add_secret("password-workspace-admin", password(20), replace=False) diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index e85a96644c..5b35d1a0ab 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -71,9 +71,6 @@ def __init__( self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_hedgedoc_database_admin = self.get_secret( - pulumi_opts, "password-hedgedoc-database-admin" - ) self.password_nexus_admin = self.get_secret(pulumi_opts, "password-nexus-admin") self.password_user_database_admin = self.get_secret( pulumi_opts, "password-user-database-admin" @@ -293,11 +290,14 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Hedgedoc database admin password + password_hedgedoc_database_admin = pulumi_random.RandomPassword( + f"{self._name}_password_hedgedoc_database_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_hedgedoc_database_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_hedgedoc_database_admin + value=password_hedgedoc_database_admin.result ), resource_group_name=resource_group.name, secret_name="password-hedgedoc-database-admin", @@ -305,6 +305,8 @@ def __init__( opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_nexus_admin", properties=keyvault.SecretPropertiesArgs(value=props.password_nexus_admin), @@ -744,7 +746,7 @@ def __init__( password_gitea_database_admin.result ) self.password_hedgedoc_database_admin = Output.secret( - props.password_hedgedoc_database_admin + password_hedgedoc_database_admin.result ) self.password_user_database_admin = Output.secret( props.password_user_database_admin From 803579131fc6e30bf8daf7edff1363c7f9e783cc Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:40:06 +0100 Subject: [PATCH 06/15] :truck: Update 'password-nexus-admin' to use pulumi-random --- data_safe_haven/commands/deploy_sre.py | 1 - data_safe_haven/infrastructure/stacks/sre/data.py | 12 ++++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index a11dca3a21..6f5d54b1ef 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -141,7 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret("password-nexus-admin", password(20), replace=False) stack.add_secret("password-user-database-admin", password(20), replace=False) stack.add_secret("password-workspace-admin", password(20), replace=False) stack.add_secret("salt-dns-server-admin", bcrypt_salt(), replace=False) diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index 5b35d1a0ab..5f0e2a60cb 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -71,7 +71,6 @@ def __init__( self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_nexus_admin = self.get_secret(pulumi_opts, "password-nexus-admin") self.password_user_database_admin = self.get_secret( pulumi_opts, "password-user-database-admin" ) @@ -306,16 +305,21 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Nexus admin password + password_nexus_admin = pulumi_random.RandomPassword( + f"{self._name}_password_nexus_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_nexus_admin", - properties=keyvault.SecretPropertiesArgs(value=props.password_nexus_admin), + properties=keyvault.SecretPropertiesArgs(value=password_nexus_admin.result), resource_group_name=resource_group.name, secret_name="password-nexus-admin", vault_name=key_vault.name, opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_user_database_admin", properties=keyvault.SecretPropertiesArgs( @@ -735,7 +739,7 @@ def __init__( storage_account_data_configuration.name ) self.managed_identity = identity_key_vault_reader - self.password_nexus_admin = Output.secret(props.password_nexus_admin) + self.password_nexus_admin = Output.secret(password_nexus_admin.result) self.password_database_service_admin = Output.secret( password_database_service_admin.result ) From 98f683478918f05d8084f6a3de026bcf224e395f Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:41:37 +0100 Subject: [PATCH 07/15] :truck: Update 'password-user-database-admin' to use pulumi-random --- data_safe_haven/commands/deploy_sre.py | 1 - data_safe_haven/infrastructure/stacks/sre/data.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index 6f5d54b1ef..c6b2d60803 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -141,7 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret("password-user-database-admin", password(20), replace=False) stack.add_secret("password-workspace-admin", password(20), replace=False) stack.add_secret("salt-dns-server-admin", bcrypt_salt(), replace=False) stack.add_secret("token-azuread-graphapi", graph_api.token, replace=True) diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index 5f0e2a60cb..7f0c8e6f3a 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -71,9 +71,6 @@ def __init__( self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_user_database_admin = self.get_secret( - pulumi_opts, "password-user-database-admin" - ) self.password_workspace_admin = self.get_secret( pulumi_opts, "password-workspace-admin" ) @@ -319,11 +316,14 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Guacamole user database admin password + password_user_database_admin = pulumi_random.RandomPassword( + f"{self._name}_password_user_database_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_user_database_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_user_database_admin + value=password_user_database_admin.result ), resource_group_name=resource_group.name, secret_name="password-user-database-admin", @@ -331,6 +331,8 @@ def __init__( opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_workspace_admin", properties=keyvault.SecretPropertiesArgs( @@ -753,7 +755,7 @@ def __init__( password_hedgedoc_database_admin.result ) self.password_user_database_admin = Output.secret( - props.password_user_database_admin + password_user_database_admin.result ) self.password_workspace_admin = Output.secret(props.password_workspace_admin) self.resource_group_name = resource_group.name From 76253289b77f5414220274d725426fb7f8859a79 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 10:43:32 +0100 Subject: [PATCH 08/15] :truck: Update 'password-workspace-admin' to use pulumi-random --- data_safe_haven/commands/deploy_sre.py | 3 +-- data_safe_haven/infrastructure/stacks/sre/data.py | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index c6b2d60803..554940c688 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -4,7 +4,7 @@ DataSafeHavenError, ) from data_safe_haven.external import GraphApi -from data_safe_haven.functions import alphanumeric, bcrypt_salt, password +from data_safe_haven.functions import alphanumeric, bcrypt_salt from data_safe_haven.infrastructure import SHMStackManager, SREStackManager from data_safe_haven.provisioning import SREProvisioningManager from data_safe_haven.utility import DatabaseSystem, SoftwarePackageCategory @@ -141,7 +141,6 @@ def deploy_sre( ) # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) - stack.add_secret("password-workspace-admin", password(20), replace=False) stack.add_secret("salt-dns-server-admin", bcrypt_salt(), replace=False) stack.add_secret("token-azuread-graphapi", graph_api.token, replace=True) diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index 7f0c8e6f3a..c5f6294c89 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -71,9 +71,6 @@ def __init__( self.networking_resource_group_name = Output.from_input( networking_resource_group ).apply(get_name_from_rg) - self.password_workspace_admin = self.get_secret( - pulumi_opts, "password-workspace-admin" - ) self.private_dns_zone_base_id = self.get_secret( pulumi_opts, "shm-networking-private_dns_zone_base_id" ) @@ -332,11 +329,14 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Workspace admin password + password_workspace_admin = pulumi_random.RandomPassword( + f"{self._name}_password_workspace_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_workspace_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_workspace_admin + value=password_workspace_admin.result ), resource_group_name=resource_group.name, secret_name="password-workspace-admin", @@ -757,5 +757,5 @@ def __init__( self.password_user_database_admin = Output.secret( password_user_database_admin.result ) - self.password_workspace_admin = Output.secret(props.password_workspace_admin) + self.password_workspace_admin = Output.secret(password_workspace_admin.result) self.resource_group_name = resource_group.name From ce7d88226d11249258c1596199eb9094198da6d9 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 13:50:18 +0100 Subject: [PATCH 09/15] :truck: Update provisioning to read 'password-user-databse-admin' from the Key Vault rather than passing a Pulumi secret --- .../infrastructure/stacks/declarative_sre.py | 1 + data_safe_haven/infrastructure/stacks/sre/data.py | 8 +++++++- .../provisioning/sre_provisioning_manager.py | 14 +++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/data_safe_haven/infrastructure/stacks/declarative_sre.py b/data_safe_haven/infrastructure/stacks/declarative_sre.py index e47bf8c6c4..cf5534cb26 100644 --- a/data_safe_haven/infrastructure/stacks/declarative_sre.py +++ b/data_safe_haven/infrastructure/stacks/declarative_sre.py @@ -313,6 +313,7 @@ def run(self) -> None: ) # Export values for later use + pulumi.export("data", data.exports) pulumi.export( "ldap", { diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index c5f6294c89..6c7326342b 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -317,7 +317,7 @@ def __init__( password_user_database_admin = pulumi_random.RandomPassword( f"{self._name}_password_user_database_admin", length=20, special=True ) - keyvault.Secret( + kvs_password_user_database_admin = keyvault.Secret( f"{self._name}_kvs_password_user_database_admin", properties=keyvault.SecretPropertiesArgs( value=password_user_database_admin.result @@ -759,3 +759,9 @@ def __init__( ) self.password_workspace_admin = Output.secret(password_workspace_admin.result) self.resource_group_name = resource_group.name + + # Register exports + self.exports = { + "key_vault_name": key_vault.name, + "password_user_database_admin_secret": kvs_password_user_database_admin.name, + } diff --git a/data_safe_haven/provisioning/sre_provisioning_manager.py b/data_safe_haven/provisioning/sre_provisioning_manager.py index 16e99ad71d..e79dcae35a 100644 --- a/data_safe_haven/provisioning/sre_provisioning_manager.py +++ b/data_safe_haven/provisioning/sre_provisioning_manager.py @@ -29,11 +29,19 @@ def __init__( self.sre_name = sre_name self.subscription_name = subscription_name + # Read secrets from key vault + keyvault_name = sre_stack.output("data")["key_vault_name"] + secret_name = sre_stack.output("data")["password_user_database_admin_secret"] + azure_api = AzureApi(self.subscription_name) + connection_db_server_password = azure_api.get_keyvault_secret( + keyvault_name, secret_name + ) + # Construct remote desktop parameters self.remote_desktop_params = sre_stack.output("remote_desktop") - self.remote_desktop_params["connection_db_server_password"] = sre_stack.secret( - "password-user-database-admin" - ) + self.remote_desktop_params[ + "connection_db_server_password" + ] = connection_db_server_password self.remote_desktop_params["timezone"] = timezone # Construct security group parameters From 334b66dd61c435a5722a07d770065f4d5d50c411 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Mon, 9 Oct 2023 14:29:13 +0100 Subject: [PATCH 10/15] :truck: Move GraphAPI token to be requested on a per-user basis rather than being a shared secret --- data_safe_haven/commands/deploy_sre.py | 3 +-- data_safe_haven/commands/teardown_sre.py | 10 +++++++++- data_safe_haven/infrastructure/stack_manager.py | 7 ++++++- .../infrastructure/stacks/declarative_sre.py | 7 +++++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index 554940c688..7f95227e17 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -49,7 +49,7 @@ def deploy_sre( # Initialise Pulumi stack shm_stack = SHMStackManager(config) - stack = SREStackManager(config, sre_name) + stack = SREStackManager(config, sre_name, graph_api_token=graph_api.token) # Set Azure options stack.add_option("azure-native:location", config.azure.location, replace=False) stack.add_option( @@ -142,7 +142,6 @@ def deploy_sre( # Add necessary secrets stack.copy_secret("password-domain-ldap-searcher", shm_stack) stack.add_secret("salt-dns-server-admin", bcrypt_salt(), replace=False) - stack.add_secret("token-azuread-graphapi", graph_api.token, replace=True) # Deploy Azure infrastructure with Pulumi if force is None: diff --git a/data_safe_haven/commands/teardown_sre.py b/data_safe_haven/commands/teardown_sre.py index 32c97d6cb2..6c51ca856a 100644 --- a/data_safe_haven/commands/teardown_sre.py +++ b/data_safe_haven/commands/teardown_sre.py @@ -4,6 +4,7 @@ DataSafeHavenError, DataSafeHavenInputError, ) +from data_safe_haven.external import GraphApi from data_safe_haven.functions import alphanumeric from data_safe_haven.infrastructure import SREStackManager @@ -18,9 +19,16 @@ def teardown_sre(name: str) -> None: # Load config file config = Config() + # Load GraphAPI as this may require user-interaction that is not possible as + # part of a Pulumi declarative command + graph_api = GraphApi( + tenant_id=config.shm.aad_tenant_id, + default_scopes=["Application.ReadWrite.All", "Group.ReadWrite.All"], + ) + # Remove infrastructure deployed with Pulumi try: - stack = SREStackManager(config, sre_name) + stack = SREStackManager(config, sre_name, graph_api_token=graph_api.token) if stack.work_dir.exists(): stack.teardown() else: diff --git a/data_safe_haven/infrastructure/stack_manager.py b/data_safe_haven/infrastructure/stack_manager.py index 084a927964..d86b994b4f 100644 --- a/data_safe_haven/infrastructure/stack_manager.py +++ b/data_safe_haven/infrastructure/stack_manager.py @@ -401,6 +401,11 @@ def __init__( self, config: Config, sre_name: str, + *, + graph_api_token: str | None = None, ) -> None: """Constructor""" - super().__init__(config, DeclarativeSRE(config, config.shm.name, sre_name)) + token = graph_api_token if graph_api_token else "" + super().__init__( + config, DeclarativeSRE(config, config.shm.name, sre_name, token) + ) diff --git a/data_safe_haven/infrastructure/stacks/declarative_sre.py b/data_safe_haven/infrastructure/stacks/declarative_sre.py index cf5534cb26..245339bf29 100644 --- a/data_safe_haven/infrastructure/stacks/declarative_sre.py +++ b/data_safe_haven/infrastructure/stacks/declarative_sre.py @@ -45,8 +45,11 @@ class DeclarativeSRE: """Deploy with Pulumi""" - def __init__(self, config: Config, shm_name: str, sre_name: str) -> None: + def __init__( + self, config: Config, shm_name: str, sre_name: str, graph_api_token: str + ) -> None: self.cfg = config + self.graph_api_token = graph_api_token self.shm_name = shm_name self.sre_name = sre_name self.short_name = f"sre-{sre_name}" @@ -198,7 +201,7 @@ def run(self) -> None: SRERemoteDesktopProps( aad_application_name=f"sre-{self.sre_name}-azuread-guacamole", aad_application_fqdn=networking.sre_fqdn, - aad_auth_token=self.pulumi_opts.require("token-azuread-graphapi"), + aad_auth_token=self.graph_api_token, aad_tenant_id=self.cfg.shm.aad_tenant_id, allow_copy=self.cfg.sres[self.sre_name].remote_desktop.allow_copy, allow_paste=self.cfg.sres[self.sre_name].remote_desktop.allow_paste, From b918603fdfe3a5a1cecbcdca02fe44cccfb2b2c7 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Tue, 10 Oct 2023 09:01:56 +0100 Subject: [PATCH 11/15] :truck: Update 'password-domain-admin' to use pulumi-random --- data_safe_haven/commands/deploy_shm.py | 1 - data_safe_haven/infrastructure/stacks/shm/data.py | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/data_safe_haven/commands/deploy_shm.py b/data_safe_haven/commands/deploy_shm.py index 808a95ffb1..36a5dbfdf7 100644 --- a/data_safe_haven/commands/deploy_shm.py +++ b/data_safe_haven/commands/deploy_shm.py @@ -50,7 +50,6 @@ def deploy_shm( ) stack.add_option("azure-native:tenantId", config.azure.tenant_id, replace=False) # Add necessary secrets - stack.add_secret("password-domain-admin", password(20), replace=False) stack.add_secret( "password-domain-azure-ad-connect", password(20), replace=False ) diff --git a/data_safe_haven/infrastructure/stacks/shm/data.py b/data_safe_haven/infrastructure/stacks/shm/data.py index 36a70e5d16..92d3874833 100644 --- a/data_safe_haven/infrastructure/stacks/shm/data.py +++ b/data_safe_haven/infrastructure/stacks/shm/data.py @@ -1,6 +1,7 @@ """Pulumi component for SHM state""" from collections.abc import Mapping, Sequence +import pulumi_random from pulumi import ComponentResource, Config, Input, Output, ResourceOptions from pulumi_azure_native import keyvault, resources, storage @@ -22,9 +23,6 @@ def __init__( self.admin_group_id = admin_group_id self.admin_ip_addresses = admin_ip_addresses self.location = location - self.password_domain_admin = self.get_secret( - pulumi_opts, "password-domain-admin" - ) self.password_domain_azure_ad_connect = self.get_secret( pulumi_opts, "password-domain-azure-ad-connect" ) @@ -138,16 +136,21 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Domain admin password + password_domain_admin = pulumi_random.RandomPassword( + f"{self._name}_password_domain_admin", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_domain_admin", - properties=keyvault.SecretPropertiesArgs(value=props.password_domain_admin), + properties=keyvault.SecretPropertiesArgs(value=password_domain_admin.result), resource_group_name=resource_group.name, secret_name="password-domain-admin", vault_name=key_vault.name, opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_domain_azure_ad_connect", properties=keyvault.SecretPropertiesArgs( @@ -249,7 +252,7 @@ def __init__( ) # Register outputs - self.password_domain_admin = props.password_domain_admin + self.password_domain_admin = Output.secret(password_domain_admin.result) self.password_domain_azure_ad_connect = props.password_domain_azure_ad_connect self.password_domain_computer_manager = props.password_domain_computer_manager self.password_domain_searcher = props.password_domain_searcher From 71b823e73bdf66b4dd39f184b4d0ea3ed68f89ce Mon Sep 17 00:00:00 2001 From: James Robinson Date: Tue, 10 Oct 2023 09:03:58 +0100 Subject: [PATCH 12/15] :truck: Update 'password-domain-azure-ad-connect' to use pulumi-random --- data_safe_haven/commands/deploy_shm.py | 3 --- .../infrastructure/stacks/shm/data.py | 20 ++++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/data_safe_haven/commands/deploy_shm.py b/data_safe_haven/commands/deploy_shm.py index 36a5dbfdf7..29541341eb 100644 --- a/data_safe_haven/commands/deploy_shm.py +++ b/data_safe_haven/commands/deploy_shm.py @@ -50,9 +50,6 @@ def deploy_shm( ) stack.add_option("azure-native:tenantId", config.azure.tenant_id, replace=False) # Add necessary secrets - stack.add_secret( - "password-domain-azure-ad-connect", password(20), replace=False - ) stack.add_secret( "password-domain-computer-manager", password(20), replace=False ) diff --git a/data_safe_haven/infrastructure/stacks/shm/data.py b/data_safe_haven/infrastructure/stacks/shm/data.py index 92d3874833..f67a835765 100644 --- a/data_safe_haven/infrastructure/stacks/shm/data.py +++ b/data_safe_haven/infrastructure/stacks/shm/data.py @@ -23,9 +23,6 @@ def __init__( self.admin_group_id = admin_group_id self.admin_ip_addresses = admin_ip_addresses self.location = location - self.password_domain_azure_ad_connect = self.get_secret( - pulumi_opts, "password-domain-azure-ad-connect" - ) self.password_domain_computer_manager = self.get_secret( pulumi_opts, "password-domain-computer-manager" ) @@ -142,7 +139,9 @@ def __init__( ) keyvault.Secret( f"{self._name}_kvs_password_domain_admin", - properties=keyvault.SecretPropertiesArgs(value=password_domain_admin.result), + properties=keyvault.SecretPropertiesArgs( + value=password_domain_admin.result + ), resource_group_name=resource_group.name, secret_name="password-domain-admin", vault_name=key_vault.name, @@ -150,11 +149,14 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Azure ADConnect password + password_domain_azure_ad_connect = pulumi_random.RandomPassword( + f"{self._name}_password_domain_azure_ad_connect", length=20, special=True + ) keyvault.Secret( f"{self._name}_kvs_password_domain_azure_ad_connect", properties=keyvault.SecretPropertiesArgs( - value=props.password_domain_azure_ad_connect + value=password_domain_azure_ad_connect.result ), resource_group_name=resource_group.name, secret_name="password-domain-azure-ad-connect", @@ -162,6 +164,8 @@ def __init__( opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Deploy key vault secrets keyvault.Secret( f"{self._name}_kvs_password_domain_computer_manager", properties=keyvault.SecretPropertiesArgs( @@ -253,7 +257,9 @@ def __init__( # Register outputs self.password_domain_admin = Output.secret(password_domain_admin.result) - self.password_domain_azure_ad_connect = props.password_domain_azure_ad_connect + self.password_domain_azure_ad_connect = Output.secret( + password_domain_azure_ad_connect.result + ) self.password_domain_computer_manager = props.password_domain_computer_manager self.password_domain_searcher = props.password_domain_searcher self.password_update_server_linux_admin = ( From 672b8c28143495105ac5e8b4a8204cb5f22c2fea Mon Sep 17 00:00:00 2001 From: James Robinson Date: Tue, 10 Oct 2023 09:07:06 +0100 Subject: [PATCH 13/15] :coffin: Removed unused domain computer manager, as we are not domain-joining any computers --- data_safe_haven/commands/deploy_shm.py | 3 - .../infrastructure/stacks/declarative_shm.py | 1 - .../infrastructure/stacks/shm/data.py | 15 ----- .../stacks/shm/domain_controllers.py | 5 -- .../PrimaryDomainController.ps1 | 67 ------------------- 5 files changed, 91 deletions(-) diff --git a/data_safe_haven/commands/deploy_shm.py b/data_safe_haven/commands/deploy_shm.py index 29541341eb..c3579a81e4 100644 --- a/data_safe_haven/commands/deploy_shm.py +++ b/data_safe_haven/commands/deploy_shm.py @@ -50,9 +50,6 @@ def deploy_shm( ) stack.add_option("azure-native:tenantId", config.azure.tenant_id, replace=False) # Add necessary secrets - stack.add_secret( - "password-domain-computer-manager", password(20), replace=False - ) stack.add_secret("password-domain-ldap-searcher", password(20), replace=False) stack.add_secret( "password-update-server-linux-admin", password(20), replace=False diff --git a/data_safe_haven/infrastructure/stacks/declarative_shm.py b/data_safe_haven/infrastructure/stacks/declarative_shm.py index af274fa951..777f042b27 100644 --- a/data_safe_haven/infrastructure/stacks/declarative_shm.py +++ b/data_safe_haven/infrastructure/stacks/declarative_shm.py @@ -130,7 +130,6 @@ def run(self) -> None: log_analytics_workspace=monitoring.log_analytics_workspace, password_domain_admin=data.password_domain_admin, password_domain_azuread_connect=data.password_domain_azure_ad_connect, - password_domain_computer_manager=data.password_domain_computer_manager, password_domain_searcher=data.password_domain_searcher, private_ip_address=networking.domain_controller_private_ip, subnet_identity_servers=networking.subnet_identity_servers, diff --git a/data_safe_haven/infrastructure/stacks/shm/data.py b/data_safe_haven/infrastructure/stacks/shm/data.py index f67a835765..7f7c5b4862 100644 --- a/data_safe_haven/infrastructure/stacks/shm/data.py +++ b/data_safe_haven/infrastructure/stacks/shm/data.py @@ -23,9 +23,6 @@ def __init__( self.admin_group_id = admin_group_id self.admin_ip_addresses = admin_ip_addresses self.location = location - self.password_domain_computer_manager = self.get_secret( - pulumi_opts, "password-domain-computer-manager" - ) self.password_domain_searcher = self.get_secret( pulumi_opts, "password-domain-ldap-searcher" ) @@ -166,17 +163,6 @@ def __init__( ) # Deploy key vault secrets - keyvault.Secret( - f"{self._name}_kvs_password_domain_computer_manager", - properties=keyvault.SecretPropertiesArgs( - value=props.password_domain_computer_manager - ), - resource_group_name=resource_group.name, - secret_name="password-domain-computer-manager", - vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), - tags=child_tags, - ) keyvault.Secret( f"{self._name}_kvs_password_domain_searcher", properties=keyvault.SecretPropertiesArgs( @@ -260,7 +246,6 @@ def __init__( self.password_domain_azure_ad_connect = Output.secret( password_domain_azure_ad_connect.result ) - self.password_domain_computer_manager = props.password_domain_computer_manager self.password_domain_searcher = props.password_domain_searcher self.password_update_server_linux_admin = ( props.password_update_server_linux_admin diff --git a/data_safe_haven/infrastructure/stacks/shm/domain_controllers.py b/data_safe_haven/infrastructure/stacks/shm/domain_controllers.py index cbeec81bc0..b0f9a81ea3 100644 --- a/data_safe_haven/infrastructure/stacks/shm/domain_controllers.py +++ b/data_safe_haven/infrastructure/stacks/shm/domain_controllers.py @@ -33,7 +33,6 @@ def __init__( log_analytics_workspace: Input[WrappedLogAnalyticsWorkspace], password_domain_admin: Input[str], password_domain_azuread_connect: Input[str], - password_domain_computer_manager: Input[str], password_domain_searcher: Input[str], private_ip_address: Input[str], subnet_identity_servers: Input[network.GetSubnetResult], @@ -55,7 +54,6 @@ def __init__( self.log_analytics_workspace = log_analytics_workspace self.password_domain_admin = password_domain_admin self.password_domain_azuread_connect = password_domain_azuread_connect - self.password_domain_computer_manager = password_domain_computer_manager self.password_domain_searcher = password_domain_searcher self.private_ip_address = private_ip_address self.subnet_name = Output.from_input(subnet_identity_servers).apply( @@ -65,7 +63,6 @@ def __init__( # Note that usernames have a maximum of 20 characters self.username_domain_admin = "dshdomainadmin" self.username_domain_azuread_connect = "dshazureadsync" - self.username_domain_computer_manager = "dshcomputermanager" self.username_domain_searcher = "dshldapsearcher" self.virtual_network_name = virtual_network_name self.virtual_network_resource_group_name = virtual_network_resource_group_name @@ -136,8 +133,6 @@ def __init__( AzureADConnectUsername=props.username_domain_azuread_connect, DomainAdministratorPassword=props.password_domain_admin, DomainAdministratorUsername=props.username_domain_admin, - DomainComputerManagerPassword=props.password_domain_computer_manager, - DomainComputerManagerUsername=props.username_domain_computer_manager, DomainName=props.domain_fqdn, DomainNetBios=props.domain_netbios_name, DomainRootDn=props.domain_root_dn, diff --git a/data_safe_haven/resources/desired_state_configuration/PrimaryDomainController.ps1 b/data_safe_haven/resources/desired_state_configuration/PrimaryDomainController.ps1 index 2690968137..d1d856af16 100644 --- a/data_safe_haven/resources/desired_state_configuration/PrimaryDomainController.ps1 +++ b/data_safe_haven/resources/desired_state_configuration/PrimaryDomainController.ps1 @@ -138,14 +138,6 @@ Configuration ConfigureActiveDirectory { [ValidateNotNullOrEmpty()] [String]$CADDomainAdministratorUsername, - [Parameter(Mandatory = $true, HelpMessage = "Domain computer manager password")] - [ValidateNotNullOrEmpty()] - [String]$CADDomainComputerManagerPassword, - - [Parameter(Mandatory = $true, HelpMessage = "Domain computer manager username")] - [ValidateNotNullOrEmpty()] - [String]$CADDomainComputerManagerUsername, - [Parameter(Mandatory = $true, HelpMessage = "FQDN for the SHM domain")] [ValidateNotNullOrEmpty()] [String]$CADDomainName, @@ -193,11 +185,6 @@ Configuration ConfigureActiveDirectory { Password = $CADAzureADConnectPassword Username = $CADAzureADConnectUsername } - ComputerManager = @{ - Description = "DSH domain computers manager" - Password = $CADDomainComputerManagerPassword - Username = $CADDomainComputerManagerUsername - } LDAPSearcher = @{ Description = "DSH LDAP searcher" Password = $CADLDAPSearcherPassword @@ -344,50 +331,6 @@ Configuration ConfigureActiveDirectory { TestScript = { $false } DependsOn = "[ADUser]AzureADSynchroniser" } - - # Allow the computer manager to register computers in the domain - Script SetComputerManagerPermissions { - SetScript = { - try { - $success = $true - $DomainComputerManagerUsername = $using:DataSafeHavenServiceAccounts.ComputerManager.Username - # $OuDescription = $using:DataSafeHavenUnits.DomainComputers.Description - # $OrganisationalUnit = Get-ADObject -Filter "Name -eq '$OuDescription'" - $OrganisationalUnit = Get-ADObject -Filter "Name -eq '$($using:DataSafeHavenUnits.DomainComputers.Description)'" - $DomainComputerManagerSID = (Get-ADUser -Identity $DomainComputerManagerUsername).SID - # Add permission to create child computer objects - $null = dsacls $OrganisationalUnit /I:T /G "${userPrincipalName}:CC;computer" - $success = $success -and $? - # Give 'write property' permissions over several attributes of child computer objects - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;DNS Host Name Attributes;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;msDS-SupportedEncryptionTypes;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;operatingSystem;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;operatingSystemVersion;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;operatingSystemServicePack;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;sAMAccountName;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;servicePrincipalName;computer" - $success = $success -and $? - $null = dsacls $OrganisationalUnit /I:S /G "${DomainComputerManagerSID}:WP;userPrincipalName;computer" - $success = $success -and $? - if ($success) { - Write-Verbose -Message "Successfully delegated Active Directory permissions on '$OrganisationalUnit' to '$DomainComputerManagerUsername'" - } else { - throw "Failed to delegate Active Directory permissions on '$OrganisationalUnit' to '$DomainComputerManagerUsername'!" - } - } catch { - Write-Error "SetComputerManagerPermissions: $($_.Exception)" - } - } - GetScript = { @{} } - TestScript = { $false } - DependsOn = "[ADUser]ComputerManager" - } } } @@ -444,14 +387,6 @@ Configuration PrimaryDomainController { [ValidateNotNullOrEmpty()] [String]$DomainAdministratorUsername, - [Parameter(Mandatory = $true, HelpMessage = "Domain computer manager password")] - [ValidateNotNullOrEmpty()] - [String]$DomainComputerManagerPassword, - - [Parameter(Mandatory = $true, HelpMessage = "Domain computer manager username")] - [ValidateNotNullOrEmpty()] - [String]$DomainComputerManagerUsername, - [Parameter(Mandatory = $true, HelpMessage = "FQDN for the SHM domain")] [ValidateNotNullOrEmpty()] [String]$DomainName, @@ -510,8 +445,6 @@ Configuration PrimaryDomainController { CADAzureADConnectUsername = $AzureADConnectUsername CADDomainAdministratorPassword = $DomainAdministratorPassword CADDomainAdministratorUsername = $DomainAdministratorUsername - CADDomainComputerManagerPassword = $DomainComputerManagerPassword - CADDomainComputerManagerUsername = $DomainComputerManagerUsername CADDomainName = $DomainName CADDomainRootDn = $DomainRootDn CADLDAPSearcherPassword = $LDAPSearcherPassword From fd15158912344c06cc7a8f561633c51c4eeaafa6 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Tue, 10 Oct 2023 09:09:25 +0100 Subject: [PATCH 14/15] :truck: Update 'password-update-server-linux-admin' to use pulumi-random --- data_safe_haven/commands/deploy_shm.py | 3 -- .../infrastructure/stacks/shm/data.py | 28 ++++++++++--------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/data_safe_haven/commands/deploy_shm.py b/data_safe_haven/commands/deploy_shm.py index c3579a81e4..f23d52afae 100644 --- a/data_safe_haven/commands/deploy_shm.py +++ b/data_safe_haven/commands/deploy_shm.py @@ -51,9 +51,6 @@ def deploy_shm( stack.add_option("azure-native:tenantId", config.azure.tenant_id, replace=False) # Add necessary secrets stack.add_secret("password-domain-ldap-searcher", password(20), replace=False) - stack.add_secret( - "password-update-server-linux-admin", password(20), replace=False - ) stack.add_secret( "verification-azuread-custom-domain", verification_record, replace=False ) diff --git a/data_safe_haven/infrastructure/stacks/shm/data.py b/data_safe_haven/infrastructure/stacks/shm/data.py index 7f7c5b4862..7ad0b7e89f 100644 --- a/data_safe_haven/infrastructure/stacks/shm/data.py +++ b/data_safe_haven/infrastructure/stacks/shm/data.py @@ -26,9 +26,6 @@ def __init__( self.password_domain_searcher = self.get_secret( pulumi_opts, "password-domain-ldap-searcher" ) - self.password_update_server_linux_admin = self.get_secret( - pulumi_opts, "password-update-server-linux-admin" - ) self.tenant_id = tenant_id def get_secret(self, pulumi_opts: Config, secret_name: str) -> Output[str]: @@ -162,25 +159,30 @@ def __init__( tags=child_tags, ) - # Deploy key vault secrets + # Secret: Linux update server admin password + password_update_server_linux_admin = pulumi_random.RandomPassword( + f"{self._name}_password_update_server_linux_admin", length=20, special=True + ) keyvault.Secret( - f"{self._name}_kvs_password_domain_searcher", + f"{self._name}_kvs_password_update_server_linux_admin", properties=keyvault.SecretPropertiesArgs( - value=props.password_domain_searcher + value=password_update_server_linux_admin.result ), resource_group_name=resource_group.name, - secret_name="password-domain-ldap-searcher", + secret_name="password-update-server-linux-admin", vault_name=key_vault.name, opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, ) + + # Add other Pulumi secrets to key vault keyvault.Secret( - f"{self._name}_kvs_password_update_server_linux_admin", + f"{self._name}_kvs_password_domain_searcher", properties=keyvault.SecretPropertiesArgs( - value=props.password_update_server_linux_admin + value=props.password_domain_searcher ), resource_group_name=resource_group.name, - secret_name="password-update-server-linux-admin", + secret_name="password-domain-ldap-searcher", vault_name=key_vault.name, opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), tags=child_tags, @@ -246,9 +248,9 @@ def __init__( self.password_domain_azure_ad_connect = Output.secret( password_domain_azure_ad_connect.result ) - self.password_domain_searcher = props.password_domain_searcher - self.password_update_server_linux_admin = ( - props.password_update_server_linux_admin + self.password_domain_searcher = Output.secret(props.password_domain_searcher) + self.password_update_server_linux_admin = Output.secret( + password_update_server_linux_admin.result ) self.resource_group_name = Output.from_input(resource_group.name) self.vault = key_vault From 1190b3ec3f8b07757f3b527e90962dcfe6c02d77 Mon Sep 17 00:00:00 2001 From: James Robinson Date: Tue, 10 Oct 2023 15:21:11 +0100 Subject: [PATCH 15/15] :truck: Update child relationships of resources --- .../infrastructure/stacks/shm/data.py | 27 +++++++--- .../infrastructure/stacks/sre/data.py | 52 ++++++++++++++----- .../infrastructure/stacks/sre/dns_server.py | 2 +- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/data_safe_haven/infrastructure/stacks/shm/data.py b/data_safe_haven/infrastructure/stacks/shm/data.py index 7ad0b7e89f..2ccc002744 100644 --- a/data_safe_haven/infrastructure/stacks/shm/data.py +++ b/data_safe_haven/infrastructure/stacks/shm/data.py @@ -129,7 +129,10 @@ def __init__( # Secret: Domain admin password password_domain_admin = pulumi_random.RandomPassword( - f"{self._name}_password_domain_admin", length=20, special=True + f"{self._name}_password_domain_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_domain_admin", @@ -139,13 +142,18 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-domain-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_domain_admin) + ), tags=child_tags, ) # Secret: Azure ADConnect password password_domain_azure_ad_connect = pulumi_random.RandomPassword( - f"{self._name}_password_domain_azure_ad_connect", length=20, special=True + f"{self._name}_password_domain_azure_ad_connect", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_domain_azure_ad_connect", @@ -155,13 +163,18 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-domain-azure-ad-connect", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_domain_azure_ad_connect) + ), tags=child_tags, ) # Secret: Linux update server admin password password_update_server_linux_admin = pulumi_random.RandomPassword( - f"{self._name}_password_update_server_linux_admin", length=20, special=True + f"{self._name}_password_update_server_linux_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_update_server_linux_admin", @@ -171,7 +184,9 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-update-server-linux-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_update_server_linux_admin) + ), tags=child_tags, ) diff --git a/data_safe_haven/infrastructure/stacks/sre/data.py b/data_safe_haven/infrastructure/stacks/sre/data.py index 6c7326342b..e1e5832462 100644 --- a/data_safe_haven/infrastructure/stacks/sre/data.py +++ b/data_safe_haven/infrastructure/stacks/sre/data.py @@ -240,7 +240,10 @@ def __init__( # Secret: database service admin password password_database_service_admin = pulumi_random.RandomPassword( - f"{self._name}_password_database_service_admin", length=20, special=True + f"{self._name}_password_database_service_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_database_service_admin", @@ -250,7 +253,9 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-database-service-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_database_service_admin) + ), tags=child_tags, ) @@ -269,7 +274,10 @@ def __init__( # Secret: Gitea database admin password password_gitea_database_admin = pulumi_random.RandomPassword( - f"{self._name}_password_gitea_database_admin", length=20, special=True + f"{self._name}_password_gitea_database_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_gitea_database_admin", @@ -279,13 +287,18 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-gitea-database-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_gitea_database_admin) + ), tags=child_tags, ) # Secret: Hedgedoc database admin password password_hedgedoc_database_admin = pulumi_random.RandomPassword( - f"{self._name}_password_hedgedoc_database_admin", length=20, special=True + f"{self._name}_password_hedgedoc_database_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_hedgedoc_database_admin", @@ -295,13 +308,18 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-hedgedoc-database-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_hedgedoc_database_admin) + ), tags=child_tags, ) # Secret: Nexus admin password password_nexus_admin = pulumi_random.RandomPassword( - f"{self._name}_password_nexus_admin", length=20, special=True + f"{self._name}_password_nexus_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_nexus_admin", @@ -309,13 +327,18 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-nexus-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_nexus_admin) + ), tags=child_tags, ) # Secret: Guacamole user database admin password password_user_database_admin = pulumi_random.RandomPassword( - f"{self._name}_password_user_database_admin", length=20, special=True + f"{self._name}_password_user_database_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) kvs_password_user_database_admin = keyvault.Secret( f"{self._name}_kvs_password_user_database_admin", @@ -325,13 +348,18 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-user-database-admin", vault_name=key_vault.name, - opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), + opts=ResourceOptions.merge( + child_opts, ResourceOptions(parent=password_user_database_admin) + ), tags=child_tags, ) # Secret: Workspace admin password password_workspace_admin = pulumi_random.RandomPassword( - f"{self._name}_password_workspace_admin", length=20, special=True + f"{self._name}_password_workspace_admin", + length=20, + special=True, + opts=ResourceOptions.merge(child_opts, ResourceOptions(parent=key_vault)), ) keyvault.Secret( f"{self._name}_kvs_password_workspace_admin", @@ -341,7 +369,7 @@ def __init__( resource_group_name=resource_group.name, secret_name="password-workspace-admin", vault_name=key_vault.name, - opts=ResourceOptions(parent=key_vault), + opts=ResourceOptions(parent=password_workspace_admin), tags=child_tags, ) diff --git a/data_safe_haven/infrastructure/stacks/sre/dns_server.py b/data_safe_haven/infrastructure/stacks/sre/dns_server.py index 133c8f5ce2..4d904be94d 100644 --- a/data_safe_haven/infrastructure/stacks/sre/dns_server.py +++ b/data_safe_haven/infrastructure/stacks/sre/dns_server.py @@ -68,7 +68,7 @@ def __init__( # Generate admin password password_admin = pulumi_random.RandomPassword( - f"{self._name}_password_admin", length=20, special=True + f"{self._name}_password_admin", length=20, special=True, opts=child_opts ) # Read AdGuardHome setup files