Azure KeyVault Configuration Provider reload values on change - c#

I'm using Azure Key Vault Configuration Provider to read some secrets at app startup. The secrets however keep rotating throughout the day and I want to be able to reload the new values when this rotation happens.
What I'm talking about is similar to the reloadOnChange api
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", reloadOnChange: true);
})
Is this possible at all?
This is a webapi project so in practice, I could get away with manually reloading the values for every HttpRequest if that's better/more feasibe.

Using Microsoft.Extensions.Configuration.AzureKeyVault (v3) you can do the following:
configurationBuilder.AddAzureKeyVault(new AzureKeyVaultConfigurationOptions
{
Vault = configuration["KeyVaultUrl"],
ReloadInterval = TimeSpan.FromMinutes(10),
Client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(
new AzureServiceTokenProvider().KeyVaultTokenCallback))
});
Now when you request for IConfiguration in your services, the KeyVault secrets will be available and refreshed based on your reload interval.

Secrets are cached until IConfigurationRoot.Reload() is called. Expired, disabled, and updated secrets in the key vault are not respected by the app until Reload is executed.
Configuration.Reload();
For more details, you could refer to this article.

Same thing as Bobby Koteski proposed, but with a newer Azure.Extensions.AspNetCore.Configuration.Secrets package, as Microsoft.Extensions.Configuration.AzureKeyVault is deprecated.
ReloadInterval is a time to wait between attempts at polling the Azure Key Vault for changes.
configurationBuilder.AddAzureKeyVault(
new SecretClient(
new Uri(configuration["KeyVaultBaseUrl"]),
new ManagedIdentityCredential(configuration["UserAssignedManagedIdentityClientId"])
),
new AzureKeyVaultConfigurationOptions()
{
ReloadInterval = TimeSpan.FromSeconds(1000)
}
);
And a link to a source code to see how it actually works :)

Related

C# ASP.NET MVC Use of Azure Key Vault Runtime Initialized Variables

I am looking for the most appropriate way to store and/or use variables initialized during startup (Program.cs) throughout the application as needed, or an acceptable alternative process if there's a better way to accomplish this.
I.e., the following code snippet in Program.cs initializes the Azure Key Vault connectionString variable at runtime with a correct value retrieved from the designated Azure Key Vault:
var keyVaultUrl = builder.Configuration.GetValue<string>("KeyVault:KeyVaultUrl");
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
SecretClientOptions options = new SecretClientOptions()
{
Retry =
{
Delay= TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(16),
MaxRetries = 5,
Mode = RetryMode.Exponential
}
};
var client = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential(), options);
KeyVaultSecret secretConnectionString = client.GetSecret("ConnectionString");
string connectionString = secretConnectionString.Value;
}
The objective is to use this variable or others on-demand without having to call the code another time. Any thoughts are appreciated.
Check the below steps to store the KeyVault Connection string in Azure Appsettings.
I do agree with #Dai, yes instead of getting the ConnectionString/Varaibles from KeyVault , we can store the values in Azure App Service => Configuration => Application Settings.
My appsettings.json
"ConnectionStrings": {
"MyVal": "DummyString"
}
Create a secret in KeyVault and copy the Secret Identifier.
Azure App Settings
Thanks to #Jayant Kulkarni - reference taken from c-sharpcorner.

Azure App Configuration Managed Identity failing when called from Azure Function

I'm following Use managed identities to access app configuration. For step 5 i've assigned my function FilterFunction as havingthe App Configuration Data Reader role:
The code for my function on startup is the following:
var appConfigEndpoint = Environment.GetEnvironmentVariable("Endpoint");
var environment = Environment.GetEnvironmentVariable("Environment");
var sentinelValue = Environment.GetEnvironmentVariable("ConfigSentinelKey");
builder.ConfigurationBuilder.AddAzureAppConfiguration(options =>
{
// Load the configuration using labels
options.Connect(new Uri(appConfigEndpoint), new ManagedIdentityCredential())
.ConfigureRefresh(refreshoptions => refreshoptions.Register(
key: sentinelValue,
label: environment,
true))
.Select(KeyFilter.Any, environment);
});
However when i publish my function to Azure i see the following error:
Why am i getting this error?
Once a role is assigned to grant access to Azure App Configuration. It may take up to ~15 minutes to propagate. During this time, the error message you provided will be observed.
This is especially true if the identity is first used to make a request without having the role assignment (resulting in 403) and then the role is added afterward.

Azure keyvault from on prem .net app without exposing clientid/clientsecret?

