Rob
Rob

Reputation: 7217

Azure - authenticate a console app using alternative to MSI

I'm working on a solution that currently runs code in Azure Functions. These functions are authenticated to our PaaS services (Key Vault/Service Bus/Blob Storage etc) using MSI authentication.

The code to authenticate is then as follows:

// Connect to KeyVault in the context of the running code.
var tokenProvider = new AzureServiceTokenProvider();

var config = new ConfigurationBuilder().AddAzureKeyVault(
     keyvaultUri,
     new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback)),
     new DefaultKeyVaultSecretManager())
  .Build();

This then allows me to connect to securely to Service Bus using code like this:

// Get ServiceBus connection settings.
config.GetSection("Messaging").Bind(ConfigSettings);    
var namespaceName = Regex.Match(ConfigSettings.ConnectionString, @"Endpoint=sb:\/\/([^.]*)", RegexOptions.IgnoreCase).Groups[1].Value;


var token = tokenProvider.GetAccessTokenAsync("https://management.core.windows.net/", string.Empty).Result;
var tokenCredentials = new TokenCredentials(token);

var client = RestClient.Configure()
        .WithEnvironment(AzureEnvironment.AzureGlobalCloud)
        .WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
        .WithCredentials(new AzureCredentials(tokenCredentials, tokenCredentials, string.Empty, AzureEnvironment.AzureGlobalCloud))
    .Build();

// Authenticate against Service Bus.
ServiceBusNamespace = Azure.Authenticate(client, string.Empty)
    .WithSubscription(ConfigSettings.SubscriptionId)
    .ServiceBusNamespaces.List()
    .SingleOrDefault(n => n.Name == namespaceName);

We are changing our solution so that instead of running Functions on ASE, we are going to carry out the same operation in a Console App (running on Linux). Obviously MSI auth won't work in this scenario, so I was looking to use a Service Priniple AppId and AppSercret. I setup the Principle in AAD as follows:

enter image description here

That code to utilise this Service Principles AppId and AppSecret is as follows:

    public async static Task<string> GetAccessToken(string tenantId, string appId, string appSecret)
    {
        var authenticationContext = new AuthenticationContext($"https://login.windows.net/{tenantId}");
        var credential = new ClientCredential(clientId: appId, clientSecret: appSecret);
        var result = await authenticationContext.AcquireTokenAsync(resource: "https://management.core.windows.net/", clientCredential: credential);

        if (result == null) {
            throw new InvalidOperationException("Failed to obtain the JWT token");
        }

        return result.AccessToken;    
     }

BUT it requires me to hold the likes of the AppId, AppSecret and TenantId ready for the app to use. I don't want to use AppSettings for obvious security reasons.

I can now run the Service Bus authentication using similar code to what I had before:

    var namespaceName = Regex.Match(connectionString, @"Endpoint=sb:\/\/([^.]*)", RegexOptions.IgnoreCase).Groups[1].Value;

    var tokenCredentials = new TokenCredentials(token);

    var client = RestClient.Configure()
        .WithEnvironment(AzureEnvironment.AzureGlobalCloud)
        .WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
        .WithCredentials(new AzureCredentials(tokenCredentials, tokenCredentials, string.Empty, AzureEnvironment.AzureGlobalCloud))
        .Build();

    var serviceBusNamespace = Azure.Authenticate(client, string.Empty)
        .WithSubscription(subscriptionId)
        .ServiceBusNamespaces.List()
        .SingleOrDefault(n => n.Name == namespaceName);

    if (serviceBusNamespace == null)
    {
        throw new InvalidOperationException($"Couldn't find the service bus namespace {namespaceName} in the subscription with ID {subscriptionId}");
    }

My question - it seems counter productive to worry about security and then store the required config fields (AppId, AppSecret, TenantId) in config or Env Vars. Do I have any other options? I cant use KeyVault unless I have authenticated but again, I need my Service Principle authenticated before I can access it.

Has anyone made this approach before? Maybe Service Principle isn't the right approach?

Thanks in advance for any pointers!

Upvotes: 0

Views: 1127

Answers (1)

Joey Cai
Joey Cai

Reputation: 20067

According to your description, you do not want to store AppId in config file and want use Service Principle to authenticate.

If so, you could refer to the juunas article and you could use AzureServiceTokenProvider to authenticate and go on what you want.

You could use the following code to get key vault secret without using AppId and AppSecret.

var azureServiceTokenProvider = new AzureServiceTokenProvider();

var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));

var scret = keyVaultClient.GetSecretAsync("https://xxxx.vault.azure.net", "xxxx").GetAwaiter().GetResult();

Upvotes: 1

Related Questions