I am trying to retrieve secrets from Azure Key Vault using Service Identity in an ASPNet 4.6.2 web application. I am using the code as outlined in this article. Locally, things are working fine, though this is because it is using my identity. When I deploy the application to Azure I get an exception when keyVaultClient.GetSecretAsync(keyUrl) is called.
As best as I can tell everything is configured correctly. I created a User assigned identity so it could be reused and made sure that identity had get access to secrets and keys in the KeyVault policy.
The exception is an AzureServiceTokenProviderException. It is verbose and outlines how it tried four methods to authenticate. The information I'm concerned about is when it tries to use Managed Service Identity:
Tried to get token using Managed Service Identity. Access token could
not be acquired. MSI ResponseCode: BadRequest, Response:
I checked application insights and saw that it tried to make the following connection with a 400 result error:
http://127.0.0.1:41340/MSI/token/?resource=https://vault.azure.net&api-version=2017-09-01
There are two things interesting about this:
Why is it trying to connect to a localhost address? This seems wrong.
Could this be getting a 400 back because the resource parameter isn't escaped?
In the MsiAccessTokenProvider source, it only uses that form of an address when the environment variables MSI_ENDPOINT and MSI_SECRET are set. They are not set in application settings, but I can see them in the debug console when I output environment variables.
At this point I don't know what to do. The examples online all make it seem like magic, but if I'm right about the source of the problem then there's some obscure automated setting that needs fixing.
For completeness here is all of my relevant code:
public class ServiceIdentityKeyVaultUtil : IDisposable
{
private readonly AzureServiceTokenProvider azureServiceTokenProvider;
private readonly Uri baseSecretsUri;
private readonly KeyVaultClient keyVaultClient;
public ServiceIdentityKeyVaultUtil(string baseKeyVaultUrl)
{
baseSecretsUri = new Uri(new Uri(baseKeyVaultUrl, UriKind.Absolute), "secrets/");
azureServiceTokenProvider = new AzureServiceTokenProvider();
keyVaultClient = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
}
public async Task<string> GetSecretAsync(string key, CancellationToken cancellationToken = new CancellationToken())
{
var keyUrl = new Uri(baseSecretsUri, key).ToString();
try
{
var secret = await keyVaultClient.GetSecretAsync(keyUrl, cancellationToken);
return secret.Value;
}
catch (Exception ex)
{
/** rethrows error with extra details */
}
}
/** IDisposable support */
}
UPDATE #2 (I erased update #1)
I created a completely new app or a new service instance and was able to recreate the error. However, in all instances I was using a User Assigned Identity. If I remove that and use a System Assigned Identity then it works just fine.
I don't know why these would be any different. Anybody have an insight as I would prefer the user assigned one.
One of the key differences of a user assigned identity is that you can assign it to multiple services. It exists as a separate asset in azure whereas a system identity is bound to the life cycle of the service to which it is paired.
From the docs:
A system-assigned managed identity is enabled directly on an Azure service instance. When the identity is enabled, Azure creates an identity for the instance in the Azure AD tenant that's trusted by the subscription of the instance. After the identity is created, the credentials are provisioned onto the instance. The lifecycle of a system-assigned identity is directly tied to the Azure service instance that it's enabled on. If the instance is deleted, Azure automatically cleans up the credentials and the identity in Azure AD.
A user-assigned managed identity is created as a standalone Azure resource. Through a create process, Azure creates an identity in the Azure AD tenant that's trusted by the subscription in use. After the identity is created, the identity can be assigned to one or more Azure service instances. The lifecycle of a user-assigned identity is managed separately from the lifecycle of the Azure service instances to which it's assigned.
User assigned identities are still in preview for App Services. See the documentation here. It may still be in private preview (i.e. Microsoft has to explicitly enable it on your subscription), it may not be available in the region you have selected, or it could be a defect.
To use a user-assigned identity, the HTTP call to get a token must include the identity's id.
Otherwise it will attempt to use a system-assigned identity.
Why is it trying to connect to a localhost address? This seems wrong.
Because the MSI endpoint is local to App Service, only accessible from within the instance.
Could this be getting a 400 back because the resource parameter isn't escaped?
Yes, but I don't think that was the reason here.
In the MsiAccessTokenProvider source, it only uses that form of an address when the environment variables MSI_ENDPOINT and MSI_SECRET are set. They are not set in application settings, but I can see them in the debug console when I output environment variables.
These are added by App Service invisibly, not added to app settings.
As for how to use the user-assigned identity,
I couldn't see a way to do that with the AppAuthentication library.
You could make the HTTP call manually in Azure: https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http.
Then you gotta take care of caching yourself though!
Managed identity endpoints can't handle a lot of queries at one time :)
Related
I have created a web api that handles the creation of jwt token based on the encrypted user details that it receives in a post request.
In addition to this STS api should also handle the population of the caching layer (Redis or Hazelcast) with all the user data present in the database. Presently I have registered the caching service using dependency injection.This will happen only once when the api is first initialized.
services.AddSingleton<ICacheService, RedisCacheService>();
And in the TokenController added the service as a parameter to initialize the CachingService class and thereby initialize the caching layer.So that when the cacheService object is fist initialized it fetches all the user rows from the database and stores it as a key value pair inside Redis/Hazelcast database.
public TokenController(
ICryptographyService cryptographyService,
crudDBContext crudDBContext,
IConfiguration configuration,
ICacheService cacheService)
{
_cryptographyService = cryptographyService;
_context = crudDBContext;
_config = configuration;
_cacheService = cacheService;
}
But the Token Controller constructor is initialized only when an endpoint is called, so i had to create a separate default [HttpGet] endpoint to ensure that the constructor is called when the STS api is first initialized so as to ensure that the cacheService object gets created and the data gets loaded to the cache.
public ActionResult<string> Get()
{
return "STS";
}
Please let me know if there is a proper way of doing this without calling an endpoint, like be able to use dependency injection but at the same time call some code without the endpoint being called.I need to use dependency injection because i should be able to switch between Redis and Hazelcast by just changing the classname in the startup.cs file.
With respect to Hazelcast and dependency injection: First you would need to use the sources and not the Hazelcast NuGet version. Next the configuration depends on if you are in a Container Environment or a Hosted Environment. In both cases configuration keys will be gathered from the same sources and in the same order, and options will be registered in the service container, and available via dependency injection
We're in the process of trying to secure our application secrets in our internal ASP.NET Framework web applications. The initial plan offered to me was to use Azure Key Vault. I began development work using my Visual Studio Enterprise subscription, and that seems to work fine, locally.
We've created a second Key Vault in our company's production environment, and again, I can use it locally, because my own AAD account has access to the vault. However, in this project (4.7.2 Web Forms web application), I don't see any means of specifying the Access Policy principal that we've created for the application.
My google-fu is failing me: is there any documentation that explains how to do this? Is this scenario -- an on-prem, ASP.NET Framework app outside of the Azure environment, accessing Key Vault for confiugation values -- even possible?
Thanks.
UPDATE: I was unable to find a solution that would allow me to use the Access Policy principal from within the "Add Connected Service" dialog. I'm somewhat surprised it's not in there, or is hidden enough to elude me. So I ended up writing my own Key Vault Secret-Reader function, similar to the marked answer. Hope this helps someone...
In this scenario, your option is to use the service principal to access the keyvault, please follow the steps below, my sample get the secret from the keyvault.
1.Register an application with Azure AD and create a service principal.
2.Get values for signing in and create a new application secret.
3.Navigate to the keyvault in the portal -> Access policies -> add the correct secret permission for the service principal.
4.Then use the code below, replace the <client-id>, <tenant-id>, <client-secret> with the values got before.
using System;
using Microsoft.Azure.KeyVault;
using Microsoft.Azure.Services.AppAuthentication;
namespace test1
{
class Program
{
static void Main(string[] args)
{
var azureServiceTokenProvider = new AzureServiceTokenProvider("RunAs=App;AppId=<client-id>;TenantId=<tenant-id>;AppKey=<client-secret>");
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = kv.GetSecretAsync("https://keyvaultname.vault.azure.net/", "mySecret123").GetAwaiter().GetResult();
Console.WriteLine(secret);
}
}
}
I was working on a project which required me to create Keyvault reference in azure AppConfiguration, add/update secrets in KeyVault and to access values in AppConfiguration using Configuration.
Currently, I'm using :
-ConfigurationClient to create key Vault reference.
-SecretClient to add/update secrets in KeyVault.
-Configuration build using the builder.AddAzureAppConfiguration().build() to access values in AppConfiguration.(using builder.AddAzureAppConfiguration() is a necessity due to its features).
So, basically 3 connections to azure are made here. Is there any way to decrease the no. of connections? Like, using the ConfigurationBuilder to get a ConfigurationClient and/or SecretClient.
Since your application is accessing two different resources, App Configuration and Key Vault, a minimum of two connections are needed. This is due to lack of support for shared connections across different services.
Assuming your application is using ConfigureKeyVault to access Key Vault references, the call to AddAzureAppConfiguration().Build() is actually creating two connections - one to App Configuration and the other to Key Vault. In this case, there are a total of 4 connections. You can reduce it to 3 by registering the SecretClient you created to add/update secrets in Key Vault in the AddAzureAppConfiguration method.
SecretClient secretClient = new SecretClient(new Uri("http://my-keyvault-uri"), new DefaultAzureCredential());
builder.AddAzureAppConfiguration(options =>
{
options.Connect(settings["connection_string"])
.ConfigureKeyVault(kv => kv.Register(secretClient));
});
At this time, there isn't a supported way to provide an existing instance of ConfigurationClient while setting up the AddAzureAppConfiguration method, but this may be supported in the future.
I have a problem with ASP.Net Identity used in application scaled to two instances. It seems that we have problem with checking password (checking hashes?) or verifying generated tokens (for example: password reset). When application works on one instance, then everything seems to be fine. It's weird because I read that using ASP.Net Identity in Azure cloud should be safe - it should use the same machine key on both instances.
Our user manager use token provider created below:
public DataProtectorTokenProvider<ExternalUser> Create(string purpose = "GeneralPurpose")
{
var provider = new DpapiDataProtectionProvider(_appName);
var result = new DataProtectorTokenProvider<ExternalUser>(provider.Create(purpose));
return result;
}
What is more, our user manager is singleton, but I think it shouldn't be a difference.
Any idea why it doesn't work on two instances in Azure? I will appreciate any help or advice.
I have Wp8.1 Silverlight app that receives push notification (WNS) from Mobileservice (the old azure service).
I therefore wanted to update to the new service because of the new features. I have now created/upgraded a new server to use App Service - Mobile App. And tested push notification with the sample app from azure (everything works).
Going back to my app WP8.1 -> Adding the new package Microsoft.Azure.Mobile.Client through NuGet (2.0.1), there is the issue that the Microsoft.WindowsAzure.Mobile.Ext does not contain the 'GetPush' extension. It seems like it is missing it? looking to the WP8 version, it only registers to MPNS, and I need WNS. So I do not know if any other assembly could be used.
Can I add another assembly reference?
Update
The following code lets me register the device on the server, and I can see the device register correctly. where the channelUri and the installationInformation are retrieved by the client and send to the server.
Installation ins = new Installation();
ins.Platform = NotificationPlatform.Wns;
ins.PushChannel = uTagAndChan.ChannelUri;
ins.Tags = uTagAndChan.Tags;
ins.InstallationId = uTagAndChan.installationInformation;
await hubClient.CreateOrUpdateInstallationAsync(ins);
Sending a test toast-notification to the registered tags, results in the following error :
The Token obtained from the Token Provider is wrong
Searching on this issue I found Windows Store App Push Notifications via Azure Service Bus. Which the proposed solution says to register to the notification hub directly from the app, I would rather not have the app to have directly access to the hub. But is this the only way? (mind you the answer was not accepted, but I will try it all though it is not a desired solution)
Update
Registering for notifications via client (WP8.1 Silverligt), makes a registration to MPNS, which I do not want.
The snippet on the server registers a WNS, the two registrations can be seen here:
The URI retrieval is done using
var channel = await Windows.Networking.PushNotifications.PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
which in the description states it returns a WNS. This seems to infer that the registration I am doing on the server (code snippet in the top) is correct and the registration on the client is faulty.
But the registration on the image seems wrong. Shouldn't the PNS Identifier be different for the two registrations? also expiration date seems wrong ?
How to mend this since the GetPush() (which was available in the sample registered the client correctly for notifications) does not exist in the NuGet package?
Update
I read one place that deleting and recreating the NotificationHub could help. I will try this today. Even IF it works, it would be more desirable to have the solution, and to know if the registrations are done correctly?
Temporary solution:
Deltede, recreated, inserted Package SID and Secret. And it works again (strange)!
Still interested in the underlying issue!
Deleted and recreated the service, setting all the same settings made it work again.
I had same issue with my UWP. But in my case I had issue with self signed certificate.
When I set the AppxPackageSigningEnabled property to True (in .csproj) then notifications stopped working and I got "The token obtained from the Token Provider is wrong" (Test send from Azure Portal).
The certificate must have same issuer as Publisher in Identity element in .appxmanifest file.