I've registered my app in azure AD and created a client secret and then created a vault and added a secret for the dbconnectionstring below. It works ok but I need the "client-id" and "client-secret" since the identity is managed as service principal. Is there a way to get thos values through an API so that my app doesn't have to save those in the config? It's kind of defeating the purpose since thos whole exercise was to avoid having to save connection strings in the web.config/appsettings.json; as now I can save those in the vault but I would need to save the clientid/secret in the config.
var kvClient = new KeyVaultClient(async (authority, resource, scope) =>
{
var context = new AuthenticationContext(authority);
var credential = new ClientCredential("client-id", "client-secret");
AuthenticationResult result = await context.AcquireTokenAsync(resource, credential);
return result.AccessToken;
});
try
{
var connStrENTT = kvClient.GetSecretAsync("https://myvault.vault.azure.net/", "DBConfigConnection").Result.Value;
}
Why do you need to acquire token via your code if you are using managed identity? Managed identity is supposed to hide this for you.
Please use the guidance provided in a sample like this to take the correct steps.

Identity Server 4 - IDX10630: PII is hidden

I'm fairly new to using encryption and rsa tokens and I'm trying to get IDentityServer4 to not use the developersigning, but one of my own. Here is what I have tried so far:
var keyInfo = new RSACryptoServiceProvider().ExportParameters(true);
var rsaSecurityKey = new RsaSecurityKey(new RSAParameters
{
D = keyInfo.D,
DP = keyInfo.DP,
DQ = keyInfo.DQ,
Exponent = keyInfo.Exponent,
InverseQ = keyInfo.InverseQ,
Modulus = keyInfo.Modulus,
P = keyInfo.P,
Q = keyInfo.Q
});
services.AddIdentityServer()
.AddSigningCredential(rsaSecurityKey)
.AddInMemoryPersistedGrants()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<User>();
However, when I run Identity Server4 and I get redirected to sign in page from another website, I get the following error:
IDX10630: The '[PII is hidden]' for signing cannot be smaller than '[PII is hidden]' bits. KeySize: '[PII is hidden]'.
Parameter name: key.KeySize
I have to admit, I've been on this all weekend, trying to figure out how to use SigningCredentials and I'm not really sure what I've done wrong above.
You can see more details in development by adding the following to Configure() in the Startup class:
if (env.IsDevelopment())
{
IdentityModelEventSource.ShowPII = true;
}
For those who are having the same problem:
The ShowPII configuration is set globally, it's a static property of IdentityModelEventSource and can be set in the Startup class, for example. Once I added it I could see that it was throwing a InvalidIssuer exception for token validation. For me it was related to how I was generating the JWT to communicate with my API (which is protected with Identity Server 4). I was generating the token over the url: http://localhost:5002(out side of docker-compose network) which is different them the url Identity Server issuer inside my API: http://<<docker-service-name>>. So, if you are using docker-compose and manage to use your Identity Server as a separated container inside the same docker-compose, be aware that your authentication should generate a token with IDENTICAL issuer that is used in your API.

IDX21323 OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocolValidatedIdToken.Payload.Nonce was not null

