diff --git a/stig/tools/examples/scale-deployment-data-1.psd1 b/stig/tools/examples/scale-deployment-data-1.psd1 index 3a77f25..b1cc762 100644 --- a/stig/tools/examples/scale-deployment-data-1.psd1 +++ b/stig/tools/examples/scale-deployment-data-1.psd1 @@ -4,17 +4,17 @@ @( @{ # Windows Deployment Example w/AvailabilitySet - ResourceGroupName = 'deploymentRG' # Resource Group name + ResourceGroupName = 'atoTesting' # Resource Group name adminUsername = 'testuser' # Admin user account name for VM - virtualNetworkNewOrExisting = 'Existing' # vNet 'New' or 'Existing' - vmVirtualNetwork = 'stig-vm-vnet' # vNet name, creates new if not does exist + virtualNetworkNewOrExisting = 'new' # vNet 'new' or 'existing' + vmVirtualNetwork = 'ato-vm-vnet' # vNet name, creates new if not does exist subnetName = 'subnet1' # Subnet name within specified vNet diagnosticStorageResourceId = '' # Diagnostic Storage Resource Id (Get-AzStorageAccount -ResourceGroupName -Name ).Id logAnalyticsWorkspaceId = '' # Log Analytic Workspace Id (Get-AzOperationalInsightsWorkspace -ResourceGroupName -Name ).ResourceId osDiskEncryptionSetResourceId = '' # Disk Encryption Set Resource Id (Get-AzDiskEncryptionSet -ResourceGroupName -Name ).Id OsVersion = '2019-Datacenter' # OS Version, i.e.: '2019-Datacenter', '2016-Datacenter', '19h2-ent' VmName = 'win2019' # Virtual Machine Name, this will be the base name used in conjunction with VmNamePrefix, VmNameSuffixDelimiter and VmNameSuffixStartingNumber - VmNamePrefix = 'cl-' # VM Name prefix, i.e.: 'cl-' + VmNamePrefix = 'ato-' # VM Name prefix, i.e.: 'ato-' VmNameSuffixDelimiter = '-' # Delimiter used in conjunction with VmName and Suffix Starting Number, i.e.: '-' VmNameSuffixStartingNumber = 1 # VM Name Suffix Starting number, used to create unique VM Name, i.e.: 1 Count = 1 # Number of unque VMs or VM Availability Sets to deploy, i.e.: 2 @@ -23,30 +23,31 @@ UpdateDomains = 3 # Update domains (valid range 1-5), i.e.: 3 AvailabilityOptions = 'availabilitySet' # if 'availabilitySet' is specified, AvailabilitySet is created. 'default' no AvailabilitySet is created AvailabilitySetNameSuffix = '-as' # AvailabilitySetName Suffix to be used with scaled deployment, i.e.: '-as' + TimeInSecondsBetweenJobs = 30 # Specify, in seconds, how long to wait before executing the next deployment. This is useful when creating a new vNet with the first deployment, min/default value is 10 }, @{ # Windows Deployment Example w/o AvailabilitySet - ResourceGroupName = 'deploymentRG' # Resource Group name + ResourceGroupName = 'atoTesting' # Resource Group name adminUsername = 'testuser' # Admin user account name for VM - virtualNetworkNewOrExisting = 'Existing' # vNet 'New' or 'Existing' - vmVirtualNetwork = 'stig-vm-vnet' # vNet name, creates new if not does exist + virtualNetworkNewOrExisting = 'existing' # vNet 'new' or 'existing' + vmVirtualNetwork = 'ato-vm-vnet' # vNet name, creates new if not does exist subnetName = 'subnet1' # Subnet name within specified vNet diagnosticStorageResourceId = '' # Diagnostic Storage Resource Id (Get-AzStorageAccount -ResourceGroupName -Name ).Id logAnalyticsWorkspaceId = '' # Log Analytic Workspace Id (Get-AzOperationalInsightsWorkspace -ResourceGroupName -Name ).ResourceId osDiskEncryptionSetResourceId = '' # Disk Encryption Set Resource Id (Get-AzDiskEncryptionSet -ResourceGroupName -Name ).Id OsVersion = '2016-Datacenter' # OS Version, i.e.: '2019-Datacenter', '2016-Datacenter', '19h2-ent' VmName = 'win2016' # Virtual Machine Name, this will be the base name used in conjunction with VmNamePrefix, VmNameSuffixDelimiter and VmNameSuffixStartingNumber - VmNamePrefix = 'cl-' # VM Name prefix, i.e.: 'cl-' + VmNamePrefix = 'ato-' # VM Name prefix, i.e.: 'ato-' VmNameSuffixDelimiter = '-' # Delimiter used in conjunction with VmName and Suffix Starting Number, i.e.: '-' VmNameSuffixStartingNumber = 1 # VM Name Suffix Starting number, used to create unique VM Name, i.e.: 1 Count = 1 # Number of unque VMs or VM Availability Sets to deploy, i.e.: 2 }, @{ # Linux Deployment Example w/AvailabilitySet - ResourceGroupName = 'deploymentRG' # Resource Group name + ResourceGroupName = 'atoTesting' # Resource Group name adminUsername = 'testuser' # Admin user account name for VM - virtualNetworkNewOrExisting = 'Existing' # vNet 'New' or 'Existing' - vmVirtualNetwork = 'stig-vm-vnet' # vNet name, creates new if not does exist + virtualNetworkNewOrExisting = 'existing' # vNet 'new' or 'existing' + vmVirtualNetwork = 'ato-vm-vnet' # vNet name, creates new if not does exist subnetName = 'subnet1' # Subnet name within specified vNet authenticationType = 'password' # Type of authentication to use on the Virtual Machine (valid values 'sshPublicKey' and 'password') diagnosticStorageResourceId = '' # Diagnostic Storage Resource Id (Get-AzStorageAccount -ResourceGroupName -Name ).Id @@ -54,7 +55,7 @@ osDiskEncryptionSetResourceId = '' # Disk Encryption Set Resource Id (Get-AzDiskEncryptionSet -ResourceGroupName -Name ).Id OsVersion = 'RHEL79' # OS Verison, i.e.: 'CentOS79', 'RHEL79', 'Ubuntu1804' VmName = 'redhat' # Virtual Machine Name, this will be the base name used in conjunction with VmNamePrefix, VmNameSuffixDelimiter and VmNameSuffixStartingNumber - VmNamePrefix = 'cl-' # VM Name prefix, i.e.: 'cl-' + VmNamePrefix = 'ato-' # VM Name prefix, i.e.: 'ato-' VmNameSuffixDelimiter = '-' # Delimiter used in conjunction with VmName and Suffix Starting Number, i.e.: '-' VmNameSuffixStartingNumber = 1 # VM Name Suffix Starting number, used to create unique VM Name, i.e.: 1 Count = 1 # Number of unque VMs or VM Availability Sets to deploy, i.e.: 2 @@ -66,10 +67,10 @@ }, @{ # Linux Deployment Example w/o AvailabilitySet - ResourceGroupName = 'deploymentRG' # Resource Group name + ResourceGroupName = 'atoTesting' # Resource Group name adminUsername = 'testuser' # Admin user account name for VM - virtualNetworkNewOrExisting = 'Existing' # vNet 'New' or 'Existing' - vmVirtualNetwork = 'stig-vm-vnet' # vNet name, creates new if not does exist + virtualNetworkNewOrExisting = 'existing' # vNet 'new' or 'existing' + vmVirtualNetwork = 'ato-vm-vnet' # vNet name, creates new if not does exist subnetName = 'subnet1' # Subnet name within specified vNet authenticationType = 'password' # Type of authentication to use on the Virtual Machine (valid values 'sshPublicKey' and 'password') diagnosticStorageResourceId = '' # Diagnostic Storage Resource Id (Get-AzStorageAccount -ResourceGroupName -Name ).Id @@ -77,7 +78,7 @@ osDiskEncryptionSetResourceId = '' # Disk Encryption Set Resource Id (Get-AzDiskEncryptionSet -ResourceGroupName -Name ).Id OsVersion = 'CentOS79' # OS Verison, i.e.: 'CentOS79', 'RHEL79', 'Ubuntu1804' VmName = 'centos' # Virtual Machine Name, this will be the base name used in conjunction with VmNamePrefix, VmNameSuffixDelimiter and VmNameSuffixStartingNumber - VmNamePrefix = 'cl-' # VM Name prefix, i.e.: 'cl-' + VmNamePrefix = 'ato-' # VM Name prefix, i.e.: 'ato-' VmNameSuffixDelimiter = '-' # Delimiter used in conjunction with VmName and Suffix Starting Number, i.e.: '-' VmNameSuffixStartingNumber = 1 # VM Name Suffix Starting number, used to create unique VM Name, i.e.: 1 Count = 1 # Number of unque VMs or VM Availability Sets to deploy, i.e.: 2 diff --git a/stig/tools/kick-start-scaled-deployment.ps1 b/stig/tools/kick-start-scaled-deployment.ps1 index 27003d4..70bd8b7 100644 --- a/stig/tools/kick-start-scaled-deployment.ps1 +++ b/stig/tools/kick-start-scaled-deployment.ps1 @@ -1,3 +1,4 @@ +#Requires -Module @{ ModuleName = 'Az.Resources'; ModuleVersion = '3.5.0' } <# .SYNOPSIS Kick start script that copies artifact data to a storeage account, then deploys Virtual Machines based on the specified data file. @@ -57,24 +58,61 @@ param [Parameter(Mandatory = $true)] [ValidateScript({Test-Path -Path $_})] [string] - $DataFilePath + $DataFilePath, + + [Parameter(Mandatory = $false)] + [SecureString] + $AdminPasswordOrKey ) -# prompt for AdminPassword, twice, confirm they are equal before proceeding -$passTryCount = 0 -do +# if AdminPasswordOrKey is not passed at runtime, prompt the user for password +if ($PSBoundParameters.ContainsKey('AdminPasswordOrKey') -eq $false) { - $passwordEntry = Read-Host -Prompt 'Deployment Admin Password' -AsSecureString - $passConfEntry = Read-Host -Prompt 'Confirm Deployment Admin Password' -AsSecureString - $passwordEntryDecrypt = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordEntry)) - $passConfEntryDecrypt = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($passConfEntry)) - if ($passTryCount -gt 1 -and $passwordEntryDecrypt -ne $passConfEntryDecrypt) + # explain the password requirements to the user + Write-Host "Deployment Admin Password must be at least 12 characters long, contain upper case, lower case, number and symbol." -ForegroundColor Magenta + + # prompt for AdminPassword, check for complexity, confirm they are equal before proceeding + $passTryCount = 0 + do { - throw "Deployment Admin Password confirmation does not match, Check the password and try again." + if ($passTryCount -gt 3) + { + throw "The password validation checks failed, check the password and try again." + } + + $passTryCount++ + + # ensure clean decrypted vars through each iteration + $passwordEntryDecrypt = $null + $passConfEntryDecrypt = $null + + $passwordEntry = Read-Host -Prompt 'Initial --> Deployment Admin Password' -AsSecureString + $passwordIntPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordEntry) + $passwordEntryDecrypt = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($passwordIntPtr) + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passwordIntPtr) + + # test decrypted password to ensure complexity + $passwordComplexityMatchPattern = '^(?=.*[A-Z])(?=.*[.!@#$%^&*()-_=+])(?=.*[0-9])(?=.*[a-z]).{12,40}$' + if ($passwordEntryDecrypt -notmatch $passwordComplexityMatchPattern) + { + Write-Warning "The password does not meet complexity requirements stated above, try again..." + continue + } + + # prompt again to ensure passwords match + $passConfEntry = Read-Host -Prompt 'Confirm --> Deployment Admin Password' -AsSecureString + $passConfIntPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($passConfEntry) + $passConfEntryDecrypt = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($passConfIntPtr) + [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($passConfIntPtr) + if ($passwordEntryDecrypt -ne $passConfEntryDecrypt) + { + Write-Warning "The password confirmation does not match, check the password and try again..." + } } - $passTryCount++ + until ($passwordEntryDecrypt -eq $passConfEntryDecrypt) + + $adminPasswordOrKey = $passwordEntry } -until ($passwordEntryDecrypt -eq $passConfEntryDecrypt) # connect to AzAccount if ($null -eq $(Get-AzContext)) @@ -92,8 +130,9 @@ if ($null -eq $(Get-AzContext)) # call the publish-to-blob.ps1 script to copy deployment artifacts to the specified ResourceGroup and StorageAccount $publishToBlobScript = Join-Path -Path $PSScriptRoot -ChildPath '.\publish-to-blob.ps1' [void] $PSBoundParameters.Remove('DataFilePath') +[void] $PSBoundParameters.Remove('AdminPasswordOrKey') $artifactLocationParams = & $publishToBlobScript @PSBoundParameters -MetadataPassthru # call the scale-deployment.ps1 script to deploy all datafile VM resources using the artificats previously copied via the publish-to-blob.ps1 script $scaleDeployment = Join-Path -Path $PSScriptRoot -ChildPath '.\scale-deployment.ps1' -& $scaleDeployment @artifactLocationParams -DataFilePath $DataFilePath -AdminPasswordOrKey $passwordEntry +& $scaleDeployment @artifactLocationParams -DataFilePath $DataFilePath -AdminPasswordOrKey $AdminPasswordOrKey diff --git a/stig/tools/scale-deployment.ps1 b/stig/tools/scale-deployment.ps1 index d6ee2ef..31108f6 100644 --- a/stig/tools/scale-deployment.ps1 +++ b/stig/tools/scale-deployment.ps1 @@ -1,3 +1,4 @@ +#Requires -Module @{ ModuleName = 'Az.Resources'; ModuleVersion = '3.5.0' } <# .SYNOPSIS This script enables scaled STIG VM deployment using ATO Tool Kit artifacts. @@ -207,6 +208,11 @@ Param $AdminUserName, [Parameter(Mandatory = $false, ParameterSetName = 'Default')] + [ValidateSet( + "new", + "existing", + IgnoreCase = $false + )] [string] $VirtualNetworkNewOrExisting, @@ -441,7 +447,8 @@ if ($PSCmdlet.ParameterSetName -eq 'Default') } $jobInfo = New-AzResourceGroupDeployment @newAzResourceGroupDeploymentParams - Write-Verbose -Message "JobId: $($jobInfo.Id); Name: $($jobInfo.Name); For more details use: Get-Job -Id $($jobInfo.Id)" + $jobVerboseMessage = 'JobId: {0}; Name: {1}; For more details use: Get-Job -Id {0}; SecondsBetweenJobs: {2}' -f $jobInfo.Id, $jobInfo.Name, $TimeInSecondsBetweenJobs + Write-Verbose -Message $jobVerboseMessage # sleep statement is required to provide sufficient time for pid deployment to succeed. Start-Sleep -Seconds $TimeInSecondsBetweenJobs } @@ -449,8 +456,10 @@ if ($PSCmdlet.ParameterSetName -eq 'Default') else { # import PowerShell data file, structure should mimic .psd1 documented here: + Write-Verbose -Message "Importing deployment data file: $DataFilePath" $dataFileStructureLink = '_placeHolder_for_ato_markdown_link_' $deploymentDataFileImport = Import-PowerShellDataFile -Path $DataFilePath + Write-Verbose -Message "-- Total deployments to be created: $($($deploymentDataFileImport[$deploymentDataFileImport.Keys]).Count)" # if the hashtable keys are more than one, fail, since the structure is incorrect. if ($deploymentDataFileImport.Keys.Count -gt 1)