Skip to content

Commit

Permalink
Sync eng/common directory with azure-sdk-tools for PR 2248 (#25408)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
azure-sdk and benbp authored Dec 1, 2021
1 parent 345c70e commit 2869980
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 112 deletions.
164 changes: 91 additions & 73 deletions eng/common/TestResources/New-TestResources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ param (
[switch] $OutFile
)

. $PSScriptRoot/SubConfig-Helpers.ps1

# By default stop for any error.
if (!$PSBoundParameters.ContainsKey('ErrorAction')) {
$ErrorActionPreference = 'Stop'
Expand Down Expand Up @@ -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])'")
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
93 changes: 93 additions & 0 deletions eng/common/TestResources/SubConfig-Helpers.ps1
Original file line number Diff line number Diff line change
@@ -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"
}
45 changes: 6 additions & 39 deletions eng/common/TestResources/build-test-resource-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}:
Expand All @@ -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

0 comments on commit 2869980

Please sign in to comment.