KeyVault GetSecretAsync never returns

前端 未结 5 1425
醉话见心
醉话见心 2021-01-03 10:37

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

public static async Task GetSecret(string secr         


        
相关标签:
5条回答
  • 2021-01-03 10:48

    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;
            }
    
        }
    }
    
    0 讨论(0)
  • 2021-01-03 10:52

    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.

    0 讨论(0)
  • 2021-01-03 10:55

    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");
    
    0 讨论(0)
  • 2021-01-03 11:07

    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

    0 讨论(0)
  • 2021-01-03 11:14

    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));
    
    0 讨论(0)
提交回复
热议问题