public async Task <IChallengeResponder> ParseChallengeResponderAsync(CertificateRenewalOptions cfg, CancellationToken cancellationToken) { var certStore = ParseCertificateStore(cfg); var target = ParseTargetResource(cfg); var cr = cfg.ChallengeResponder ?? new GenericEntry { Type = "storageAccount", Properties = JObject.FromObject(new StorageProperties { AccountName = ConvertToValidStorageAccountName(target.Name), KeyVaultName = certStore.Name }) }; switch (cr.Type.ToLowerInvariant()) { case "storageaccount": var props = cr.Properties?.ToObject <StorageProperties>() ?? new StorageProperties { KeyVaultName = cr.Name, AccountName = ConvertToValidStorageAccountName(cr.Name) }; // try MSI first, must do check if we can read to know if we have access var accountName = props.AccountName; if (string.IsNullOrEmpty(accountName)) { accountName = ConvertToValidStorageAccountName(target.Name); } var storage = await _storageFactory.FromMsiAsync(accountName, props.ContainerName, cancellationToken); // verify that MSI access works, fallback otherwise // not ideal since it's a readonly check // -> we need Blob Contributor for challenge persist but user could set Blob Reader and this check would pass // alternative: write + delete a file from container as a check try { await storage.ExistsAsync(FileNameForPermissionCheck, cancellationToken); } catch (RequestFailedException e) when(e.Status == (int)HttpStatusCode.Forbidden) { _logger.LogWarning($"MSI access to storage {accountName} failed. Attempting fallbacks via connection string. (You can ignore this warning if you don't use MSI authentication)."); var connectionString = props.ConnectionString; if (string.IsNullOrEmpty(connectionString)) { // falback to secret in keyvault var keyVaultName = props.KeyVaultName; if (string.IsNullOrEmpty(keyVaultName)) { keyVaultName = certStore.Name; } _logger.LogInformation($"No connection string in config, checking keyvault {keyVaultName} for secret {props.SecretName}"); try { connectionString = await GetSecretAsync(keyVaultName, props.SecretName, cancellationToken); } catch (Exception ex) { throw new AggregateException($"Failed to get connectionstring in secret {props.SecretName} from keyvault {keyVaultName}. If you intended to use storage MSI access, set \"Storage Blob Data Contributor\" on the respective storage container (permissions might take more than 10 minutes to take effect)", new[] { ex }); } } if (string.IsNullOrEmpty(connectionString)) { throw new InvalidOperationException($"MSI access failed for {accountName} and could not find fallback connection string for storage access. Unable to proceed with Let's encrypt challenge"); } storage = _storageFactory.FromConnectionString(connectionString, props.ContainerName); } return(new AzureStorageHttpChallengeResponder(storage, props.Path)); default: throw new NotImplementedException(cr.Type); } }