From 6d36e2a86baf8d227fc6dce587bd69997d67fb5e Mon Sep 17 00:00:00 2001 From: Billy O'Neal Date: Fri, 29 May 2020 22:46:05 -0700 Subject: [PATCH] [vcpkg] Use a crypto RNG to generate admin passwords (#11629) * [vcpkg] Use a crypto RNG to generate admin passwords * Apply code review comments from Stephan and Casey. * Extract functions into create-vmss-helpers.psm1. * Put Wait-Shutdown back. --- .../azure-pipelines/create-vmss-helpers.psm1 | 165 ++++++++++++++++++ scripts/azure-pipelines/linux/create-vmss.ps1 | 106 +---------- .../azure-pipelines/windows/create-vmss.ps1 | 140 +-------------- 3 files changed, 167 insertions(+), 244 deletions(-) create mode 100755 scripts/azure-pipelines/create-vmss-helpers.psm1 diff --git a/scripts/azure-pipelines/create-vmss-helpers.psm1 b/scripts/azure-pipelines/create-vmss-helpers.psm1 new file mode 100755 index 000000000..08057d676 --- /dev/null +++ b/scripts/azure-pipelines/create-vmss-helpers.psm1 @@ -0,0 +1,165 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: MIT + +<# +.SYNOPSIS +Returns whether there's a name collision in the resource group. + +.DESCRIPTION +Find-ResourceGroupNameCollision takes a list of resources, and checks if $Test +collides names with any of the resources. + +.PARAMETER Test +The name to test. + +.PARAMETER Resources +The list of resources. +#> +function Find-ResourceGroupNameCollision { + [CmdletBinding()] + Param([string]$Test, $Resources) + + foreach ($resource in $Resources) { + if ($resource.ResourceGroupName -eq $Test) { + return $true + } + } + + return $false +} + +<# +.SYNOPSIS +Attempts to find a name that does not collide with any resources in the resource group. + +.DESCRIPTION +Find-ResourceGroupName takes a set of resources from Get-AzResourceGroup, and finds the +first name in {$Prefix, $Prefix-1, $Prefix-2, ...} such that the name doesn't collide with +any of the resources in the resource group. + +.PARAMETER Prefix +The prefix of the final name; the returned name will be of the form "$Prefix(-[1-9][0-9]*)?" +#> +function Find-ResourceGroupName { + [CmdletBinding()] + Param([string] $Prefix) + + $resources = Get-AzResourceGroup + $result = $Prefix + $suffix = 0 + while (Find-ResourceGroupNameCollision -Test $result -Resources $resources) { + $suffix++ + $result = "$Prefix-$suffix" + } + + return $result +} + +<# +.SYNOPSIS +Generates a random password. + +.DESCRIPTION +New-Password generates a password, randomly, of length $Length, containing +only alphanumeric characters, underscore, and dash. + +.PARAMETER Length +The length of the returned password. +#> +function New-Password { + Param ([int] $Length = 32) + + # This 64-character alphabet generates 6 bits of entropy per character. + # The power-of-2 alphabet size allows us to select a character by masking a random Byte with bitwise-AND. + $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" + $mask = 63 + if ($alphabet.Length -ne 64) { + throw 'Bad alphabet length' + } + + [Byte[]]$randomData = [Byte[]]::new($Length) + $rng = $null + try { + $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() + $rng.GetBytes($randomData) + } + finally { + if ($null -ne $rng) { + $rng.Dispose() + } + } + + $result = '' + for ($idx = 0; $idx -lt $Length; $idx++) { + $result += $alphabet[$randomData[$idx] -band $mask] + } + + return $result +} + +<# +.SYNOPSIS +Waits for the shutdown of the specified resource. + +.DESCRIPTION +Wait-Shutdown takes a VM, and checks if there's a 'PowerState/stopped' +code; if there is, it returns. If there isn't, it waits ten seconds and +tries again. + +.PARAMETER ResourceGroupName +The name of the resource group to look up the VM in. + +.PARAMETER Name +The name of the virtual machine to wait on. +#> +function Wait-Shutdown { + [CmdletBinding()] + Param([string]$ResourceGroupName, [string]$Name) + + Write-Host "Waiting for $Name to stop..." + while ($true) { + $Vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $Name -Status + $highestStatus = $Vm.Statuses.Count + for ($idx = 0; $idx -lt $highestStatus; $idx++) { + if ($Vm.Statuses[$idx].Code -eq 'PowerState/stopped') { + return + } + } + + Write-Host "... not stopped yet, sleeping for 10 seconds" + Start-Sleep -Seconds 10 + } +} + +<# +.SYNOPSIS +Sanitizes a name to be used in a storage account. + +.DESCRIPTION +Sanitize-Name takes a string, and removes all of the '-'s and +lowercases the string, since storage account names must have no +'-'s and must be completely lowercase alphanumeric. It then makes +certain that the length of the string is not greater than 24, +since that is invalid. + +.PARAMETER RawName +The name to sanitize. +#> +function Sanitize-Name { + [CmdletBinding()] + Param( + [string]$RawName + ) + + $result = $RawName.Replace('-', '').ToLowerInvariant() + if ($result.Length -gt 24) { + Write-Error 'Sanitized name for storage account $result was too long.' + } + + return $result +} + +Export-ModuleMember -Function Find-ResourceGroupName +Export-ModuleMember -Function New-Password +Export-ModuleMember -Function Wait-Shutdown +Export-ModuleMember -Function Sanitize-Name diff --git a/scripts/azure-pipelines/linux/create-vmss.ps1 b/scripts/azure-pipelines/linux/create-vmss.ps1 index ac438254a..de89c2207 100755 --- a/scripts/azure-pipelines/linux/create-vmss.ps1 +++ b/scripts/azure-pipelines/linux/create-vmss.ps1 @@ -27,111 +27,7 @@ $ProgressActivity = 'Creating Scale Set' $TotalProgress = 10 $CurrentProgress = 1 -<# -.SYNOPSIS -Returns whether there's a name collision in the resource group. - -.DESCRIPTION -Find-ResourceGroupNameCollision takes a list of resources, and checks if $Test -collides names with any of the resources. - -.PARAMETER Test -The name to test. - -.PARAMETER Resources -The list of resources. -#> -function Find-ResourceGroupNameCollision { - [CmdletBinding()] - Param([string]$Test, $Resources) - - foreach ($resource in $Resources) { - if ($resource.ResourceGroupName -eq $Test) { - return $true - } - } - - return $false -} - -<# -.SYNOPSIS -Attempts to find a name that does not collide with any resources in the resource group. - -.DESCRIPTION -Find-ResourceGroupName takes a set of resources from Get-AzResourceGroup, and finds the -first name in {$Prefix, $Prefix-1, $Prefix-2, ...} such that the name doesn't collide with -any of the resources in the resource group. - -.PARAMETER Prefix -The prefix of the final name; the returned name will be of the form "$Prefix(-[1-9][0-9]*)?" -#> -function Find-ResourceGroupName { - [CmdletBinding()] - Param([string] $Prefix) - - $resources = Get-AzResourceGroup - $result = $Prefix - $suffix = 0 - while (Find-ResourceGroupNameCollision -Test $result -Resources $resources) { - $suffix++ - $result = "$Prefix-$suffix" - } - - return $result -} - -<# -.SYNOPSIS -Creates a randomly generated password. - -.DESCRIPTION -New-Password generates a password, randomly, of length $Length, containing -only alphanumeric characters (both uppercase and lowercase). - -.PARAMETER Length -The length of the returned password. -#> -function New-Password { - Param ([int] $Length = 32) - - $Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - $result = '' - for ($idx = 0; $idx -lt $Length; $idx++) { - # NOTE: this should probably use RNGCryptoServiceProvider - $result += $Chars[(Get-Random -Minimum 0 -Maximum $Chars.Length)] - } - - return $result -} - -<# -.SYNOPSIS -Sanitizes a name to be used in a storage account. - -.DESCRIPTION -Sanitize-Name takes a string, and removes all of the '-'s and -lowercases the string, since storage account names must have no -'-'s and must be completely lowercase alphanumeric. It then makes -certain that the length of the string is not greater than 24, -since that is invalid. - -.PARAMETER RawName -The name to sanitize. -#> -function Sanitize-Name { - [CmdletBinding()] - Param( - [string]$RawName - ) - - $result = $RawName.Replace('-', '').ToLowerInvariant() - if ($result.Length -gt 24) { - Write-Error 'Sanitized name for storage account $result was too long.' - } - - return $result -} +Import-Module "$PSScriptRoot/../create-vmss-helpers.psm1" -DisableNameChecking #################################################################################################### Write-Progress ` diff --git a/scripts/azure-pipelines/windows/create-vmss.ps1 b/scripts/azure-pipelines/windows/create-vmss.ps1 index 6a3f01fc1..1417c298e 100644 --- a/scripts/azure-pipelines/windows/create-vmss.ps1 +++ b/scripts/azure-pipelines/windows/create-vmss.ps1 @@ -29,145 +29,7 @@ $ProgressActivity = 'Creating Scale Set' $TotalProgress = 12 $CurrentProgress = 1 -<# -.SYNOPSIS -Returns whether there's a name collision in the resource group. - -.DESCRIPTION -Find-ResourceGroupNameCollision takes a list of resources, and checks if $Test -collides names with any of the resources. - -.PARAMETER Test -The name to test. - -.PARAMETER Resources -The list of resources. -#> -function Find-ResourceGroupNameCollision { - [CmdletBinding()] - Param([string]$Test, $Resources) - - foreach ($resource in $Resources) { - if ($resource.ResourceGroupName -eq $Test) { - return $true - } - } - - return $false -} - -<# -.SYNOPSIS -Attempts to find a name that does not collide with any resources in the resource group. - -.DESCRIPTION -Find-ResourceGroupName takes a set of resources from Get-AzResourceGroup, and finds the -first name in {$Prefix, $Prefix-1, $Prefix-2, ...} such that the name doesn't collide with -any of the resources in the resource group. - -.PARAMETER Prefix -The prefix of the final name; the returned name will be of the form "$Prefix(-[1-9][0-9]*)?" -#> -function Find-ResourceGroupName { - [CmdletBinding()] - Param([string] $Prefix) - - $resources = Get-AzResourceGroup - $result = $Prefix - $suffix = 0 - while (Find-ResourceGroupNameCollision -Test $result -Resources $resources) { - $suffix++ - $result = "$Prefix-$suffix" - } - - return $result -} - -<# -.SYNOPSIS -Creates a randomly generated password. - -.DESCRIPTION -New-Password generates a password, randomly, of length $Length, containing -only alphanumeric characters (both uppercase and lowercase). - -.PARAMETER Length -The length of the returned password. -#> -function New-Password { - Param ([int] $Length = 32) - - $Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - $result = '' - for ($idx = 0; $idx -lt $Length; $idx++) { - # NOTE: this should probably use RNGCryptoServiceProvider - $result += $Chars[(Get-Random -Minimum 0 -Maximum $Chars.Length)] - } - - return $result -} - -<# -.SYNOPSIS -Waits for the shutdown of the specified resource. - -.DESCRIPTION -Wait-Shutdown takes a VM, and checks if there's a 'PowerState/stopped' -code; if there is, it returns. If there isn't, it waits ten seconds and -tries again. - -.PARAMETER ResourceGroupName -The name of the resource group to look up the VM in. - -.PARAMETER Name -The name of the virtual machine to wait on. -#> -function Wait-Shutdown { - [CmdletBinding()] - Param([string]$ResourceGroupName, [string]$Name) - - Write-Host "Waiting for $Name to stop..." - while ($true) { - $Vm = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $Name -Status - $highestStatus = $Vm.Statuses.Count - for ($idx = 0; $idx -lt $highestStatus; $idx++) { - if ($Vm.Statuses[$idx].Code -eq 'PowerState/stopped') { - return - } - } - - Write-Host "... not stopped yet, sleeping for 10 seconds" - Start-Sleep -Seconds 10 - } -} - -<# -.SYNOPSIS -Sanitizes a name to be used in a storage account. - -.DESCRIPTION -Sanitize-Name takes a string, and removes all of the '-'s and -lowercases the string, since storage account names must have no -'-'s and must be completely lowercase alphanumeric. It then makes -certain that the length of the string is not greater than 24, -since that is invalid. - -.PARAMETER RawName -The name to sanitize. -#> -function Sanitize-Name { - [CmdletBinding()] - Param( - [string]$RawName - ) - - $result = $RawName.Replace('-', '').ToLowerInvariant() - if ($result.Length -gt 24) { - Write-Error 'Sanitized name for storage account $result was too long.' - } - - return $result -} +Import-Module "$PSScriptRoot/../create-vmss-helpers.psm1" -DisableNameChecking #################################################################################################### Write-Progress `