I'm attempting to authenticate for Azure AD and Graph for an Intranet (Based off Orchard CMS), this functions as expected on my local machine, however, when accessing what will be the production site (already set up with ssl on our internal dns), I get the above error at times, it's relatively inconsistent, others in my department while accessing usually get this error.
My Authentication Controller is as follows:
public void LogOn()
{
if (!Request.IsAuthenticated)
{
// Signal OWIN to send an authorization request to Azure.
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
public void LogOff()
{
if (Request.IsAuthenticated)
{
ClaimsPrincipal _currentUser = (System.Web.HttpContext.Current.User as ClaimsPrincipal);
// Get the user's token cache and clear it.
string userObjectId = _currentUser.Claims.First(x => x.Type.Equals(ClaimTypes.NameIdentifier)).Value;
SessionTokenCache tokenCache = new SessionTokenCache(userObjectId, HttpContext);
HttpContext.GetOwinContext().Authentication.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
SDKHelper.SignOutClient();
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
My openid options are configured as follows:
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
var openIdOptions = new OpenIdConnectAuthenticationOptions
{
ClientId = Settings.ClientId,
Authority = "https://login.microsoftonline.com/common/v2.0",
PostLogoutRedirectUri = Settings.LogoutRedirectUri,
RedirectUri = Settings.LogoutRedirectUri,
Scope = "openid email profile offline_access " + Settings.Scopes,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async (context) =>
{
var claim = ClaimsPrincipal.Current;
var code = context.Code;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new SessionTokenCache(signedInUserID,
context.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(
Settings.ClientId,
Settings.LogoutRedirectUri,
new ClientCredential(Settings.AppKey),
userTokenCache,
null);
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Settings.SplitScopes.ToArray());
},
AuthenticationFailed = (context) =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
};
var cookieOptions = new CookieAuthenticationOptions();
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(cookieOptions);
app.UseOpenIdConnectAuthentication(openIdOptions);
The url for redirection is kept consistent both at apps.dev.microsoft.com and in our localized web config.
In my case, this was a very weird problem because it didn't happen in for everyone, only few clients and devs have this problem.
If you are having this problem in chrome only (or a browser that have the same engine) you could try setting this flag on chrome to disabled.
What happens here is that chrome have this different security rule that " If a cookie without SameSite restrictions is set without the Secure attribute, it will be rejected". So you can disable this rule and it will work.
OR, you can set the Secure attribute too, but I don't know how to do that ;(
How to solve IDX21323
The problem is solved with this lines of codes, the reason of the error was that ASP.NET don't has the sessiĆ³n info created yet. The function "authFailed.OwinContext.Authentication.Challenge()" fill the header with the info that needs for the authentication.
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = AuthenticationFailedNotification<OpenIdConnect.OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> authFailed =>
{
if (authFailed.Exception.Message.Contains("IDX21323"))
{
authFailed.HandleResponse();
authFailed.OwinContext.Authentication.Challenge();
}
await Task.FromResult(true);
}
}
});
Check the URL mentioned in the AD App Registrations --> Settings --> Reply URL's. if for example that url is https://localhost:44348/
Go to MVC Project --> Properties (Right Click and Properties) --> Web Section --> Start URL and Project URL should also be https://localhost:44348/
This has resolved the issue for me. other option is to dynamically set the Redirect URL after AD authentication in Startup.Auth
See System.Web response cookie integration issues by Chris Ross (AKA Tratcher on github). The OWIN cookie manager and the original cookie management built into ASP.NET Framework can clash in an unhelpful way, and there is no universal solution to this. However, in setting up OIDC authentication I found this suggested work-around from that link worked for me:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
And:
OpenIdConnectAuthenticationOptions.CookieManager = new SystemWebCookieManager();
This causes OWIN to use the ASP.NET Framework cookie jar/store and avoid the clash. I imagine there will be side effects to his, so tread carefully! Read the link for a full explanation.
With it being inconsistent, it makes me believe the error you are seeing is caused by what people call "Katana bug #197".
Luckily, there is a workaround with a nuget package called Kentor.OwinCookieSaver.
After installing the nuget package add app.UseKentorOwinCookieSaver(); before app.UseCookieAuthentication(cookieOptions);.
For more info, checkout the Kentor.OwinCookieSaver repo on GitHub.
Also check this link:
https://learn.microsoft.com/en-us/aspnet/samesite/owin-samesite
For me it didn't work at first but the solution was to use https. I used Visual Studio IIS Express which hosts a website default using http. In test it worked because of https.
I've got the same error in production environment while locally it worked for all development team. I've tried Kentor.OwinCookieSaver solution suggested by Michael Flanagan but it did not help. After digging a little bit I discovered that authentication itself completed successfully and OwinContext contains user identity and claims, but AuthenticationFailed event handler is raised with IDX21323 exception. So I decided to use the following workaround - I updated AuthenticationFailed event handler:
// skip IDX21323 exception
if (context.Exception.Message.Contains("IDX21323"))
{
context.SkipToNextMiddleware();
} else {
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
}
return Task.FromResult(0);
This way system will not throw IDX21323 exception but continues auth process and allows users to login and use the system.
I know this not a solution, but at least users can now login until I find a better way to solve this issue.
My start and project URL's were different than the Redirect URI in Azure. I made all these match and no longer get IDX2132 error.
I needed to switch my local project to use HTTPS. I changed IISExpress to https:// and port to :443__ in the project settings.
Azure problems?
Make sure you are using the right domain name. I was debugging some firewall problems on Azure and I was using my-subdomain.azurewebsites.net to see if the site itself was up, and once it was, I forgot to change the domain name back to my-subdomain.mydomainname.org.
In my case, we were standing up new non-prod environments with very similar configs but one environment was throwing this error. It turned out that it was an issue with IIS configuration. After installing the HTTP Redirection role on the server and restarting, everything worked fine.
This can be configured by
Opening up the Server Manager
Click "Add roles and features" below "Configure this local server"
Continue through setup pages to get to server roles. "Before You Begin" -> "Installation Type" -> "Server Selection" -> "Server Roles"
Under Roles expand "Web Server (IIS)" -> "Web Server" -> "Common HTTP Features"
Check "HTTP Redirection"
Complete installation and restart server.
This is probably a niche answer as I'm guessing it's related to using URL rewrite/redirect rules specified in a web.config, but it took me forever to track down the issue. Hope it helps someone!

Categories