I have an U-SQL script with custom extractor, which access Azure Key Vault to get some credentials.
I followed this tutorial. And I have equivalent code to get token from AD and then to call provided URI for actual credentials:
public static async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
var clientCred = new ClientCredential(applicationId, authenticationKey);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the AD token");
}
return result.AccessToken;
}
public static async Task<string> GetSecret(string secretUri)
{
var keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(GetToken)
);
var sec = await keyVaultClient.GetSecretAsync(secretUri);
return sec.Value;
}
My credentials were put into vault successfully, and I have an URI to access them - something like:
https://my-key-vault-name.vault.azure.net:443/secrets/MyCredentialsName/123abc
I've registered my app in Azure AD and got application-id and authentication-key for it and I allowed my app to read secret from Key Vault. In my U-SQL script I've referenced all needed assemblies.
When I run my script locally everything works great (that means connection from local machine to AD and to Key Vault are OK), but when I submit it for execution on remote Data Lake Analytics account I got the following error:
The remote name could not be resolved:
'my-key-vault-name.vault.azure.net'
at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult
asyncResult) at
System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar)
My administrative rights on Azure resource group are limited, but I can access Firewall tab on Data Lake Analytics blade - I've tried enabling and disabling firewall, switching on/off Allow access to Azure services, still the error persists.
As dependencies, I am referencing Microsoft.Azure.KeyVault 2.0.6, Microsoft.Azure.KeyVaultWebKey 2.0.4, Microsoft.IdentityModel.Clients.ActiveDirectory 3.13.9.
Any ideas on how can I attempt to resolve it?
U-SQL code running in ADLA does not allow you to connect to resources outside the container/VM. The reason is:
U-SQL's custom code calls possibly scaled out over 100 to 1000s of containers getting invoked millions for millions of rows. This can easily lead to a (hopefully unintended) distributed denial of service attach against the service you are trying to reach, leading to possibly DDOSing the service and getting the Azure IP ranges blocked.
Local run does not currently run in a container so has no such limit enforcement.
What are you trying to achieve with this call? Note that the data in storage can already be transparently encoded with Azure Key Vault.
Related
I want the azure functions to use the connection string that is stored in azure key vault, not from application settings in azure portal.
I have stored the secret in azure portal and fetching it using the below code in Startup.cs file, but it is not working.
Below is code piece I have tried:
Startup.cs
public override void Configure(IFunctionsHostBuilder builder)
{
// Reading connection string from app settings, (but I don't want this method)
// var configuration = Environment.GetEnvironmentVariable("SQL:ConnectionString");
var configuration = AzureKeyVault.GetSecretKey();
builder.Services.AddHttpClient();
Server.Module.Load(builder.Services, configuration);
}
AzureKeyVault Class
public class AzureKeyVault
{
public static String GetSecretKey()
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVault = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secretKey = Task.Run(async () => await keyVault.GetSecretAsync("https://somesecreturl.azure.net/", "mysecretkeyname")).Result;
var connectionString = secretKey.Value;
return connectionString;
}
}
The above code works fine in local, but after deploying it is not picking the connection string from azure key vault (for which I have written code), but I am getting an error Function host is not running
When I add connection string in application settings, function app is working fine with Environment.GetEnvironmentVariable("SQL:ConnectionString")
I am a newbie in azure. Azure functions are designed in a way that it can able to read the connection string only from application settings alone?
Any help would be highly appreciated. Thanks!
Key Vault Access
Since it is working for you locally (I assume the local instance of the function uses your AZ CLI identity with your account that has access to Key Vault, because you have added that secret beforehand), I believe your issue might be connected with granting Key Vault Access for your Function App. Please make sure you have granted the access as described here: https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?tabs=azure-cli#granting-your-app-access-to-key-vault.
Key Vault References
If my assumption is correct and you are trying to hide the secret in Key Vault rather than storing it as plain-text in App Settings, you can always insert a reference like:
#Microsoft.KeyVault(VaultName=myvault;SecretName=mysecret)
as your application setting and then you will be able to read it in your application just like it would be a regular env variable:
var configuration = Environment.GetEnvironmentVariable("SQL:ConnectionString");
This will make your code much simpler and will allow you to decide in the future if you want to take configuration from another service (for example App Configuration Service) without any code changes to your app.
Note: using reference will not fix your access issue, you need to fix that first anyways.
I'm developing a Blazor Server App with VS2019. When running locally (debug or release) it is running and working fine. After publishing it to Azure App Services I get the remote certificate invalid message. At the moment I call a controller method.
Part if the razor page code is:
protected override async Task OnParametersSetAsync()
{
await Task.Run(async () => await GetExperimentInfo());
}
protected async Task GetExperimentInfo()
{
if (string.IsNullOrEmpty(eid))
{
ExperimentName = "Experiment entity not provided";
return;
}
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(NavigationManager.BaseUri);
ExpInfo = await client.GetFromJsonAsync<ExperimentInfo>("api/experiment/" + eid);
if (ExpInfo == null)
{
ExperimentName = "Error: Experiment not found";
return;
}
ExperimentName = ExpInfo.Name;
}
The 'eid' is specified as an argument calling the razor page.
When calling the controller GET method in the server app on Azure App Service directly returns the correct information.
Calling the same controller GET method from within the razor page returns the AuthenticationException of invalid remote certificate!
The method called in the controller is:
[Route("api/experiment/{eid}")]
[HttpGet]
public ExperimentInfo GetExperimentInfo(string eid)
{
var ExpInfo = GetSNExperimentData(eid);
return ExpInfo;
}
I've browsed a lot of articles on the web, but so far did not find a correct answer why and how to resolve this.
Anyone any idea or experience? Thx
The problem was solved by Microsoft Azure Support (by Kevin Kessler) with the following explanation:
This indicates that whichever Root CA is within the remote web service's certificate chain, is not trusted. This is due to the Root CA not being contained within the app service's Trusted Root store.
The Azure web app resides on an App Service Environment (ASE). In this case you may be able to resolve the issue by uploading the needed certificates and assigning their thumbprint values to the app service settings.
Please see this document, which covers the use of certificates on an ASE and how to configure on an app service:
https://learn.microsoft.com/en-us/azure/app-service/environment/certificates#private-client-certificate
Additionally, this StackOverflow article may provide further guidance:
How to make the azure app service trust the certificates issued by the OnPrem CA?
Resolution: Uploaded Root and intermediate certificates to ASE
I have written a .NET Core 3.0 console application that accesses SSM for some encrypted credentials to run a job. When I run it locally, it works fine and I can access the parameters from my account's SSM. However, when I deploy this via docker and ECS Fargate and run it as a task, it seems like it can't access the parameters. I get the following error:
Unable to get IAM security credentials from EC2 Instance Metadata Service.
This is coupled with a ParameterNotFound exception.
As I understand it,AmazonSimpleSystemsManagementClient is trying to grab the default app config and use the aws credentials from there to access other aws resources. This works fine when I run it locally since I have the AWS CLI configured and my credentials are accessible. When this runs through ECS, I guess that it can't find this config so it then tries to look for the EC2 Meta config, which may not exist in ecs?
I have attached the SSM Full access policy to the task definition's role and even the AdministratorFullAccess role and still doesn't seem to fix the exception. Am I missing something?
public static async Task<Login> GetCredentialsAsync(string parameterName)
{
var request = new GetParameterRequest()
{
Name = $"/credentials/{parameterName}",
WithDecryption = true
};
using (var client = new AmazonSimpleSystemsManagementClient())
{
try
{
var response = await client.GetParameterAsync(request);
var responseObject = JsonConvert.DeserializeObject<Login>(response.Parameter.Value);
return responseObject;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error occurred: {ex.Message}");
}
}
throw new ParameterNotFoundException($"Could not find credentials for {parameterName}");
}
As it turns out, I needed to set a Task Role for the instance in the Task Definition. I had only set an Execution Role.
I am working on the Official Azure sample: Getting started - Managing Compute Resources using Azure .NET SDK. And getting the following error on line resourceGroup = await resourceGroups.CreateOrUpdateAsync(resourceGroupName, resourceGroup); of the following code where app is trying to create a Resource Group. I have followed the instructions for Registering an app and from this link provided by the sample. And, have assigned a role to app as follows:
Error:
Azure.Identity.AuthenticationFailedException
HResult=0x80131500
Message=DefaultAzureCredential authentication failed.
Source=Azure.Identity
Inner Exception 2:
MsalServiceException: AADSTS70002: The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal
static async Task Main(string[] args)
{
var subscriptionId = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
var resourceClient = new ResourcesManagementClient(subscriptionId, new DefaultAzureCredential());
// Create Resource Group
Console.WriteLine("--------Start create group--------");
var resourceGroups = resourceClient.ResourceGroups;
var location = "westus2";
var resourceGroupName = "QuickStartRG";
var resourceGroup = new ResourceGroup(location);
resourceGroup = await resourceGroups.CreateOrUpdateAsync(resourceGroupName, resourceGroup);
Console.WriteLine("--------Finish create group--------");
// Create a Virtual Machine
await Program.CreateVmAsync(subscriptionId, "QuickStartRG", location, "quickstartvm");
// Delete resource group if necessary
//Console.WriteLine("--------Start delete group--------");
//await (await resourceGroups.StartDeleteAsync(resourceGroupName)).WaitForCompletionAsync();
//Console.WriteLine("--------Finish delete group--------");
//Console.ReadKey();
}
UPDATE:
As per instructions in the sample, following is how I Used the portal to create an Azure AD application and service principal that can access resources. I may not have done something right here. Please let me know what I am not doing right here:
Role Assignment for the registered app in Access Control (IAM):
Authentication and Direct URI:
API Permissions for the Registered App:
UPDATE-2:
Working with #JoyWan, I was able to resolve the issue (thank you Joy). Below is the screenshot of successful creation of all required compute resources including VM. NOTE: Clicking on the image would provide a better view of the screenshot.
I test the code, it works fine on my side. The steps you mentioned are also correct.
In this sample, the DefaultAzureCredential() actually uses the EnvironmentCredential() in local, so if you run the code in local, make sure you have Set Environment Variables with the AD App Client ID, Client Secret, Tenant ID.
Update:
From #nam's comment, the issue was that environment vars were not refreshed yesterday, since he had shutdown the machine yesterday and restarted it again today, the environment var got in sync and hence the app started working.
I have an MVC 5 web application running on .NET 4.7.2 and hosted in an Azure AppService, that uses Azure Key Vault to hold secrets. The project uses the Microsoft.Azure.KeyVault 3.0.3 NuGet package and the secrets are accessed using the KeyVaultClient and .GetSecretAsync(). All resources are located in the same Azure region.
For the most part this works very well, and for about 90% of the time it returns the secret in milliseconds.
But every now and then the call to access the Key Vault fails. This doesn't manifest itself as an exception thrown by the SDK, but the web app hangs. Eventually - and normally in around 1 minute but sometimes longer - the secret is returned and all is fine again. This is because the SDK uses a retry pattern, which will keep trying to get the secret.
Looking at Application Insights for the AppService I can see that the GET request generated by the SDK gets an HTTP 500 response from the Key Vault and a SocketException is thrown, with a result code of ConnectFailure.
The exception is:
Looking at the telemetry and stepping through the code there is no element of commonality or obvious cause. It seems to be entirely random.
The bottom line is the Azure hosted AppService sometimes cannot connect to an Azure hosted Key Vault in the same datacentre, using the latest framework and SDK version.
Has anyone else seen this or have any idea? I've searched around and found a few instances of people experiencing the same issue, but nobody has a cause or solution.
EDIT (1): I have now tried spinning up a new Key Vault in a different region entirely, and the problem remains exactly the same.
We experienced the same behavior on our project, where KeyVault would be fast and reliable most of the time, and then intermittently stop responding or take a very long time to return once in a while with no obvious reason to explain why. This occurred in all tiers of our application, from the API, to Azure Functions, to command line tools.
Eventually, we had to work around this by caching secrets in memory to avoid hitting the KeyVault too often, where our AppSettings class would cache these internally. In addition to this, we also configured our DI container to treat this class as a singleton.
Here is a very simplified example:
public class MyAppSettings : IAppSettings
{
private readonly ObjectCache _cache = MemoryCache.Default;
private readonly object _lock = new Object();
private KeyValueClient _kvClient;
public string MySecretValue => GetSecret("MySecretValue");
private KeyValueClient GetKeyVaultClient()
{
// Initialize _kvClient if required
return _kvClient;
}
private string GetSecret(string name)
{
lock (_lock)
{
if (_cache.Contains(key))
return (string) _cache.Get(key);
// Sanitize name if required, remove reserved chars
// Construct path
var path = "...";
// Get value from KV
var kvClient = GetKeyVaultClient();
Task<SecretBundle> task = Task.Run(async() => await kvClient.GetSecretAsync(path));
var value = task.Result;
// Cache it
_cache.Set(name, value, DateTime.UtcNow.AddHours(1));
return value;
}
}
}
This isn't production ready - you'll need to modify this and implement the GetKeyVaultClient method to actually return your KeyVaultClient object, and also the GetSecret method should sanitize the key name being retrieved.
In our DI registry, we had this setup to use a singleton like this:
For<IAppSettings>().Use<MyAppSettings>().Singleton();
These two changes seemed to work well for us, and we haven't had any issues with this for a while now.
Another option is to deploy the secrets from keyvault to your app service application as app settings in your deployment pipeline.
Pros:
Keep the secrets out of source control
Remove the runtime dependency on keyvault
Faster reliable local access to the secrets
Cons:
Updating the secrets requires a redeploy