diff --git a/eng/common/TestResources/New-TestResources.ps1 b/eng/common/TestResources/New-TestResources.ps1 index 17c4def39..6ee09ff3b 100644 --- a/eng/common/TestResources/New-TestResources.ps1 +++ b/eng/common/TestResources/New-TestResources.ps1 @@ -80,6 +80,19 @@ param ( [ValidateNotNull()] [hashtable] $EnvironmentVariables = @{}, + # List of CIDR ranges to add to specific resource firewalls, e.g. @(10.100.0.0/16, 10.200.0.0/16) + [Parameter()] + [ValidateCount(0,399)] + [Validatescript({ + foreach ($range in $PSItem) { + if ($range -like '*/31' -or $range -like '*/32') { + throw "Firewall IP Ranges cannot contain a /31 or /32 CIDR" + } + } + return $true + })] + [array] $AllowIpRanges = @(), + [Parameter()] [switch] $CI = ($null -ne $env:SYSTEM_TEAMPROJECTID), @@ -106,15 +119,6 @@ param ( . $PSScriptRoot/SubConfig-Helpers.ps1 -$azsdkPipelineVnet = "/subscriptions/a18897a6-7e44-457d-9260-f2854c0aca42/resourceGroups/azsdk-pools/providers/Microsoft.Network/virtualNetworks/azsdk-pipeline-vnet-wus" -$azsdkPipelineSubnets = @( - ($azsdkPipelineVnet + "/subnets/pipeline-subnet-ubuntu-1804-general"), - ($azsdkPipelineVnet + "/subnets/pipeline-subnet-ubuntu-2004-general"), - ($azsdkPipelineVnet + "/subnets/pipeline-subnet-ubuntu-2204-general"), - ($azsdkPipelineVnet + "/subnets/pipeline-subnet-win-2019-general"), - ($azsdkPipelineVnet + "/subnets/pipeline-subnet-win-2022-general") -) - if (!$ServicePrincipalAuth) { # Clear secrets if not using Service Principal auth. This prevents secrets # from being passed to pre- and post-scripts. @@ -262,7 +266,7 @@ function MergeHashes([hashtable] $source, [psvariable] $dest) function BuildBicepFile([System.IO.FileSystemInfo] $file) { if (!(Get-Command bicep -ErrorAction Ignore)) { - Write-Error "A bicep file was found at '$($file.FullName)' but the Azure Bicep CLI is not installed. See https://aka.ms/install-bicep-pwsh" + Write-Error "A bicep file was found at '$($file.FullName)' but the Azure Bicep CLI is not installed. See aka.ms/bicep-install" throw } @@ -758,8 +762,9 @@ try { if ($TestApplicationSecret -and $ServicePrincipalAuth) { $templateParameters.Add('testApplicationSecret', $TestApplicationSecret) } - if ($CI -and $Environment -eq 'AzureCloud') { - $templateParameters.Add('azsdkPipelineSubnetList', $azsdkPipelineSubnets) + # Only add subnets when running in an azure pipeline context + if ($CI -and $Environment -eq 'AzureCloud' -and $env:PoolSubnet) { + $templateParameters.Add('azsdkPipelineSubnetList', @($env:PoolSubnet)) } $defaultCloudParameters = LoadCloudConfig $Environment @@ -838,6 +843,32 @@ try { -templateFile $templateFile ` -environmentVariables $EnvironmentVariables + $storageAccounts = Retry { Get-AzResource -ResourceGroupName $ResourceGroupName -ResourceType "Microsoft.Storage/storageAccounts" } + # Add client IP to storage account when running as local user. Pipeline's have their own vnet with access + if ($storageAccounts) { + foreach ($account in $storageAccounts) { + $rules = Get-AzStorageAccountNetworkRuleSet -ResourceGroupName $ResourceGroupName -AccountName $account.Name + if ($rules -and $rules.DefaultAction -eq "Allow") { + Write-Host "Restricting network rules in storage account '$($account.Name)' to deny access by default" + Retry { Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $ResourceGroupName -Name $account.Name -DefaultAction Deny } + if ($CI -and $env:PoolSubnet) { + Write-Host "Enabling access to '$($account.Name)' from pipeline subnet $($env:PoolSubnet)" + Retry { Add-AzStorageAccountNetworkRule -ResourceGroupName $ResourceGroupName -Name $account.Name -VirtualNetworkResourceId $env:PoolSubnet } + } elseif ($AllowIpRanges) { + Write-Host "Enabling access to '$($account.Name)' to $($AllowIpRanges.Length) IP ranges" + $ipRanges = $AllowIpRanges | ForEach-Object { + @{ Action = 'allow'; IPAddressOrRange = $_ } + } + Retry { Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $ResourceGroupName -Name $account.Name -IPRule $ipRanges | Out-Null } + } elseif (!$CI) { + Write-Host "Enabling access to '$($account.Name)' from client IP" + $clientIp ??= Retry { Invoke-RestMethod -Uri 'https://icanhazip.com/' } # cloudflare owned ip site + Retry { Add-AzStorageAccountNetworkRule -ResourceGroupName $ResourceGroupName -Name $account.Name -IPAddressOrRange $clientIp | Out-Null } + } + } + } + } + $postDeploymentScript = $templateFile.originalFilePath | Split-Path | Join-Path -ChildPath "$ResourceType-resources-post.ps1" if (Test-Path $postDeploymentScript) { Log "Invoking post-deployment script '$postDeploymentScript'" @@ -852,7 +883,6 @@ try { Write-Host "Deleting ARM deployment as it may contain secrets. Deployed resources will not be affected." $null = $deployment | Remove-AzResourceGroupDeployment } - } finally { $exitActions.Invoke() } @@ -1023,6 +1053,10 @@ Optional key-value pairs of parameters to pass to the ARM template(s). .PARAMETER EnvironmentVariables Optional key-value pairs of parameters to set as environment variables to the shell. +.PARAMETER AllowIpRanges +Optional array of CIDR ranges to add to the network firewall for resource types like storage. +When running locally, if this parameter is not set then the client's IP will be queried and added to the firewall instead. + .PARAMETER CI Indicates the script is run as part of a Continuous Integration / Continuous Deployment (CI/CD) build (only Azure Pipelines is currently supported). diff --git a/eng/common/TestResources/build-test-resource-config.yml b/eng/common/TestResources/build-test-resource-config.yml index 2042ec344..c64664f85 100644 --- a/eng/common/TestResources/build-test-resource-config.yml +++ b/eng/common/TestResources/build-test-resource-config.yml @@ -14,6 +14,25 @@ parameters: default: null steps: + - task: AzurePowerShell@5 + displayName: Set Pipeline Subnet Info + env: ${{ parameters.EnvVars }} + inputs: + azureSubscription: azure-sdk-tests + azurePowerShellVersion: LatestVersion + pwsh: true + ScriptType: InlineScript + Inline: | + Set-AzContext 'Azure SDK Engineering System' + if ($env:Pool -eq 'Azure Pipelines') { + Write-Host "Skipping pool subnet lookup when running on an Azure Pipelines hosted pool" + Write-Host "##vso[task.setvariable variable=PoolSubnet;]" + exit 0 + } + $poolSubnet = (Get-AzResource -ResourceGroupName azsdk-pools -Name $env:Pool -ExpandProperties).Properties.networkProfile.subnetId + Write-Host "Setting pipeline subnet env variable PoolSubnet to '$poolSubnet'" + Write-Host "##vso[task.setvariable variable=PoolSubnet;]$poolSubnet" + - pwsh: | . ./eng/common/TestResources/SubConfig-Helpers.ps1 diff --git a/eng/common/TestResources/deploy-test-resources.yml b/eng/common/TestResources/deploy-test-resources.yml index 8976ad269..d5fd1d353 100644 --- a/eng/common/TestResources/deploy-test-resources.yml +++ b/eng/common/TestResources/deploy-test-resources.yml @@ -46,6 +46,7 @@ steps: displayName: Deploy test resources env: TEMP: $(Agent.TempDirectory) + PoolSubnet: $(PoolSubnet) ${{ insert }}: ${{ parameters.EnvVars }} inputs: azureSubscription: ${{ parameters.ServiceConnection }} @@ -68,6 +69,7 @@ steps: -DeleteAfterHours '${{ parameters.DeleteAfterHours }}' ` @subscriptionConfiguration ` -AdditionalParameters ${{ parameters.ArmTemplateParameters }} ` + -AllowIpRanges ('$(azsdk-corp-net-ip-ranges)' -split ',') ` -CI ` -Force ` -Verbose | Out-Null @@ -89,6 +91,7 @@ steps: -DeleteAfterHours '${{ parameters.DeleteAfterHours }}' ` @subscriptionConfiguration ` -AdditionalParameters ${{ parameters.ArmTemplateParameters }} ` + -AllowIpRanges ('$(azsdk-corp-net-ip-ranges)' -split ',') ` -CI ` -ServicePrincipalAuth ` -Force ` @@ -96,4 +99,5 @@ steps: displayName: Deploy test resources env: TEMP: $(Agent.TempDirectory) + PoolSubnet: $(PoolSubnet) ${{ insert }}: ${{ parameters.EnvVars }}