Skip to content

Commit d991ffd

Browse files
eliaslopezgtfabiocavmattchenderson
authored
Adds missing commits for cherry-pick secret repository tests fix (#10333)
* Updating Host CI lease access (#10314) * Updating Host CI lease access (#10294) * Fixing Azure Powershell argument configuration (#10307) * WorkloadIdentityCredential in KVSecretsRepository (#10310) * Updating secret repository tests (#10317) (#10328) --------- Co-authored-by: Fabio Cavalcante <[email protected]> Co-authored-by: Matthew Henderson <[email protected]>
1 parent 832efff commit d991ffd

File tree

8 files changed

+182
-140
lines changed

8 files changed

+182
-140
lines changed

azure-pipelines.yml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -340,17 +340,12 @@ jobs:
340340
targetType: 'inline'
341341
script: 'Install-Module -Name Az.Storage -RequiredVersion 1.11.0 -Scope CurrentUser -Force -AllowClobber'
342342

343-
- task: AzureKeyVault@1
344-
inputs:
345-
# Note: This is actually a Service Connection in DevOps, not an Azure subscription name
346-
azureSubscription: 'Azure-Functions-Host-CI'
347-
keyVaultName: 'azure-functions-host-ci'
348-
secretsFilter: '*'
349-
- task: PowerShell@2
343+
- task: AzurePowerShell@5
350344
displayName: 'Checkout secrets'
351345
inputs:
352-
filePath: '$(Build.Repository.LocalPath)\build\checkout-secrets.ps1'
353-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'''
346+
azureSubscription: Azure-Functions-Host-CI
347+
ScriptPath: '$(Build.Repository.LocalPath)\build\checkout-secrets.ps1'
348+
azurePowerShellVersion: 'LatestVersion'
354349
- task: AzureKeyVault@1
355350
inputs:
356351
# Note: This is actually a Service Connection in DevOps, not an Azure subscription name
@@ -375,12 +370,14 @@ jobs:
375370
AzureWebJobsSecretStorageKeyVaultTenantId: $(AzureTenantId)
376371
AzureWebJobsSecretStorageKeyVaultClientId: $(AzureClientId)
377372
AzureWebJobsSecretStorageKeyVaultClientSecret: $(AzureClientSecret)
378-
- task: PowerShell@2
373+
- task: AzurePowerShell@5
379374
condition: always()
380375
displayName: 'Checkin secrets'
381376
inputs:
382-
filePath: '$(Build.Repository.LocalPath)\build\checkin-secrets.ps1'
383-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'' -leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'
377+
azureSubscription: Azure-Functions-Host-CI
378+
ScriptPath: '$(Build.Repository.LocalPath)\build\checkin-secrets.ps1'
379+
ScriptArguments: '-leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'
380+
azurePowerShellVersion: 'LatestVersion'
384381

385382
- job: RunIntegrationTests
386383
strategy:
@@ -418,11 +415,12 @@ jobs:
418415
azureSubscription: 'Azure-Functions-Host-CI'
419416
keyVaultName: 'azure-functions-host-ci'
420417
secretsFilter: '*'
421-
- task: PowerShell@2
418+
- task: AzurePowerShell@5
422419
displayName: 'Checkout secrets'
423420
inputs:
424-
filePath: '$(Build.Repository.LocalPath)\build\checkout-secrets.ps1'
425-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'''
421+
azureSubscription: Azure-Functions-Host-CI
422+
ScriptPath: '$(Build.Repository.LocalPath)\build\checkout-secrets.ps1'
423+
azurePowerShellVersion: 'LatestVersion'
426424
- task: AzureKeyVault@1
427425
inputs:
428426
# Note: This is actually a Service Connection in DevOps, not an Azure subscription name
@@ -567,12 +565,14 @@ jobs:
567565
arguments: '--filter "Group=ReleaseTests" --no-build -f $(targetFramework)'
568566
projects: |
569567
**\WebJobs.Script.Tests.Integration.csproj
570-
- task: PowerShell@2
568+
- task: AzurePowerShell@5
571569
condition: always()
572570
displayName: 'Checkin secrets'
573571
inputs:
574-
filePath: '$(Build.Repository.LocalPath)\build\checkin-secrets.ps1'
575-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'' -leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'
572+
azureSubscription: Azure-Functions-Host-CI
573+
ScriptPath: '$(Build.Repository.LocalPath)\build\checkin-secrets.ps1'
574+
ScriptArguments: '-leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'
575+
azurePowerShellVersion: 'LatestVersion'
576576

577577
- job: PublishArtifacts
578578
dependsOn:

build/checkin-secrets.ps1

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
param (
2-
[string]$connectionString = "",
32
[string]$leaseBlob = "",
43
[string]$leaseToken = ""
54
)
@@ -16,7 +15,7 @@ if ($leaseToken -eq "") {
1615

1716
Write-Host "Breaking lease for $leaseBlob."
1817

19-
$storageContext = New-AzStorageContext -ConnectionString $connectionString
18+
$storageContext = New-AzStorageContext -StorageAccountName "azurefunctionshostci0" -UseConnectedAccount
2019
$blob = Get-AzStorageBlob -Context $storageContext -Container "ci-locks" -Blob $leaseBlob
2120

2221
$accessCondition = New-Object -TypeName Microsoft.Azure.Storage.AccessCondition

build/checkout-secrets.ps1

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
param (
2-
[string]$connectionString = ""
3-
)
4-
51
function AcquireLease($blob) {
62
try {
73
return $blob.ICloudBlob.AcquireLease($null, $null, $null, $null, $null)
@@ -14,15 +10,10 @@ function AcquireLease($blob) {
1410
# use this for tracking metadata in lease blobs
1511
$buildName = "3.0." + $env:buildNumber + "_" + $env:SYSTEM_JOBDISPLAYNAME
1612

17-
$azVersion = "1.11.0"
1813
Import-Module Az.Storage
19-
$azModule = Get-Module -Name Az.Storage
20-
if ($azModule.Version -ne $azVersion) {
21-
throw "Az.Storage module version $azVersion was not found. Current version: $($azModule.Version)"
22-
}
2314

2415
# get a blob lease to prevent test overlap
25-
$storageContext = New-AzStorageContext -ConnectionString $connectionString
16+
$storageContext = New-AzStorageContext -StorageAccountName "azurefunctionshostci0" -UseConnectedAccount
2617

2718
While($true) {
2819
$blobs = Get-AzStorageBlob -Context $storageContext -Container "ci-locks"

eng/ci/templates/official/jobs/run-integration-tests.yml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,12 @@ jobs:
4141
command: ci
4242
workingDir: sample/CustomHandlerRetry
4343

44-
- task: AzureKeyVault@1
45-
inputs:
46-
# Note: This is actually a Service Connection in DevOps, not an Azure subscription name
47-
azureSubscription: Azure-Functions-Host-CI-internal
48-
keyVaultName: azure-functions-host-ci
49-
secretsFilter: '*'
50-
51-
- task: PowerShell@2
44+
- task: AzurePowerShell@5
5245
displayName: Checkout secrets
5346
inputs:
54-
filePath: build/checkout-secrets.ps1
55-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'''
47+
azureSubscription: Azure-Functions-Host-CI-internal
48+
azurePowerShellVersion: 'LatestVersion'
49+
ScriptPath: build/checkout-secrets.ps1
5650

5751
- task: AzureKeyVault@1
5852
inputs:
@@ -200,9 +194,11 @@ jobs:
200194
arguments: '--filter "Group=ReleaseTests" --no-build'
201195
projects: $(IntegrationProject)
202196

203-
- task: PowerShell@2
197+
- task: AzurePowerShell@5
204198
condition: always()
205199
displayName: Checkin secrets
206200
inputs:
207-
filePath: build/checkin-secrets.ps1
208-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'' -leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'
201+
azureSubscription: Azure-Functions-Host-CI-internal
202+
azurePowerShellVersion: 'LatestVersion'
203+
ScriptPath: build/checkin-secrets.ps1
204+
ScriptArguments: '-leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'

eng/ci/templates/official/jobs/run-non-e2e-tests.yml

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,12 @@ jobs:
2929
targetType: inline
3030
script: 'Install-Module -Name Az.Storage -RequiredVersion 1.11.0 -Scope CurrentUser -Force -AllowClobber'
3131

32-
- task: AzureKeyVault@1
33-
inputs:
34-
# Note: This is actually a Service Connection in DevOps, not an Azure subscription name
35-
azureSubscription: Azure-Functions-Host-CI-internal
36-
keyVaultName: azure-functions-host-ci
37-
secretsFilter: '*'
38-
39-
- task: PowerShell@2
32+
- task: AzurePowerShell@5
4033
displayName: Checkout secrets
4134
inputs:
42-
filePath: build/checkout-secrets.ps1
43-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'''
35+
azureSubscription: Azure-Functions-Host-CI-internal
36+
azurePowerShellVersion: 'LatestVersion'
37+
ScriptPath: build/checkout-secrets.ps1
4438

4539
- task: AzureKeyVault@1
4640
inputs:
@@ -49,6 +43,24 @@ jobs:
4943
keyVaultName: azure-functions-host-$(LeaseBlob)
5044
secretsFilter: '*'
5145

46+
- task: AzureCLI@2
47+
displayName: 'Setup Azure environment'
48+
inputs:
49+
azureSubscription: Azure-Functions-Host-CI-internal
50+
addSpnToEnvironment: true
51+
scriptType: bash
52+
scriptLocation: inlineScript
53+
inlineScript: |
54+
echo "##vso[task.setvariable variable=ARM_CLIENT_ID]$servicePrincipalId"
55+
echo "##vso[task.setvariable variable=ARM_ID_TOKEN]$idToken"
56+
echo "##vso[task.setvariable variable=ARM_TENANT_ID]$tenantId"
57+
58+
# This step ensures the azure context defined by the previous task is persisted
59+
# and available to subsequent steps/tasks.
60+
- bash: |
61+
az login --service-principal -u $(ARM_CLIENT_ID) --tenant $(ARM_TENANT_ID) --allow-no-subscriptions --federated-token $(ARM_ID_TOKEN)
62+
displayName: 'Login to Azure'
63+
5264
- task: DotNetCoreCLI@2
5365
displayName: Build Integration.csproj
5466
inputs:
@@ -73,9 +85,11 @@ jobs:
7385
AzureWebJobsSecretStorageKeyVaultClientId: $(AzureClientId)
7486
AzureWebJobsSecretStorageKeyVaultClientSecret: $(AzureClientSecret)
7587

76-
- task: PowerShell@2
88+
- task: AzurePowerShell@5
7789
condition: always()
7890
displayName: Checkin secrets
7991
inputs:
80-
filePath: build/checkin-secrets.ps1
81-
arguments: '-connectionString ''$(Storage-azurefunctionshostci0)'' -leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'
92+
azureSubscription: Azure-Functions-Host-CI-internal
93+
azurePowerShellVersion: 'LatestVersion'
94+
ScriptPath: build/checkin-secrets.ps1
95+
ScriptArguments: '-leaseBlob $(LeaseBlob) -leaseToken $(LeaseToken)'

src/WebJobs.Script.WebHost/Security/KeyManagement/KeyVaultSecretsRepository.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class KeyVaultSecretsRepository : BaseSecretsRepository
2626
private const string FunctionKeyPrefix = "host--functionKey--";
2727
private const string SystemKeyPrefix = "host--systemKey--";
2828

29+
private readonly Lazy<TokenCredential> _tokenCredential;
2930
private readonly Lazy<SecretClient> _secretClient;
3031
private readonly IEnvironment _environment;
3132

@@ -38,19 +39,38 @@ public KeyVaultSecretsRepository(string secretsSentinelFilePath, string vaultUri
3839

3940
Uri keyVaultUri = string.IsNullOrEmpty(vaultUri) ? throw new ArgumentException(nameof(vaultUri)) : new Uri(vaultUri);
4041

41-
_secretClient = new Lazy<SecretClient>(() =>
42+
_tokenCredential = new Lazy<TokenCredential>(() =>
4243
{
43-
// If clientSecret and tenantId are provided, use ClientSecret credential; otherwise use managed identity
44-
TokenCredential credential = !string.IsNullOrEmpty(clientSecret) && !string.IsNullOrEmpty(tenantId)
45-
? new ClientSecretCredential(tenantId, clientId, clientSecret)
46-
: new ChainedTokenCredential(new ManagedIdentityCredential(clientId), new ManagedIdentityCredential());
44+
if (!TryCreateTokenCredential(clientId, clientSecret, tenantId, out TokenCredential credential))
45+
{
46+
throw new InvalidOperationException("Failed to create token credential for KeyVaultSecretsRepository");
47+
}
4748

48-
return new SecretClient(keyVaultUri, credential);
49+
return credential;
50+
});
51+
52+
_secretClient = new Lazy<SecretClient>(() =>
53+
{
54+
return new SecretClient(keyVaultUri, _tokenCredential.Value);
4955
});
5056

5157
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
5258
}
5359

60+
internal KeyVaultSecretsRepository(string secretsSentinelFilePath, string vaultUri, ILogger logger, IEnvironment environment, TokenCredential testEnvironmentTokenCredential)
61+
: this(secretsSentinelFilePath, vaultUri, null, null, null, logger, environment)
62+
{
63+
_tokenCredential = new Lazy<TokenCredential>(() =>
64+
{
65+
if (!TryCreateTokenCredential(null, null, null, out TokenCredential credential))
66+
{
67+
throw new InvalidOperationException("Failed to create token credential for KeyVaultSecretsRepository");
68+
}
69+
70+
return new ChainedTokenCredential(testEnvironmentTokenCredential, credential);
71+
});
72+
}
73+
5474
// For testing
5575
internal KeyVaultSecretsRepository(SecretClient secretClient, string secretsSentinelFilePath, ILogger logger, IEnvironment environment) : base(secretsSentinelFilePath, logger, environment)
5676
{
@@ -72,6 +92,25 @@ public override async Task<ScriptSecrets> ReadAsync(ScriptSecretsType type, stri
7292
return type == ScriptSecretsType.Host ? await ReadHostSecrets() : await ReadFunctionSecrets(functionName);
7393
}
7494

95+
private static bool TryCreateTokenCredential(string clientId, string clientSecret, string tenantId, out TokenCredential credential)
96+
{
97+
if (!string.IsNullOrEmpty(clientId) && !string.IsNullOrEmpty(clientSecret) && !string.IsNullOrEmpty(tenantId))
98+
{
99+
credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
100+
return true;
101+
}
102+
else if (!string.IsNullOrEmpty(clientId))
103+
{
104+
credential = new ManagedIdentityCredential(clientId);
105+
return true;
106+
}
107+
else
108+
{
109+
credential = new ManagedIdentityCredential();
110+
return true;
111+
}
112+
}
113+
75114
public override async Task WriteAsync(ScriptSecretsType type, string functionName, ScriptSecrets secrets)
76115
{
77116
// Get secrets as dictionary

src/WebJobs.Script/Utility.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,13 @@ await InvokeWithRetriesAsync(() =>
141141
}, maxRetries, retryInterval);
142142
}
143143

144-
internal static async Task InvokeWithRetriesAsync(Func<Task> action, int maxRetries, TimeSpan retryInterval)
144+
internal static async Task InvokeWithRetriesWhenAsync(Func<Task> action, int maxRetries, TimeSpan retryInterval, Func<Exception, bool> predicate)
145145
{
146+
if (predicate is null)
147+
{
148+
throw new ArgumentNullException(nameof(predicate));
149+
}
150+
146151
int attempt = 0;
147152
while (true)
148153
{
@@ -151,7 +156,7 @@ internal static async Task InvokeWithRetriesAsync(Func<Task> action, int maxRetr
151156
await action();
152157
return;
153158
}
154-
catch (Exception ex) when (!ex.IsFatal())
159+
catch (Exception ex) when (predicate(ex))
155160
{
156161
if (++attempt > maxRetries)
157162
{
@@ -162,6 +167,9 @@ internal static async Task InvokeWithRetriesAsync(Func<Task> action, int maxRetr
162167
}
163168
}
164169

170+
internal static Task InvokeWithRetriesAsync(Func<Task> action, int maxRetries, TimeSpan retryInterval)
171+
=> InvokeWithRetriesWhenAsync(action, maxRetries, retryInterval, (e) => !e.IsFatal());
172+
165173
/// <summary>
166174
/// Delays while the specified condition remains true.
167175
/// </summary>

0 commit comments

Comments
 (0)