Log4net ADOAppender use Azure MSI connection instead of plain connection string - log4net

Hi I posted this to the log4net user group but thought I'd post it here as well.
I'm working on a project that requires Azure MSI to connection from Azure PaaS to Azure SQL. Wondering if log4net’s ADOAppender supports this already connection mechanism already. From what I can tell it doesn't but thought I'd ask the community before extending log4net.
To support MSI apps can’t connect to a database with a connectionstring alone, they need to get an access token from Azure and then set the AccessToken property on the SqlConnection object. Like the code below is doing:
private static SqlConnection GetSqlConnection()
{
var sqlConnection = new SqlConnection(GetConnectionString());
if (sqlConnection.DataSource != "(localdb)\\MSSQLLocalDB")
sqlConnection.AccessToken = new AzureServiceTokenProvider()
.GetAccessTokenAsync("https://database.windows.net/").Result;
return sqlConnection;
}
This code is using two Microsoft nuget packages to get the access token.
Thanks!

Related

Azure.Messaging.ServiceBus Create a ServiceBusClient using a System Assigned Managed Identity

I'm migrating a servicebus client application from Microsoft.Azure.ServiceBus to use the current library Azure.Messaging.ServiceBus.
The application is a Worker Process running on a virtual machine in windows azure.
The VM has a system assigned managed identity which grants it access to service bus and we have been using it successfully with the old library for over a year.
On the old library we created a client using this connection string
Endpoint=sb://MyNamespace.servicebus.windows.net/;Authentication=Managed Identity
When I put that connection string into the constructor of Azure.Messaging.ServiceBus.ServiceBusClient I get the following error
The connection string used for an Service Bus client must specify the Service Bus namespace host and either a Shared Access Key (both the name and value) OR a Shared Access Signature to be valid. (Parameter 'connectionString')
I've been trawling through documents for some time now with no progress.
Is there anyway to make this work?
Ideally I would continue to use the connection string - developer machines do not have system assigned ID's so we develop with key based connection strings and let devops swap in the correct prod connection string.
UPDATE
Following on from Jesse's answer managed identity has to go trough a separate constructor which requires a namespace instead of an endpoint and an instance of ManagedIdentityCredential.
As I mentioned not all environments where we deploy have managed aged identities, some require a SharedAccessKey based connection string.
Instead introducing new "identity type" configuration parameters into our build process I've used a factory method to parse the connection string and call the correct constructor overload. Where its a managed identity It extracts the namespace from the endpoint setting.
I Hope its useful for others
private static ServiceBusClient CreateServiceBusClient(string connectionString)
{
var cs = new DbConnectionStringBuilder();
cs.ConnectionString = connectionString;
if (cs.ContainsKey("Authentication") &&
"Managed Identity".Equals(cs["Authentication"].ToString(), StringComparison.OrdinalIgnoreCase))
{
string endpoint = cs["Endpoint"].ToString() ?? String.Empty;
if (endpoint.StartsWith(#"sb://", StringComparison.OrdinalIgnoreCase)) endpoint = endpoint.Substring(5);
if (endpoint.EndsWith(#"/")) endpoint = endpoint.Substring(0, endpoint.Length - 1);
return new ServiceBusClient(endpoint, new ManagedIdentityCredential());
}
return new ServiceBusClient(connectionString);
}
it needs the Azure.Identity package and the namespace System.Data.Common for the connection string builder.
The clients in the Azure.Messaging.ServiceBus package support connection strings only in the format that the Azure portal returns them.
The ;Authentication=Managed Identity token that you've included in your connection string is not a known token and is ignored, so the client does not have the information needed to perform authorization. A managed identity cannot be specified via connection string.
To use a managed identity, you'll use one of the constructor overloads that accepts a fully qualified namespace and a TokenCredential. An example can be found in the package Overview docs. Any of the Azure.Identity credentials can be used; you may want to take take a look at the managed identity section of the Azure.Identity overview.

Service Bus Connection String using the Azure.ServiceBus.Messaging C# SDK for SAS token

I am trying to migrate from the Legacy Azure service bus SDK to the new one "Azure.Messaging.ServiceBus". But it seems many of the functionalities that were there in the old ones are not supported. How do I generate a service bus connection string using a given Sas Token? The older one could it do with the "ServiceBusConnectionBuilder". (https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.servicebusconnectionstringbuilder.-ctor?view=azure-dotnet#Microsoft_Azure_ServiceBus_ServiceBusConnectionStringBuilder__ctor_System_String_)
. How can I do the same with the latest SDK?
To use a SAS token, we recommend that you construct the ServiceBusClient using the overload that accepts an AzureSasCredential rather than a connection string. This has the advantage of allowing the SAS Token to be updated without needing to destroy and recreate the client.
For example:
var credential = new AzureSasCredential("<< SHARED ACCESS KEY STRING >>");
var fullyQualifiedNamespace = "<< NAMESPACE (likely similar to {your-namespace}.servicebus.windows.net) >>";
await using var client = new ServiceBusClient(fullyQualifiedNamespace, credential);
// ...
Though we do not recommend or publicize it, the SharedAccessSignature token is supported in a connection string, resulting in something like:
"Endpoint=sb://<<namespace-name>>.servicebus.windows.net;SharedAccessSignature=<<SAS TOKEN>>"

Unable to connect to Azure Key Vault from Azure Web App

I am trying to access Azure Key Vault from my Azure App Service. I followed the steps outlined on this documentation: https://learn.microsoft.com/en-us/azure/key-vault/managed-identity (turned on system assigned identity for the app service, updated the access policy of the key vault to include the app with Get,List secret permissions).
However, when I run the web application, it is not able to get the secret from the key vault and my web service hits the following error:
502 - Web server received an invalid response while acting as a gateway or proxy server.
There is a problem with the page you are looking for, and it cannot be displayed. When the Web server (while acting as a gateway or proxy) contacted the upstream content server, it received an invalid response from the content server.
This is what my code looks like:
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
KeyVaultClient keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = keyVaultClient.GetSecretAsync(KeyVaultUrl);
authenticationKey = secret.Result.Value;
The service gets stuck on the secret.Result.Value line. Is there something else I need to do?
This is much easier with the new package, like Azure.Security.KeyVault.Secrets. Together with Azure.Identity, you can just pass a DefaultAzureCredential like in our samples.
var client = new SecretClient(
new Uri("https://myvault.vault.azure.net"),
new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync("secret-name");
string authenticationKey = secret.Value;
The DefaultAzureCredential is optimized to work for managed identity, service principals from the environment, and interactive logins to support the same code running both in production and on developer machines. The new libraries are also faster with fewer allocations, and have much better diagnostics - defaulted to on when using Azure Application Monitor.
They target netstandard2.0 so should be compatible with the older packages these replace. Would you be able to upgrade? We're only making critical fixes for the older packages, and recommending people upgrade to Azure.* packages intead of the older Microosft.Azure.* packages.
As for the problem itself, it's hard to say without knowing when you're calling this in your application. During startup? What version of .NET? What are you using for your ASP.NET application framework?
While it's probably not the cause of the problem, it's hard to ignore that you're calling an async method synchronously, which can also cause problems. If you're in an async method, you should write your code like so:
var secret = await keyVaultClient.GetSecretAsync(KeyVaultUrl);
authenticationKey = secret.Value;
If not, call:
var secret = keyVaultClient.GetSecretAsnc(KeyVaultUrl).GetAwaiter().GetResult();
This is not recommended, though. In the new packages I mentioned above, we have both sync and async versions that are either sync or async all the way through the call stack and safer to use. Generally, though, you should use async calls - especially for network traffic like accessing Key Vault because, depending on what thread you call it, it can hang your UI.
When you test in local:
Add your vs signed account into azure keyvault. Go to keyvault> Access policy> add your account with get secret permmission.
When you publish to azure:
1.Enable webapp MSI.
2.Go to keyvault> Access policy> add your webapp's service principal with get secret permission.
The code:
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
KeyVaultClient keyVaultClient = new KeyVaultClient(new Microsoft.Azure.KeyVault.KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = keyVaultClient.GetSecretAsync("https://yourkevaultname.vault.azure.net/secrets/secretname/437d301daxxxxxx");
var authenticationKey = secret.Result.Value;
ViewBag.Message = authenticationKey.ToString();

Creating Media Service Credentials

I'm trying to set up the Azure Media Service. I created the media service from Azure but I don't know where can obtain the account key.
client = new CloudMediaContext(new MediaServicesCredentials(accountName, accountKey));
I'm following this tutorial: Integrating applications with Azure Active Directory
But after get the secret key in step 4 in "To add application credentials, or permissions to access web APIs" doesn't works.
client = new CloudMediaContext(new MediaServicesCredentials(accountName, accountKey));
Azure Media Services announces support for AAD and deprecation of ACS authentication.
Because Azure Active Directory provides powerful role-based access control features and support for more fine-grained access to resources in your account compared to the ACS token authentication model ("account keys"), we strongly recommend that you update your code and migrate from ACS to AAD-based authentication by June 22, 2018.
Also, a key reason for the rapid migration is the upcoming announced deprecation of the ACS key based authentication system.
User Authentication with AAD in Media Services
A native application would first acquire an access token from Azure Active Directory and then use that access token to make all REST API calls.
The following examples show how a daemon application may use AAD web application credentials to authenticate requests with the REST service.
For a REST API request to succeed, the calling user must be a “Contributor” or “Owner” of the Azure Media Services account it is trying to access.
Users of the .NET client SDK for Media Services must upgrade to the latest version on Nuget (windowsazure.mediaservices version 4.1.0.1 or greater) to use AAD authentication for communicating with REST requests.
You could use the following code to connect to the Media Services account.
class Program
{
// Read values from the App.config file.
private static readonly string _AADTenantDomain =
ConfigurationManager.AppSettings["AMSAADTenantDomain"];
private static readonly string _RESTAPIEndpoint =
ConfigurationManager.AppSettings["AMSRESTAPIEndpoint"];
private static readonly string _AMSClientId =
ConfigurationManager.AppSettings["AMSClientId"];
private static readonly string _AMSClientSecret =
ConfigurationManager.AppSettings["AMSClientSecret"];
private static CloudMediaContext _context = null;
static void Main(string[] args)
{
try
{
AzureAdTokenCredentials tokenCredentials =
new AzureAdTokenCredentials(_AADTenantDomain,
new AzureAdClientSymmetricKey(_AMSClientId, _AMSClientSecret),
AzureEnvironments.AzureCloudEnvironment);
var tokenProvider = new AzureAdTokenProvider(tokenCredentials);
_context = new CloudMediaContext(new Uri(_RESTAPIEndpoint), tokenProvider);
// Add calls to methods defined in this section.
// Make sure to update the file name and path to where you have your media file.
IAsset inputAsset =
UploadFile(#"C:\VideoFiles\BigBuckBunny.mp4", AssetCreationOptions.None);
IAsset encodedAsset =
EncodeToAdaptiveBitrateMP4s(inputAsset, AssetCreationOptions.None);
PublishAssetGetURLs(encodedAsset);
}
catch (Exception exception)
{
// Parse the XML error message in the Media Services response and create a new
// exception with its content.
exception = MediaServicesExceptionParser.Parse(exception);
Console.Error.WriteLine(exception.Message);
}
finally
{
Console.ReadLine();
}
}
NOTE: Applications will also need to update their references to include a new assembly "Microsoft.WindowsAzure.MediaServices.Client.Common.Authentication.dll" and add references to that namespace as well as reference to the "Microsoft.IdentityModel.Clients.ActiveDirectory" assembly to get access to the ITokenProvider interface.
Click API access and choose "
Connect to Azure Media Services API with user authentication".
This includes the API endpoint that you need to call, along with the ClientID, Domain, and Resource.
For more details about how to connect to Media Services with Azure AD, you could refer to this article.

Azure .NET SDK - List all virtual machines, failed to authenticate

Using the new Windows Azure SDK for .NET, I want to get a list of all virtual machines.
Piecing together the early documentation, here's what I came up with:
// This is a custom cert I made locally. I uploaded it to Azure's Management Certificates and added it to my local computer's cert store, see screenshot below.
X509Certificate2 myCustomCert = await this.GetAzureCert();
var credentials = new CertificateCloudCredentials(mySubscriptionId, myCustomCert);
using (var cloudServiceClient = CloudContext.Clients.CreateCloudServiceManagementClient(credentials))
{
credentials.InitializeServiceClient(cloudServiceClient); // Is this required? The next call fails either way.
// This line fails with exception: "The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription."
var services = await cloudServiceClient.CloudServices.ListAsync(CancellationToken.None);
}
My first thought was the cert was bad, but I am able to successfully call the Azure REST API using my custom certificate. As you can see below, it is properly added to the Azure Management Certificates and associated with my subscription:
What am I doing wrong?
Here's another option - rather than upload a cert, try pulling your management cert out of your publishsettings file and using the X509Certificate's constructor that takes a byte[]. Then, pass that parameter the result of a call to Convert.FromBase64String, passing it the string representation of your management certificate from your publishsettings file.
Also, take a look at the Compute management client rather than the Cloud Service Management client. There are more features specific to the compute stack in that client at this time. The code below is a demonstration of such an approach. Note, my _subscriptionId and _managementCert fields are both strings, and I just hard-code them to the values from my publishsettings file as I described above.
public async static void ListVms()
{
using (var client = new ComputeManagementClient(
new CertificateCloudCredentials(_subscriptionId,
new X509Certificate2(Convert.FromBase64String(_managementCert)))
))
{
var result = await client.HostedServices.ListAsync();
result.ToList().ForEach(x => Console.WriteLine(x.ServiceName));
}
}
There's a parameterless ListAsync method that's an extension method. Try importing the Microsoft.WindowsAzure.Management namespace (or the Microsoft.WindowsAzure.Management.Compute namespace). Once you see the parameterless ListAsync method you should be good. I'll also mock up some code to resemble what you're trying to accomplish and offer up a more comprehensive answer by the end of the day.

Resources