Azure App Configuration Managed Identity failing when called from Azure Function - c#

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.

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 KeyVault Configuration Provider reload values on change

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 :)

Get AWS caller Identity with C# SDK

When I execute this with the aws cli, i.ex. inside a fargate task, I can see the UserId that my application is going to use
aws sts get-caller-identity
with this output on the console
{
"Arn": "arn:aws:sts::643518765421:assumed-role/url_process_role/6ae81f92-66f3-30de-1eaa-3a7d1902bad9",
"UserId": "ARDYOAZLVOAQXTT5ZXTV4:4ea81f97-66f3-40de-beaa-3a7d1902bad9",
"Account": "692438514791"
}
I would like to get the same information but using the C# SDK. I tried with the methods exposed in this doc but I can see some account related details but not the UserId assigned.
So far I've tried with this but I cannot see any profile when running in a Fargate task.
var awsChain = new Amazon.Runtime.CredentialManagement.CredentialProfileStoreChain();
System.Console.WriteLine($"Found {awsChain.ListProfiles().Count} AWS profiles.");
My final goal is to get it and add to some task processed with Fargate to save a correlation Id in the database when something fails and easily find the Fargate log stream.
IAmazonSecurityTokenService will provide the same information when executed with .netcore. Notice that the above example will only work inside the AWS domain as the endpoint is not publicly available if testing from a development machine.
var getSessionTokenRequest = new GetSessionTokenRequest
{
DurationSeconds = 7200
};
var stsClient = hostContext.Configuration.GetAWSOptions().CreateServiceClient<IAmazonSecurityTokenService>();
var iden = stsClient.GetCallerIdentityAsync(new GetCallerIdentityRequest { }).Result;
System.Console.WriteLine($"A={iden.Account} ARN={iden.Arn} U={iden.UserId}");

Azure Datalake operation returned an invalid status code forbidden

I am writing a simple file to Azure Datalake to learn how to use this for other means, but I ma having issues and when I try to write I get the following error message
21/5/2018 9:03:27 AM] Executed 'NWPimFeederFromAws' (Failed, Id=39adba4b-9c27-4078-b560-c25532e8432e)
[21/5/2018 9:03:27 AM] System.Private.CoreLib: Exception while executing function: NWPimFeederFromAws. Microsoft.Azure.Management.DataLake.Store: One or more errors occurred. (Operation returned an invalid status code 'Forbidden'). Microsoft.Azure.Management.DataLake.Store: Operation returned an invalid status code 'Forbidden'.
The code in question is as follows
static void WriteToAzureDataLake() {
// 1. Set Synchronization Context
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
// 2. Create credentials to authenticate requests as an Active Directory application
var clientCredential = new ClientCredential(clientId, clientSecret);
//var creds = ApplicationTokenProvider.LoginSilentAsync(tenantId, clientCredential).Result;
var creds = ApplicationTokenProvider.LoginSilentAsync(tenantId, clientCredential).Result;
// 2. Initialise Data Lake Store File System Client
adlsFileSystemClient = new DataLakeStoreFileSystemManagementClient(creds);
// 3. Upload a file to the Data Lake Store
var source = "c:\\nwsys\\source.txt";
var destination = "/PIMRAW/destination.txt";
adlsFileSystemClient.FileSystem.UploadFile(adlsAccountName, source, destination, 1, false, true);
// FINISHED
Console.WriteLine("6. Finished!");
}
I have added the application from my Azure AD to the access list on that specific folder I am trying to write to as follows
The clientID and clientSecret in my code comes from this app so I am a bit lost as to why I get forbidden.
Have I forgotten anything else?
Could it be that the loginAsync has not yet finished before I try and create the client?
Did you give your application/service principal execute access to the parent folders in the path to the specific folder to which you're app is writing? This is needed to travers the folder path, see here for some examples: https://learn.microsoft.com/en-us/azure/data-lake-store/data-lake-store-access-control#common-scenarios-related-to-permissions.
Could it be that the loginAsync has not yet finished before I try and create the client?
It is not related to loginAsync.
Based on my test, it works correctly on my side if I assign the permissions to the folder.
Test Result:
If it is possible, you could create a new Datalake account or new folder and try it again. I recommand that you could use fiddler to capture the detail information about exception.
Not an answer, just documenting what I found when faced with a similiar error.
I added my Azure Data Factory Managed Identity to the contributor role at the account level (and therefore file system) level.
When trying to create blobs from ADF I got a forbidden error
So I added it to Storage Blob Data Contributor. It didn't work immediately but took about 10 minutes to be recognised. Then everything worked.

LDAP search fails on server, not in Visual Studio

I'm creating a service to search for users in LDAP. This should be fairly straightforward and probably done a thousand times, but I cannot seem to break through properly. I thought I had it, but then I deployed this to IIS and it all fell apart.
The following is setup as environment variables:
ldapController
ldapPort
adminUsername 🡒 Definitely a different user than the error reports
adminPassword
baseDn
And read in through my Startup.Configure method.
EDIT I know they are available to IIS, because I returned them in a REST endpoint.
This is my code:
// Connect to LDAP
LdapConnection conn = new LdapConnection();
conn.Connect(ldapController, ldapPort);
conn.Bind(adminUsername, adminPassword);
// Run search
LdapSearchResults lsc = conn.Search(
baseDn,
LdapConnection.SCOPE_SUB,
lFilter,
new string[] { /* lots of attributes to fetch */ },
false
);
// List out entries
var entries = new List<UserDto>();
while (lsc.hasMore() && entries.Count < 10) {
LdapEntry ent = lsc.next(); // <--- THIS FAILS!
// ...
}
return entries;
As I said, when debugging this in visual studio, it all works fine. When deployed to IIS, the error is;
Login failed for user 'DOMAIN\IIS_SERVER$'
Why? The user specified in adminUsername should be the user used to login (through conn.Bind(adminUsername, adminPassword);), right? So why does it explode stating that the IIS user is the one doing the login?
EDIT I'm using Novell.Directory.Ldap.NETStandard
EDIT The 'user' specified in the error above, is actually NOT a user at all. It is the AD registered name of the computer running IIS... If that makes any difference at all.
UPDATE After consulting with colleagues, I set up a new application pool on IIS, and tried to run the application as a specified user instead of the default passthrough. Exactly the same error message regardless of which user I set.
Try going via Network credentials that allows you to specify domain:
var networkCredential = new NetworkCredential(userName, password, domain);
conn.Bind(networkCredential);
If that does not work, specify auth type basic (not sure that the default is) before the call to bind.
conn.AuthType = AuthType.Basic;

Categories