diff --git a/powershell/Modules/ModPlatformAD/ModPlatformAD.psd1 b/powershell/Modules/ModPlatformAD/ModPlatformAD.psd1 index 548357798..38fb45a68 100644 --- a/powershell/Modules/ModPlatformAD/ModPlatformAD.psd1 +++ b/powershell/Modules/ModPlatformAD/ModPlatformAD.psd1 @@ -3,7 +3,7 @@ # # Generated by: Ministry of Justice # -# Generated on: 12/02/2024 +# Generated on: 05/03/2024 # @{ @@ -12,7 +12,7 @@ # RootModule = '' # Version number of this module. -ModuleVersion = '1.0.0.2' +ModuleVersion = '1.0.0.3' # Supported PSEditions # CompatiblePSEditions = @() @@ -73,7 +73,9 @@ NestedModules = @('ModPlatformADComputer.psm1', # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = 'Rename-ModPlatformADComputer', 'Add-ModPlatformADComputer', 'Remove-ModPlatformADComputer', 'Get-ModPlatformADConfig', - 'Get-ModPlatformADCredential' + 'Get-ModPlatformADSecret', 'Get-ModPlatformADJoinCredential', + 'Get-ModPlatformADAdminCredential', + 'Get-ModPlatformADSafeModeAdministratorPassword' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = '*' diff --git a/powershell/Modules/ModPlatformAD/ModPlatformADComputer.psm1 b/powershell/Modules/ModPlatformAD/ModPlatformADComputer.psm1 index 65630c115..84a3da579 100644 --- a/powershell/Modules/ModPlatformAD/ModPlatformADComputer.psm1 +++ b/powershell/Modules/ModPlatformAD/ModPlatformADComputer.psm1 @@ -73,7 +73,7 @@ function Add-ModPlatformADComputer { HashTable as returned from Get-ModPlatformADConfig function .PARAMETER ModPlatformADCredential - AD credential as returned from Get-ModPlatformADCredential function. + AD credential as returned from Get-ModPlatformADJoinCredential function. .EXAMPLE Add-ModPlatformADComputer $ModPlatformADConfig $ModPlatformADCredential @@ -124,7 +124,7 @@ function Remove-ModPlatformADComputer { Returns true if successful and a reboot required, false if already removed. .PARAMETER ModPlatformADCredential - AD credential as returned from Get-ModPlatformADCredential function. + AD credential as returned from Get-ModPlatformADJoinCredential function. .EXAMPLE Add-ModPlatformADComputer $ModPlatformADConfig $ModPlatformADCredential diff --git a/powershell/Modules/ModPlatformAD/ModPlatformADConfig.psm1 b/powershell/Modules/ModPlatformAD/ModPlatformADConfig.psm1 index 281869836..5802508f9 100644 --- a/powershell/Modules/ModPlatformAD/ModPlatformADConfig.psm1 +++ b/powershell/Modules/ModPlatformAD/ModPlatformADConfig.psm1 @@ -35,6 +35,7 @@ function Get-ModPlatformADConfig { "DomainNameFQDN" = "azure.noms.root" "DomainNameNetbios" = "AZURE" "DomainJoinUsername" = "svc_join_domain" + "DomainAdminUsername" = "svc_admin" } "azure.hmpp.root" = @{ "AccountIdsSSMParameterName" = "account_ids" @@ -44,6 +45,7 @@ function Get-ModPlatformADConfig { "DomainNameFQDN" = "azure.hmpp.root" "DomainNameNetbios" = "HMPP" "DomainJoinUsername" = "svc_join_domain" + "DomainAdminUsername" = "svc_admin" } } diff --git a/powershell/Modules/ModPlatformAD/ModPlatformADCredential.psm1 b/powershell/Modules/ModPlatformAD/ModPlatformADCredential.psm1 index 7d52204c3..5a5b2fd1a 100644 --- a/powershell/Modules/ModPlatformAD/ModPlatformADCredential.psm1 +++ b/powershell/Modules/ModPlatformAD/ModPlatformADCredential.psm1 @@ -1,24 +1,25 @@ -function Get-ModPlatformADCredential { +function Get-ModPlatformADSecret { <# .SYNOPSIS - Retrieves a domain account credential that can be used for AD operations + Retrieves domain account secret from SecretsManager .DESCRIPTION Using configuration returned from Get-ModPlatformADConfig, this function - optionally assumes a role to access a secret containing the password of the - domain join username. EC2 requires permissions to join the given role, + optionally assumes a role to access a SecretsManager secret containing + domain secrets. EC2 requires permissions to join the given role, a SSM parameter containing account IDs, and the aws cli. -.PARAMETER ModPlatformADCredential +.PARAMETER ModPlatformADConfig HashTable as returned from Get-ModPlatformADConfig function .EXAMPLE $ADConfig = Get-ModPlatformADConfig - $ADCredential = Get-ModPlatformADCredential $ADConfig + $ADSecret = Get-ModPlatformADJoinCredential $ADConfig + $Password = ConvertTo-SecureString $ADSecret.svc_join_domain .OUTPUTS - PSCredentialObject + PSCustomObject #> [CmdletBinding()] @@ -58,15 +59,150 @@ function Get-ModPlatformADCredential { } else { $SecretValueRaw = aws secretsmanager get-secret-value --secret-id "${SecretArn}" --query SecretString --output text } - $SecretValue = "$SecretValueRaw" | ConvertFrom-Json + "$SecretValueRaw" | ConvertFrom-Json +} + +function Get-ModPlatformADJoinCredential { + +<# +.SYNOPSIS + Retrieves credential that can be used for joining Computers to the domain + +.DESCRIPTION + Using configuration returned from Get-ModPlatformADConfig, this function + optionally assumes a role to access a secret containing the password of the + domain join username. EC2 requires permissions to join the given role, + a SSM parameter containing account IDs, and the aws cli. + +.PARAMETER ModPlatformADConfig + HashTable as returned from Get-ModPlatformADConfig function + +.PARAMETER ModPlatformADSecret + Optional PSCustomObject containing secrets as returned from Get-ModPlatformADSecret + +.EXAMPLE + $ADConfig = Get-ModPlatformADConfig + $ADCredential = Get-ModPlatformADJoinCredential $ADConfig + +.OUTPUTS + PSCredentialObject +#> + + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)][hashtable]$ModPlatformADConfig, + [PSCustomObject]$ModPlatformADSecret + ) + + $ErrorActionPreference = "Stop" + + if ($ModPlatformADSecret -eq $null) { + $ModPlatformADSecret = Get-ModPlatformADSecret -ModPlatformADConfig $ModPlatformADConfig + } $DomainNameNetbios = $ModPlatformADConfig.DomainNameNetbios $DomainJoinUsername = $ModPlatformADConfig.DomainJoinUsername - $DomainJoinPassword = $SecretValue.$DomainJoinUsername + $DomainJoinPassword = $ModPlatformADSecret.$DomainJoinUsername if (-not $DomainJoinPassword) { - Write-Error "Password secret ${SecretArn} does not contain domain join username ${DomainJoinUsername}" + Write-Error "Password secret does not contain domain join username ${DomainJoinUsername}" } - $DomainJoinPasswordSecureString = ConvertTo-SecureString $SecretValue.$DomainJoinUsername -AsPlainText -Force + $DomainJoinPasswordSecureString = ConvertTo-SecureString $DomainJoinPassword -AsPlainText -Force New-Object System.Management.Automation.PSCredential ("$DomainNameNetbios\$DomainJoinUsername", $DomainJoinPasswordSecureString) } -Export-ModuleMember -Function Get-ModPlatformADCredential +function Get-ModPlatformADAdminCredential { + +<# +.SYNOPSIS + Retrieves credential that can be used for administrating the domain + +.DESCRIPTION + Using configuration returned from Get-ModPlatformADConfig, this function + optionally assumes a role to access a secret containing the password of the + domain join username. EC2 requires permissions to join the given role, + a SSM parameter containing account IDs, and the aws cli. + +.PARAMETER ModPlatformADConfig + HashTable as returned from Get-ModPlatformADConfig function + +.PARAMETER ModPlatformADSecret + Optional PSCustomObject containing secrets as returned from Get-ModPlatformADSecret + +.EXAMPLE + $ADConfig = Get-ModPlatformADConfig + $ADCredential = Get-ModPlatformADAdminCredential $ADConfig + +.OUTPUTS + PSCredentialObject +#> + + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)][hashtable]$ModPlatformADConfig, + [PSCustomObject]$ModPlatformADSecret + ) + + $ErrorActionPreference = "Stop" + + if ($ModPlatformADSecret -eq $null) { + $ModPlatformADSecret = Get-ModPlatformADSecret -ModPlatformADConfig $ModPlatformADConfig + } + $DomainNameNetbios = $ModPlatformADConfig.DomainNameNetbios + $DomainAdminUsername = $ModPlatformADConfig.DomainAdminUsername + $DomainAdminPassword = $ModPlatformADSecret.$DomainAdminUsername + if (-not $DomainAdminPassword) { + Write-Error "Password secret does not contain domain admin username ${DomainAdminUsername}" + } + $DomainAdminPasswordSecureString = ConvertTo-SecureString $DomainAdminPassword -AsPlainText -Force + New-Object System.Management.Automation.PSCredential ("$DomainNameNetbios\$DomainAdminUsername", $DomainAdminPasswordSecureString) +} + +function Get-ModPlatformADSafeModeAdministratorPassword { + +<# +.SYNOPSIS + Retrieves credential that can be used for administrating the domain + +.DESCRIPTION + Using configuration returned from Get-ModPlatformADConfig, this function + optionally assumes a role to access a secret containing the password of the + domain join username. EC2 requires permissions to join the given role, + a SSM parameter containing account IDs, and the aws cli. + +.PARAMETER ModPlatformADConfig + HashTable as returned from Get-ModPlatformADConfig function + +.PARAMETER ModPlatformADSecret + Optional PSCustomObject containing secrets as returned from Get-ModPlatformADSecret + +.EXAMPLE + $ADConfig = Get-ModPlatformADConfig + $ADCredential = Get-ModPlatformADSafeModeAdministratorPassword $ADConfig + +.OUTPUTS + Secure-String +#> + + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)][hashtable]$ModPlatformADConfig, + [PSCustomObject]$ModPlatformADSecret + ) + + $ErrorActionPreference = "Stop" + + if ($ModPlatformADSecret -eq $null) { + $ModPlatformADSecret = Get-ModPlatformADSecret -ModPlatformADConfig $ModPlatformADConfig + } + $DomainNameNetbios = $ModPlatformADConfig.DomainNameNetbios + $DomainAdminUsername = $ModPlatformADConfig.DomainAdminUsername + $SafeModeAdministratorPassword = $ModPlatformADSecret.SafeModeAdministratorPassword + if (-not $SafeModeAdministratorPassword) { + Write-Error "Password secret does not contain domain admin username ${SafeModeAdministratorPassword}" + } + ConvertTo-SecureString $SafeModeAdministratorPassword -AsPlainText -Force +} + +Export-ModuleMember -Function Get-ModPlatformADSecret +Export-ModuleMember -Function Get-ModPlatformADJoinCredential +Export-ModuleMember -Function Get-ModPlatformADAdminCredential +Export-ModuleMember -Function Get-ModPlatformADSafeModeAdministratorPassword diff --git a/powershell/Modules/ModPlatformAD/README.md b/powershell/Modules/ModPlatformAD/README.md index 9233443e8..bc25741f9 100644 --- a/powershell/Modules/ModPlatformAD/README.md +++ b/powershell/Modules/ModPlatformAD/README.md @@ -3,9 +3,9 @@ Provides Active Directory related functions For example, ``` -Get-ModPlatformADConfig - derive AD config from environment -Get-ModPlatformADCredential - lookup domain join secret -Rename-ModPlatformADComputer - rename computer -Add-ModPlatformADComputer - add to domain -Remove-ModPlatformADComputer - remove from domain +Get-ModPlatformADConfig - derive AD config from environment +Get-ModPlatformADJoinCredential - lookup domain join secret +Rename-ModPlatformADComputer - rename computer +Add-ModPlatformADComputer - add to domain +Remove-ModPlatformADComputer - remove from domain ``` diff --git a/powershell/Scripts/ModPlatformAD/Install-ModPlatformADDomainController.ps1 b/powershell/Scripts/ModPlatformAD/Install-ModPlatformADDomainController.ps1 new file mode 100644 index 000000000..8d3e77062 --- /dev/null +++ b/powershell/Scripts/ModPlatformAD/Install-ModPlatformADDomainController.ps1 @@ -0,0 +1,50 @@ +<# +.SYNOPSIS + Install a Domain Controller from scratch + +.DESCRIPTION + By default the script derives the hostname from the Name tag. Or specify NewHostname parameter. + By default derives the AD configuration from EC2 tags (environment-name or domain-name), or specify DomainNameFQDN parameter. + EC2 requires permissions to get tags and the aws cli. + Exits with 3010 if reboot required and script requires re-running. For use in SSM docs + +.PARAMETER DomainNameFQDN + Optionally specify the FQDN of the domain name to join + +.EXAMPLE + Join-ModPlatformAD +#> + +[CmdletBinding()] +param ( + [string]$NewHostname = "tag:Name", + [string]$DomainNameFQDN +) + +Import-Module ModPlatformAD -Force + +$ErrorActionPreference = "Stop" + +$ADConfig = Get-ModPlatformADConfig -DomainNameFQDN $DomainNameFQDN +$ADSecret = Get-ModPlatformADSecret -ModPlatformADConfig $ADConfig +$ADJoinCredential = Get-ModPlatformADJoinCredential -ModPlatformADConfig $ADConfig -ModPlatformADSecret $ADSecret +$Renamed = Rename-ModPlatformADComputer -NewHostname $NewHostname -ModPlatformADCredential $ADJoinCredential +if ($Renamed) { + Write-Output "Renamed computer to ${Renamed}" + Exit 3010 # triggers reboot if running from SSM Doc +} +if (Add-ModPlatformADComputer -ModPlatformADConfig $ADConfig -ModPlatformADCredential $ADJoinCredential) { + Exit 3010 # triggers reboot if running from SSM Doc +} + +$DFSReplicationStatus = Get-Service "DFS Replication" -ErrorAction SilentlyContinue +if ($DFSReplicationStatus -eq $null) { + $ADAdminCredential = Get-ModPlatformADAdminCredential -ModPlatformADConfig $ADConfig -ModPlatformADSecret $ADSecret + $ADSafeModeAdministratorPassword = Get-ModPlatformADSafeModeAdministratorPassword -ModPlatformADConfig $ADConfig -ModPlatformADSecret $ADSecret + Install-WindowsFeature -Name AD-Domain-Services -IncludeAllSubFeature -IncludeManagementTools + Install-ADDSDomainController -DomainName $ADConfig.DomainNameFQDN -InstallDns:$true -Credential $ADAdminCredential -SafeModeAdministratorPassword $ADSafeModeAdministratorPassword -NoRebootOnCompletion -Force + Exit 3010 # triggers reboot if running from SSM Doc +} else { + $Services='DNS','DFS Replication','Intersite Messaging','Kerberos Key Distribution Center','NetLogon',’Active Directory Domain Services’ + ForEach ($Service in $Services) {Get-Service $Service | Select-Object Name, Status} +} diff --git a/powershell/Scripts/ModPlatformAD/Join-ModPlatformAD.ps1 b/powershell/Scripts/ModPlatformAD/Join-ModPlatformAD.ps1 index 66b56eb59..4fb8685a7 100644 --- a/powershell/Scripts/ModPlatformAD/Join-ModPlatformAD.ps1 +++ b/powershell/Scripts/ModPlatformAD/Join-ModPlatformAD.ps1 @@ -26,7 +26,7 @@ Import-Module ModPlatformAD -Force $ErrorActionPreference = "Stop" $ADConfig = Get-ModPlatformADConfig -DomainNameFQDN $DomainNameFQDN -$ADCredential = Get-ModPlatformADCredential -ModPlatformADConfig $ADConfig +$ADCredential = Get-ModPlatformADJoinCredential -ModPlatformADConfig $ADConfig $Renamed = Rename-ModPlatformADComputer -NewHostname $NewHostname -ModPlatformADCredential $ADCredential if ($Renamed) { Write-Output "Renamed computer to ${Renamed}" diff --git a/powershell/Scripts/ModPlatformAD/Leave-ModPlatformAD.ps1 b/powershell/Scripts/ModPlatformAD/Leave-ModPlatformAD.ps1 index fe79980f8..1a1755ab8 100644 --- a/powershell/Scripts/ModPlatformAD/Leave-ModPlatformAD.ps1 +++ b/powershell/Scripts/ModPlatformAD/Leave-ModPlatformAD.ps1 @@ -25,7 +25,7 @@ Import-Module ModPlatformAD -Force $ErrorActionPreference = "Stop" $ADConfig = Get-ModPlatformADConfig -DomainNameFQDN $DomainNameFQDN -$ADCredential = Get-ModPlatformADCredential -ModPlatformADConfig $ADConfig +$ADCredential = Get-ModPlatformADJoinCredential -ModPlatformADConfig $ADConfig if (Remove-ModPlatformADComputer -ModPlatformADCredential $ADCredential) { Exit 3010 # triggers reboot if running from SSM Doc } diff --git a/powershell/Scripts/ModPlatformAD/Uninstall-ModPlatformADDomainController.ps1 b/powershell/Scripts/ModPlatformAD/Uninstall-ModPlatformADDomainController.ps1 new file mode 100644 index 000000000..c14b44826 --- /dev/null +++ b/powershell/Scripts/ModPlatformAD/Uninstall-ModPlatformADDomainController.ps1 @@ -0,0 +1,41 @@ +<# +.SYNOPSIS + Install a Domain Controller from scratch + +.DESCRIPTION + By default the script derives the hostname from the Name tag. Or specify NewHostname parameter. + By default derives the AD configuration from EC2 tags (environment-name or domain-name), or specify DomainNameFQDN parameter. + EC2 requires permissions to get tags and the aws cli. + Exits with 3010 if reboot required and script requires re-running. For use in SSM docs + Example retrieval of local admin password: + aws ssm get-parameter --name ec2-user_pem --with-decryption --query Parameter.Value --output text --profile hmpps-domain-services-test > tmp.key + aws ec2 get-password-data --instance-id i-0aa02abedd9572e19 --profile core-shared-services-production-ad --priv-launch-key tmp.key + rm tmp.key + +.PARAMETER DomainNameFQDN + Optionally specify the FQDN of the domain name to join + +.EXAMPLE + Join-ModPlatformAD +#> + +[CmdletBinding()] +param ( + [string]$NewHostname = "tag:Name", + [string]$DomainNameFQDN +) + +Import-Module ModPlatformAD -Force + +$ErrorActionPreference = "Stop" + +$ADConfig = Get-ModPlatformADConfig -DomainNameFQDN $DomainNameFQDN +$ADSecret = Get-ModPlatformADSecret -ModPlatformADConfig $ADConfig + +$DFSReplicationStatus = Get-Service "DFS Replication" -ErrorAction SilentlyContinue +if ($DFSReplicationStatus -ne $null) { + $ADAdminCredential = Get-ModPlatformADAdminCredential -ModPlatformADConfig $ADConfig -ModPlatformADSecret $ADSecret + $ADSafeModeAdministratorPassword = Get-ModPlatformADSafeModeAdministratorPassword -ModPlatformADConfig $ADConfig -ModPlatformADSecret $ADSecret + Uninstall-ADDSDomainController -Credential $ADAdminCredential -NoRebootOnCompletion -Force + Exit 3010 # triggers reboot if running from SSM Doc +} diff --git a/powershell/Scripts/UserDataScripts/DomainController.ps1 b/powershell/Scripts/UserDataScripts/DomainController.ps1 index b5eea962b..e4ef73cce 100644 --- a/powershell/Scripts/UserDataScripts/DomainController.ps1 +++ b/powershell/Scripts/UserDataScripts/DomainController.ps1 @@ -1,8 +1,4 @@ -$ErrorActionPreference = "Stop" - -. ../ModPlatformAD/Join-ModPlatformAD.ps1 +. ../ModPlatformAD/Install-ModPlatformADDomainController.ps1 if ($LASTEXITCODE -ne 0) { Exit $LASTEXITCODE } - -Install-WindowsFeature AD-Domain-Services -IncludeAllSubFeature -IncludeManagementTools