Sync eng/common directory with azure-sdk-tools for PR 2248 (#3122)

* Exclude certain live test deployment outputs from being marked as log secrets

* debug

* Update subscription configuration merge jobs to use secret handler

* Rename subscription config helper function script

* Fix variable name reference in scope

Co-authored-by: Ben Broderick Phillips <bebroder@microsoft.com>
This commit is contained in:
Azure SDK Bot 2021-12-01 13:11:10 -08:00 committed by GitHub
parent 6ed5696c7c
commit 411331d0ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 190 additions and 112 deletions

View File

@ -79,6 +79,8 @@ param (
[switch] $OutFile
)
. $PSScriptRoot/SubConfig-Helpers.ps1
# By default stop for any error.
if (!$PSBoundParameters.ContainsKey('ErrorAction')) {
$ErrorActionPreference = 'Stop'
@ -126,7 +128,7 @@ function LoadCloudConfig([string] $env)
function MergeHashes([hashtable] $source, [psvariable] $dest)
{
foreach ($key in $source.Keys) {
if ($dest.Value.ContainsKey($key) -and $dest.Value[$key] -ne $source[$key]) {
if ($dest.Value.Contains($key) -and $dest.Value[$key] -ne $source[$key]) {
Write-Warning ("Overwriting '$($dest.Name).$($key)' with value '$($dest.Value[$key])' " +
"to new value '$($source[$key])'")
}
@ -155,6 +157,93 @@ function BuildBicepFile([System.IO.FileSystemInfo] $file)
return $templateFilePath
}
function BuildDeploymentOutputs([string]$serviceDirectoryPrefix, [object]$azContext, [object]$deployment) {
# Add default values
$deploymentOutputs = [Ordered]@{
"${serviceDirectoryPrefix}CLIENT_ID" = $TestApplicationId;
"${serviceDirectoryPrefix}CLIENT_SECRET" = $TestApplicationSecret;
"${serviceDirectoryPrefix}TENANT_ID" = $azContext.Tenant.Id;
"${serviceDirectoryPrefix}SUBSCRIPTION_ID" = $azContext.Subscription.Id;
"${serviceDirectoryPrefix}RESOURCE_GROUP" = $resourceGroup.ResourceGroupName;
"${serviceDirectoryPrefix}LOCATION" = $resourceGroup.Location;
"${serviceDirectoryPrefix}ENVIRONMENT" = $azContext.Environment.Name;
"${serviceDirectoryPrefix}AZURE_AUTHORITY_HOST" = $azContext.Environment.ActiveDirectoryAuthority;
"${serviceDirectoryPrefix}RESOURCE_MANAGER_URL" = $azContext.Environment.ResourceManagerUrl;
"${serviceDirectoryPrefix}SERVICE_MANAGEMENT_URL" = $azContext.Environment.ServiceManagementUrl;
}
MergeHashes $EnvironmentVariables $(Get-Variable deploymentOutputs)
foreach ($key in $deployment.Outputs.Keys) {
$variable = $deployment.Outputs[$key]
# Work around bug that makes the first few characters of environment variables be lowercase.
$key = $key.ToUpperInvariant()
if ($variable.Type -eq 'String' -or $variable.Type -eq 'SecureString') {
$deploymentOutputs[$key] = $variable.Value
}
}
return $deploymentOutputs
}
function SetDeploymentOutputs([string]$serviceName, [object]$azContext, [object]$deployment, [object]$templateFile) {
$serviceDirectoryPrefix = $serviceName.ToUpperInvariant() + "_"
$deploymentOutputs = BuildDeploymentOutputs $serviceDirectoryPrefix $azContext $deployment
if ($OutFile) {
if (!$IsWindows) {
Write-Host 'File option is supported only on Windows'
}
$outputFile = "$($templateFile.originalFilePath).env"
$environmentText = $deploymentOutputs | ConvertTo-Json;
$bytes = [System.Text.Encoding]::UTF8.GetBytes($environmentText)
$protectedBytes = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
Set-Content $outputFile -Value $protectedBytes -AsByteStream -Force
Write-Host "Test environment settings`n $environmentText`nstored into encrypted $outputFile"
} else {
if (!$CI) {
# Write an extra new line to isolate the environment variables for easy reading.
Log "Persist the following environment variables based on your detected shell ($shell):`n"
}
# Marking values as secret by allowed keys below is not sufficient, as there may be outputs set in the ARM/bicep
# file that re-mark those values as secret (since all user-provided deployment outputs are treated as secret by default).
# This variable supports a second check on not marking previously allowed keys/values as secret.
$notSecretValues = @()
foreach ($key in $deploymentOutputs.Keys) {
$value = $deploymentOutputs[$key]
$EnvironmentVariables[$key] = $value
if ($CI) {
if (ShouldMarkValueAsSecret $serviceDirectoryPrefix $key $value $notSecretValues) {
# Treat all ARM template output variables as secrets since "SecureString" variables do not set values.
# In order to mask secrets but set environment variables for any given ARM template, we set variables twice as shown below.
Write-Host "##vso[task.setvariable variable=_$key;issecret=true;]$value"
Write-Host "Setting variable as secret '$key': $value"
} else {
Write-Host "Setting variable '$key': $value"
$notSecretValues += $value
}
Write-Host "##vso[task.setvariable variable=$key;]$value"
} else {
Write-Host ($shellExportFormat -f $key, $value)
}
}
if ($key) {
# Isolate the environment variables for easy reading.
Write-Host "`n"
$key = $null
}
}
}
# Support actions to invoke on exit.
$exitActions = @({
if ($exitActions.Count -gt 1) {
@ -580,78 +669,7 @@ try {
Write-Verbose "Successfully deployed template '$($templateFile.jsonFilePath)' to resource group '$($resourceGroup.ResourceGroupName)'"
}
$serviceDirectoryPrefix = $serviceName.ToUpperInvariant() + "_"
# Add default values
$deploymentOutputs = @{
"$($serviceDirectoryPrefix)CLIENT_ID" = $TestApplicationId;
"$($serviceDirectoryPrefix)CLIENT_SECRET" = $TestApplicationSecret;
"$($serviceDirectoryPrefix)TENANT_ID" = $context.Tenant.Id;
"$($serviceDirectoryPrefix)SUBSCRIPTION_ID" = $context.Subscription.Id;
"$($serviceDirectoryPrefix)RESOURCE_GROUP" = $resourceGroup.ResourceGroupName;
"$($serviceDirectoryPrefix)LOCATION" = $resourceGroup.Location;
"$($serviceDirectoryPrefix)ENVIRONMENT" = $context.Environment.Name;
"$($serviceDirectoryPrefix)AZURE_AUTHORITY_HOST" = $context.Environment.ActiveDirectoryAuthority;
"$($serviceDirectoryPrefix)RESOURCE_MANAGER_URL" = $context.Environment.ResourceManagerUrl;
"$($serviceDirectoryPrefix)SERVICE_MANAGEMENT_URL" = $context.Environment.ServiceManagementUrl;
"$($serviceDirectoryPrefix)STORAGE_ENDPOINT_SUFFIX" = $context.Environment.StorageEndpointSuffix;
}
MergeHashes $EnvironmentVariables $(Get-Variable deploymentOutputs)
foreach ($key in $deployment.Outputs.Keys) {
$variable = $deployment.Outputs[$key]
# Work around bug that makes the first few characters of environment variables be lowercase.
$key = $key.ToUpperInvariant()
if ($variable.Type -eq 'String' -or $variable.Type -eq 'SecureString') {
$deploymentOutputs[$key] = $variable.Value
}
}
if ($OutFile) {
if (!$IsWindows) {
Write-Host 'File option is supported only on Windows'
}
$outputFile = "$($templateFile.originalFilePath).env"
$environmentText = $deploymentOutputs | ConvertTo-Json;
$bytes = [System.Text.Encoding]::UTF8.GetBytes($environmentText)
$protectedBytes = [Security.Cryptography.ProtectedData]::Protect($bytes, $null, [Security.Cryptography.DataProtectionScope]::CurrentUser)
Set-Content $outputFile -Value $protectedBytes -AsByteStream -Force
Write-Host "Test environment settings`n $environmentText`nstored into encrypted $outputFile"
} else {
if (!$CI) {
# Write an extra new line to isolate the environment variables for easy reading.
Log "Persist the following environment variables based on your detected shell ($shell):`n"
}
foreach ($key in $deploymentOutputs.Keys) {
$value = $deploymentOutputs[$key]
$EnvironmentVariables[$key] = $value
if ($CI) {
# Treat all ARM template output variables as secrets since "SecureString" variables do not set values.
# In order to mask secrets but set environment variables for any given ARM template, we set variables twice as shown below.
Write-Host "Setting variable '$key': ***"
Write-Host "##vso[task.setvariable variable=_$key;issecret=true;]$($value)"
Write-Host "##vso[task.setvariable variable=$key;]$($value)"
} else {
Write-Host ($shellExportFormat -f $key, $value)
}
}
if ($key) {
# Isolate the environment variables for easy reading.
Write-Host "`n"
$key = $null
}
}
SetDeploymentOutputs $serviceName $context $deployment $templateFile
$postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath 'test-resources-post.ps1'
if (Test-Path $postDeploymentScript) {

View File

@ -0,0 +1,93 @@
function ShouldMarkValueAsSecret([string]$serviceDirectoryPrefix, [string]$key, [string]$value, [array]$allowedValues = @())
{
$logOutputNonSecret = @(
# Environment Variables
"RESOURCEGROUP_NAME",
# Deployment Outputs
"CLIENT_ID",
"TENANT_ID",
"SUBSCRIPTION_ID",
"RESOURCE_GROUP",
"LOCATION",
"ENVIRONMENT",
"AUTHORITY_HOST",
"RESOURCE_MANAGER_URL",
"SERVICE_MANAGEMENT_URL",
"ENDPOINT_SUFFIX",
# This is used in many places and is harder to extract from the base subscription config, so hardcode it for now.
"STORAGE_ENDPOINT_SUFFIX",
# Parameters
"Environment",
"SubscriptionId",
"TenantId",
"TestApplicationId",
"TestApplicationOid",
"ProvisionerApplicationId"
)
$suffix1 = $key -replace $serviceDirectoryPrefix, ""
$suffix2 = $key -replace "AZURE_", ""
$variants = @($key, $suffix1, $suffix2)
if ($variants | Where-Object { $logOutputNonSecret -contains $_ }) {
return $false
}
if ($allowedValues -contains $value) {
return $false
}
return $true
}
function SetSubscriptionConfiguration([object]$subscriptionConfiguration)
{
foreach($pair in $subscriptionConfiguration.GetEnumerator()) {
if ($pair.Value -is [Hashtable]) {
foreach($nestedPair in $pair.Value.GetEnumerator()) {
# Mark values as secret so we don't print json blobs containing secrets in the logs.
# Prepend underscore to the variable name, so we can still access the variable names via environment
# variables if they get set subsequently.
if (ShouldMarkValueAsSecret "AZURE_" $nestedPair.Name $nestedPair.Value) {
Write-Host "##vso[task.setvariable variable=_$($nestedPair.Name);issecret=true;]$($nestedPair.Value)"
}
}
} else {
if (ShouldMarkValueAsSecret "AZURE_" $pair.Name $pair.Value) {
Write-Host "##vso[task.setvariable variable=_$($pair.Name);issecret=true;]$($pair.Value)"
}
}
}
Write-Host ($subscriptionConfiguration | ConvertTo-Json)
$serialized = $subscriptionConfiguration | ConvertTo-Json -Compress
Write-Host "##vso[task.setvariable variable=SubscriptionConfiguration;]$serialized"
}
function UpdateSubscriptionConfiguration([object]$subscriptionConfigurationBase, [object]$subscriptionConfiguration)
{
foreach ($pair in $subscriptionConfiguration.GetEnumerator()) {
if ($pair.Value -is [Hashtable]) {
if (!$subscriptionConfigurationBase.ContainsKey($pair.Name)) {
$subscriptionConfigurationBase[$pair.Name] = @{}
}
foreach($nestedPair in $pair.Value.GetEnumerator()) {
# Mark values as secret so we don't print json blobs containing secrets in the logs.
# Prepend underscore to the variable name, so we can still access the variable names via environment
# variables if they get set subsequently.
if (ShouldMarkValueAsSecret "AZURE_" $nestedPair.Name $nestedPair.Value) {
Write-Host "##vso[task.setvariable variable=_$($nestedPair.Name);issecret=true;]$($nestedPair.Value)"
}
$subscriptionConfigurationBase[$pair.Name][$nestedPair.Name] = $nestedPair.Value
}
} else {
if (ShouldMarkValueAsSecret "AZURE_" $pair.Name $pair.Value) {
Write-Host "##vso[task.setvariable variable=_$($pair.Name);issecret=true;]$($pair.Value)"
}
$subscriptionConfigurationBase[$pair.Name] = $pair.Value
}
}
$serialized = $subscriptionConfigurationBase | ConvertTo-Json -Compress
Write-Host ($subscriptionConfigurationBase | ConvertTo-Json)
Write-Host "##vso[task.setvariable variable=SubscriptionConfiguration;]$serialized"
}

View File

@ -13,22 +13,8 @@ steps:
${{ parameters.SubscriptionConfiguration }}
'@ | ConvertFrom-Json -AsHashtable
foreach($pair in $config.GetEnumerator()) {
if ($pair.Value -is [Hashtable]) {
foreach($nestedPair in $pair.Value.GetEnumerator()) {
# Mark values as secret so we don't print json blobs containing secrets in the logs.
# Prepend underscore to the variable name, so we can still access the variable names via environment
# variables if they get set subsequently.
Write-Host "##vso[task.setvariable variable=_$($nestedPair.Name);issecret=true;]$($nestedPair.Value)"
}
} else {
Write-Host "##vso[task.setvariable variable=_$($pair.Name);issecret=true;]$($pair.Value)"
}
}
Write-Host ($config | ConvertTo-Json)
$serialized = $config | ConvertTo-Json -Compress
Write-Host "##vso[task.setvariable variable=SubscriptionConfiguration;]$serialized"
. ./eng/common/TestResources/SubConfig-Helpers.ps1
SetSubscriptionConfiguration $config
displayName: Initialize SubscriptionConfiguration variable
- ${{ if parameters.SubscriptionConfigurations }}:
@ -39,33 +25,14 @@ steps:
- ${{ each config in parameters.SubscriptionConfigurations }}:
- pwsh: |
$config = @'
$configBase = @'
$(SubscriptionConfiguration)
'@ | ConvertFrom-Json -AsHashtable
$addToConfig = @'
$config = @'
${{ config }}
'@ | ConvertFrom-Json -AsHashtable
foreach ($pair in $addToConfig.GetEnumerator()) {
if ($pair.Value -is [Hashtable]) {
if (!$config.ContainsKey($pair.Name)) {
$config[$pair.Name] = @{}
}
foreach($nestedPair in $pair.Value.GetEnumerator()) {
# Mark values as secret so we don't print json blobs containing secrets in the logs.
# Prepend underscore to the variable name, so we can still access the variable names via environment
# variables if they get set subsequently.
Write-Host "##vso[task.setvariable variable=_$($nestedPair.Name);issecret=true;]$($nestedPair.Value)"
$config[$pair.Name][$nestedPair.Name] = $nestedPair.Value
}
} else {
Write-Host "##vso[task.setvariable variable=_$($pair.Name);issecret=true;]$($pair.Value)"
$config[$pair.Name] = $pair.Value
}
}
$serialized = $config | ConvertTo-Json -Compress
Write-Host ($config | ConvertTo-Json)
Write-Host "##vso[task.setvariable variable=SubscriptionConfiguration;]$serialized"
. ./eng/common/TestResources/SubConfig-Helpers.ps1
UpdateSubscriptionConfiguration $configBase $config
displayName: Merge Test Resource Configurations