Describe the bug
When I am calling SecretClient.GetSecret(key) I am sometimes getting: Azure.RequestFailedException: Status: 401 (Unauthorized). This method is usually called multiple times in parallel using single instance of SecretClient. The constructor I use for secret client:new SecretClient(new Uri(keyVaultUrl), new ManagedIdentityCredential()); Example from my logs says that it can fail 3 times for 6 concurrent requests.
Expected behavior
The method should not throw.
Actual behavior (include Exception or Stack Trace)
Azure.RequestFailedException:
Status: 401 (Unauthorized)
Content:
{""error"":{""code"":""Unauthorized"",""message"":""Request is missing a Bearer or PoP token.""}}
Headers:
Cache-Control: no-cache
Pragma: no-cache
Server: Microsoft-IIS/10.0
WWW-Authenticate: Bearer authorization=""https://login.windows.net/d37240c9-786e-4171-a9f4-37cfd2adf51f"", resource=""https://vault.azure.net""
x-ms-keyvault-region: northeurope
x-ms-request-id: 1f648564-3db0-41b3-bba3-7fadec5198cb
x-ms-keyvault-service-version: 1.1.0.891
x-ms-keyvault-network-info: addr=40.113.80.190;act_addr_fam=InterNetwork;
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Strict-Transport-Security: max-age=31536000;includeSubDomains
X-Content-Type-Options: nosniff
Date: Fri, 31 Jan 2020 09:07:17 GMT
Content-Length: 87
Content-Type: application/json; charset=utf-8
Expires: -1
at Azure.Security.KeyVault.KeyVaultPipeline.SendRequest(Request request, CancellationToken cancellationToken)
at Azure.Security.KeyVault.KeyVaultPipeline.SendRequest[TResult](RequestMethod method, Func`1 resultFactory, CancellationToken cancellationToken, String[] path)
at Azure.Security.KeyVault.Secrets.SecretClient.GetSecret(String name, String version, CancellationToken cancellationToken)
To Reproduce
Steps to reproduce the behavior (include a code snippet, screenshot, or any additional information that might help us reproduce the issue)
Call GetSecret in parralel (for same resource) with KeyVaultTestClient singleton
public class KeyVaultTestClient
{
private readonly ConcurrentDictionary<string, string> _cachedSecrets = new ConcurrentDictionary<string, string>();
private readonly SecretClient _secretClient;
public KeyVaultTestClient(string keyVaultUrl)
{
_secretClient = new SecretClient(new Uri(keyVaultUrl), new ManagedIdentityCredential());
}
public string GetSecret(string key)
{
if (_cachedSecrets.ContainsKey(key))
{
return _cachedSecrets[key];
}
var secret = _secretClient.GetSecret(key)?.Value?.Value; // GetSecret throws sometimes
if (string.IsNullOrEmpty(secret))
{
throw new KeyNotFoundException($"Secret=[{key}]");
}
_cachedSecrets[key] = secret;
return secret;
}
}
Environment:
dotnet --info output for .NET Core projects): e.g. Azure AppService .NET Core 3.1 - I do not see the problem on my other app in Azure which is using .NET Framework 4.7.2//cc: @annelo-msft, @schaabs
@heaths can you please take a look?
@macchmie3 just to confirm, you are seeing RequestFailedExceptions being thrown and not just the 401 being logged (this is expected until the client is authenticated and challenges cached)?
@heaths
Yes, it's Exception that is being thrown. It was not some kind of internal log.
This appears to be a race condition in the challenge cache which we should synchronize. Will try to repro.
@heaths
wrapping var secret = _secretClient.GetSecret(key)?.Value?.Value; with a lock statement fixes the problem - as predicted, but it seems like a dirty workaround for me.
I'm able to repro this but with other work pending for our March release we may need to punt till after that since a workaround - agreeably not ideal - is available.
BTW, just a tip: there is an implicit cast operator for KeyVaultSecret so you can simplify that statement. I generally prefer var myself as well, but this is one case where an explicit type declaration can help:
KeyVaultSecret secret = _secretClient.GetSecret(key);
@heaths
Great to hear that you were able to reproduce the problem.
Thanks for the tip!
I feel like there should be a stronger warning on the main page about using this package in production if concurrent requests cause failures...
I believe I have a fix and was hoping you could try it to verify before we release it, given the nature of this problem is impacted by machine and scenario differences.
dotnet nuget add source -n AzureSDK https://azuresdkartifacts.blob.core.windows.net/azure-sdk-for-net/index.json
dotnet add package Azure.Security.KeyVault.Secrets --version 4.0.3-dev.20200318.2
Please let me know if this solves your problem and we'll get a release out on nuget.org. Thank you.
I will check it today!
@heaths
I just tested your package and seems like the issue is solved! Of course I double-checked that the workaround-locks are removed :)
Thanks for verifying the fix. We want to do some additional testing to make sure we didn't regress anything and fixed all the related issues here, and will get a servicing release out on nuget.org shortly.
Great timing. I was just getting this same error, but only when using GetPropertiesOfSecretVersionsAsync() in my code. I just tested the development package and it fixed my problem as well.
@heaths
Hi Heath..
Need your help on this, I have updated the nuget package Azure.Security.KeyVault.Secrets -Version 4.0.3, also tried 4.1.0.
But still getting 401 Unauthorized excpetions.
Could you please help on this.
Regards,
Manojkumar Sachdev
Please open a new issue and provide diagnostic information as described here: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/keyvault/Azure.Security.KeyVault.Secrets/README.md#troubleshooting
You can also capture logs using ETW without changing code using dotnet-trace: https://heaths.dev/azure/2020/02/04/trace-azure-sdk-for-net.html
Please open a new issue and provide diagnostic information as described here: https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/keyvault/Azure.Security.KeyVault.Secrets/README.md#troubleshooting
You can also capture logs using ETW without changing code using
dotnet-trace: https://heaths.dev/azure/2020/02/04/trace-azure-sdk-for-net.html
Hi @heaths
Have raised issue with MS on this and waiting for their update
Ticket number: 120091023001449
Just wanted to note that we will also consider a challenge-free auth flow as well: #15651