2017-08-22 36 views
3

我們已經使用WebJobs SDK中的[FunctionName]屬性在類中定義了一些Azure函數。該類有幾個功能,他們都需要訪問存儲在Azure KeyVault中的祕密。問題是我們每分鐘都有數百次的函數調用,並且由於每個函數都在調用KeyVault,因此KeyVault失敗時會顯示一條消息,如「連接太多,通常只允許10個連接」。Azure KeyVault - 來自Azure函數的太多連接

@crandycodes(Chris Anderson)在Twitter上建議使KeyVaultClient爲靜態。但是,我們用於KeyVaultClient的構造函數需要構造函數的委託函數,並且不能將靜態方法用作委託。那麼我們如何才能使KeyVaultClient爲靜態?這應該允許功能共享客戶端,從而減少套接字的數量。

下面是我們的KeyVaultHelper類:

public class KeyVaultHelper 
{ 
    public string ClientId { get; protected set; } 

    public string ClientSecret { get; protected set; } 

    public string VaultUrl { get; protected set; } 

    public KeyVaultHelper(string clientId, string secret, string vaultName = null) 
    { 
     ClientId = clientId; 
     ClientSecret = secret; 
     VaultUrl = vaultName == null ? null : $"https://{vaultName}.vault.azure.net/"; 
    } 

    public async Task<string> GetSecretAsync(string key) 
    { 
     try 
     { 

      using (var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync), 
       new HttpClient())) 
      { 
       var secret = await client.GetSecretAsync(VaultUrl, key); 
       return secret.Value; 
      } 
     } 
     catch (Exception ex) 
     { 
      throw new ApplicationException($"Could not get value for secret {key}", ex); 
     } 
    } 

    public async Task<string> GetAccessTokenAsync(string authority, string resource, string scope) 
    { 
     var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared); 
     var clientCred = new ClientCredential(ClientId, ClientSecret); 
     var result = await authContext.AcquireTokenAsync(resource, clientCred); 

     if (result == null) 
     { 
      throw new InvalidOperationException("Could not get token for vault"); 
     } 

     return result.AccessToken; 
    } 
} 

下面是我們如何引用我們的功能類:

public class ProcessorEntryPoint 
{ 
    [FunctionName("MyFuncA")] 
    public static async Task ProcessA(
     [QueueTrigger("queue-a", Connection = "queues")]ProcessMessage msg, 
     TraceWriter log 
     ) 
    { 
     var keyVaultHelper = new KeyVaultHelper(CloudConfigurationManager.GetSetting("ClientId"), CloudConfigurationManager.GetSetting("ClientSecret"), 
      CloudConfigurationManager.GetSetting("VaultName")); 
     var secret = keyVaultHelper.GetSecretAsync("mysecretkey"); 
     // do a stuff 
    } 

    [FunctionName("MyFuncB")] 
    public static async Task ProcessB(
     [QueueTrigger("queue-b", Connection = "queues")]ProcessMessage msg, 
     TraceWriter log 
     ) 
    { 
     var keyVaultHelper = new KeyVaultHelper(CloudConfigurationManager.GetSetting("ClientId"), CloudConfigurationManager.GetSetting("ClientSecret"), 
      CloudConfigurationManager.GetSetting("VaultName")); 
     var secret = keyVaultHelper.GetSecretAsync("mysecretkey"); 
     // do b stuff 
    } 
} 

我們可以使KeyVaultHelper類的靜態,但反過來就需要一個靜態KeyVaultClient對象,以避免在每個函數調用上創建新的連接 - 那麼我們該怎麼做,或者有另一種解決方案?我們無法相信需要KeyVault訪問的功能不可擴展!?

回答

2

您可以使用內存緩存並將緩存的長度設置爲您的方案中可接受的某個時間。在以下情況下,您有滑動過期,您也可以使用絕對過期,具體取決於祕密更改的時間。

public async Task<string> GetSecretAsync(string key) 
{ 
    MemoryCache memoryCache = MemoryCache.Default; 
    string mkey = VaultUrl + "_" +key; 
    if (!memoryCache.Contains(mkey)) 
    { 
     try 
     { 

      using (var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync), 
      new HttpClient())) 
      { 
       memoryCache.Add(mkey, await client.GetSecretAsync(VaultUrl, key), new CacheItemPolicy() { SlidingExpiration = TimeSpan.FromHours(1) }); 
      } 
     } 
     catch (Exception ex) 
     { 
      throw new ApplicationException($"Could not get value for secret {key}", ex); 
     } 
     return memoryCache[mkey] as string; 
    } 
} 
+0

感謝@Peter - 我們結束做這樣的事情。可惜的是,你無法擴展需要KeyVault的函數 - 看起來有點棘手的問題。 MemCache感覺很糟糕。 –

0

嘗試在助手以下變化:

public class KeyVaultHelper 
{ 
    public string ClientId { get; protected set; } 

    public string ClientSecret { get; protected set; } 

    public string VaultUrl { get; protected set; } 

    KeyVaultClient client = null; 

    public KeyVaultHelper(string clientId, string secret, string vaultName = null) 
    { 
     ClientId = clientId; 
     ClientSecret = secret; 
     VaultUrl = vaultName == null ? null : $"https://{vaultName}.vault.azure.net/"; 
     client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync), new HttpClient()); 
    } 

    public async Task<string> GetSecretAsync(string key) 
    { 
     try 
     { 
      if (client == null) 
       client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync), new HttpClient()); 

      var secret = await client.GetSecretAsync(VaultUrl, key); 
      return secret.Value; 
     } 
     catch (Exception ex) 
     { 
      if (client != null) 
      { 
       client.Dispose(); 
       client = null; 
      } 
      throw new ApplicationException($"Could not get value for secret {key}", ex); 
     } 
    } 

    public async Task<string> GetAccessTokenAsync(string authority, string resource, string scope) 
    { 
     var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared); 
     var clientCred = new ClientCredential(ClientId, ClientSecret); 
     var result = await authContext.AcquireTokenAsync(resource, clientCred); 

     if (result == null) 
     { 
      throw new InvalidOperationException("Could not get token for vault"); 
     } 

     return result.AccessToken; 
    } 
} 

目前,該功能可以使用默認的靜態構造函數,以保持客戶端代理:

public static class ProcessorEntryPoint 
{ 
    static KeyVaultHelper keyVaultHelper; 

    static ProcessorEntryPoint() 
    { 
     keyVaultHelper = new KeyVaultHelper(CloudConfigurationManager.GetSetting("ClientId"), CloudConfigurationManager.GetSetting("ClientSecret"), CloudConfigurationManager.GetSetting("VaultName")); 
    } 

    [FunctionName("MyFuncA")] 
    public static async Task ProcessA([QueueTrigger("queue-a", Connection = "queues")]ProcessMessage msg, TraceWriter log) 
    {   
     var secret = keyVaultHelper.GetSecretAsync("mysecretkey"); 
     // do a stuff 

    } 

    [FunctionName("MyFuncB")] 
    public static async Task ProcessB([QueueTrigger("queue-b", Connection = "queues")]ProcessMessage msg, TraceWriter log) 
    { 
     var secret = keyVaultHelper.GetSecretAsync("mysecretkey"); 
     // do b stuff 

    } 
}