KeyVault GetSecretAsync never returns

倾然丶 夕夏残阳落幕 提交于 2020-01-01 06:38:24

问题


The sample code for working with KeyVault inside a web application has the following code in it:

public static async Task<string> GetSecret(string secretId)
{
    var secret = await keyVaultClient.GetSecretAsync(secretId);
    return secret.Value;
}

I've incorporated the KeyVaultAccessor object included in the sample in my application in order to test it. The call is executed as part of a query to one of my web api controller methods:

var secret = KeyVaultAccessor.GetSecret("https://superSecretUri").Result;

Unfortunately, the call never returns and the query hangs indefintely...

What might be the reason, because frankly I have no clue where to start...?


回答1:


This is the common deadlock issue that I describe in full on my blog. In short, the async method is attempting to return to the ASP.NET request context after the await completes, but that request only allows one thread at a time, and there is already a thread in that context (the one blocked on the call to Result). So the task is waiting for the context to be free, and the thread is blocking the context until the task completes: deadlock.

The proper solution is to use await instead of Result:

var secret = await KeyVaultAccessor.GetSecret("https://superSecretUri");



回答2:


I've used the following code to override the synchronization context:

var secret = Task.Run(async () => await KeyVaultAccessor.GetSecretAsync("https://superSecretUri")).Result;

This still lets you use .Result if you're in a non-async method




回答3:


Unfortunately, the call never returns and the query hangs indefinitely...

You have a classic deadlock. That's why you shouldn't block on async code. Behind the scenes, the compiler generates a state-machine and captures something called a SynchronizationContext. When you synchronously block the calling thread, the attempt to post the continuation back onto that same context causes the deadlock.

Instead of synchronously blocking with .Result, make your controller async and await on the Task returned from GetSecret:

public async IHttpActionResult FooAsync()
{
    var secret = await KeyVaultAccessor.GetSecretAsync("https://superSecretUri");
    return Ok();
}

Side note - Async methods should follow naming conventions and be postfixed with Async.




回答4:


Use the rest api...

public class AzureKeyVaultClient
{


    public string GetSecret(string name, string vault)
    {
        var client = new RestClient($"https://{vault}.vault.azure.net/");
        client.Authenticator = new AzureAuthenticator($"https://vault.azure.net");
        var request = new RestRequest($"secrets/{name}?api-version=2016-10-01");

        request.Method = Method.GET;


        var result = client.Execute(request);

        if (result.StatusCode != HttpStatusCode.OK)
        {
            Trace.TraceInformation($"Unable to retrieve {name} from {vault} with response {result.Content}");
            var exception =  GetKeyVaultErrorFromResponse(result.Content);
            throw exception;

        }
        else
        {
            return GetValueFromResponse(result.Content);
        }




    }

    public string GetValueFromResponse(string content)
    {

            var result = content.FromJson<keyvaultresponse>();
            return result.value;

    }


    public Exception GetKeyVaultErrorFromResponse(string content)
    {
        try
        {

            var result = content.FromJson<keyvautlerrorresponse>();
            var exception = new Exception($"{result.error.code} {result.error.message}");
            if(result.error.innererror!=null)
            {
                var innerException = new Exception($"{result.error.innererror.code} {result.error.innererror.message}");
            }

            return exception;
        }
        catch(Exception e)
        {
            return e;
        }
    }

    class keyvaultresponse
    {
        public string value { get; set; }
        public string contentType { get; set; }

    }

    class keyvautlerrorresponse
    {
        public keyvaulterror error {get;set;}
    }

    class keyvaulterror
    {
        public string code { get; set; }

        public string message { get; set; }

        public keyvaulterror innererror { get; set; }
    }

    class AzureAuthenticator : IAuthenticator
    {

        private string _authority;
        private string _clientId;
        private string _clientSecret;
        private string _resource;



        public AzureAuthenticator(string resource)
        {
            _authority = WebConfigurationManager.AppSettings["azure:Authority"];
            _clientId = WebConfigurationManager.AppSettings["azure:ClientId"];
            _clientSecret = WebConfigurationManager.AppSettings["azure:ClientSecret"];
            _resource = resource;

        }

        public AzureAuthenticator(string resource, string tennant, string clientid, string secret)
        {
            //https://login.microsoftonline.com/<tennant>/oauth2/oken
            _authority = authority;
            //azure client id (web app or native app
            _clientId = clientid;
            //azure client secret
            _clientSecret = secret;
            //vault.azure.net
            _resource = resource;

        }


        public void Authenticate(IRestClient client, IRestRequest request)
        {

            var token = GetS2SAccessTokenForProdMSA().AccessToken;
            //Trace.WriteLine(String.Format("obtained bearer token {0} from ADAL and adding to rest request",token));
            request.AddHeader("Authorization", String.Format("Bearer {0}", token));


        }

        public AuthenticationResult GetS2SAccessTokenForProdMSA()
        {
            return GetS2SAccessToken(_authority, _resource, _clientId, _clientSecret);
        }

        private AuthenticationResult GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
        {
            var clientCredential = new ClientCredential(clientId, clientSecret);
            AuthenticationContext context = new AuthenticationContext(authority, false);
            AuthenticationResult authenticationResult = context.AcquireToken(
                resource,
                clientCredential);
            return authenticationResult;
        }

    }
}



回答5:


This generic method can be used to override the deadlock issue:

public static T SafeAwaitResult<T>(Func<Task<T>> f)
{
    return Task.Run(async () => await f()).Result;
}

Use:

SafeAwaitResult(() => foo(param1, param2));


来源:https://stackoverflow.com/questions/33134579/keyvault-getsecretasync-never-returns

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!