Blog

Application configuration: an in-depth look at Cloud Native Software Development with Azure

Discover how to make application configuration smoother and more efficient with Azure. Separate configuration and code for Cloud Native applications and learn handy tricks for seamless deployment with GitHub Actions. Optimize your applications and get more out of Azure's powerful services.

Published: 21 August 2023

When building Cloud Native applications, one of the key principles is to separate configuration from code (https://12factor.net/config). Ideally, like the infrastructure, you want this configuration captured in the code. After all, this offers benefits such as versioning, traceability, and manageability. In this article, we will take you through how this can be applied using Azure App Configuration and Azure Key Vault. 

At Intercept, we build Cloud Native applications ourselves and help our customers make their applications Cloud Native (Read more about how we do that here). All these applications use configuration, which often differs between environments (test, acceptance, and production). Here we always distinguish between sensitive and non-sensitive configurations. In this blog post, we use an application built in .Net 7. We deploy this application to Azure using GitHub Actions.

 

Saving the configuration

To save our configuration, we create a new folder in our repository (e.g., /config). For each environment, we create an appsettings[environmentname].json. In these files, we can specify our configuration as key-value pairs. If desired, nesting of values is possible.

{ 
  "Supplier": { 
    "RedirectUri": "https://cloudadventures.com", 
    "Scope": "all", 
    "State":  "active" 
  } 
}

Figure 1, an example of appsettings.dev.json

For all sensitive configurations, we create secretreferences[environmentname].json. In these files, we store the references to the secrets in Azure Key Vault. So we do not store the actual sensitive information in the configuration files. When we load the configuration into our application, it will access the Key Vault to retrieve the actual configuration values.

{ 
  "Supplier" : { 
    "ClientId": "{\"uri\":\"https://cloudadventures-dev.vault.azure.net/secrets/Supplier--ClientId\"}", 
    "ClientSecret": "{\"uri\":\"https://cloudadventures-dev.vault.azure.net/secrets/Supplier--ClientSecret\"}", 
  } 
} 

Figure 2, Example of secretreferences.dev.json with references to secrets in Azure Key Vault.

 

The deployment

To deploy our configuration, we use Github Actions. There is a GitHub action (Azure/AppConfiguration-Sync: GitHub action for importing configuration files to Azure App Configuration) available from Microsoft for synchronizing the configuration in our repository with that of the Azure App Configuration. To communicate with the App Configuration, we will need to add the connection string to our GitHub 'Actions secrets and variables' (Encrypted secrets - GitHub Docs) In our Action, we refer to this secret.

- uses: actions/checkout@v1  
- uses: azure/appconfiguration-sync@v1  
  with:  
    configurationFile: 'config/appsettings.dev.json' 
    format: 'json' 
    connectionString: ${{}} 
    separator: ':'  
    prefix: '' 
 - uses: azure/appconfiguration-sync@v1 
   with: 
     configurationFile: 'config/secretreferences.dev.json' 
     format: 'json' 
     connectionString: $<<secrets.app_configuration_connection>>
     separator: ':' 
     prefix: '' 
     contentType: 'application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8' 

Figure 3, GitHub Action deploying de configuratie bestanden

 

By running our pipeline simultaneously with the deployment of our application, we ensure that each new version of our application can access the latest configuration.

 

 

Using configuration in the application

 

We have now made sure that the configuration in Azure App Configuration is updated when the Action has run. We are going to read this configuration and use it in the application. To connect to the App Configuration in our code, we will use the 'Connection string'. This can be obtained by navigating in the Azure Portal to the App configuration resource and choosing 'Access keys'. We then store this connection string in the settings of our application (locally in the user secrets Safe storage of app secrets in development in ASP.NET Core | Microsoft Learn), in Azure we can use for example the 'application settings' or 'environment variables'. In our application, we need to include a reference to the NuGet package 'Microsoft.Azure.AppConfiguration.AspNetCore'. In the program.cs, the configuration values can be read using the following code:

var connectionString = builder.Configuration.GetValue<string>("AppConfigurationConnectionString"); 
builder.Configuration.AddAzureAppConfiguration(options => 
{ 
   options.Connect(connectionString).ConfigureKeyVault(kv => 
   { 
      var credentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions 
      { 
          SharedTokenCacheTenantId = tenantId 
      }); 
       
      kv.SetCredential(credentials); 
    }) 
    .Select("*"); 
}); 

Figure 4, Configure reading out App Configuration in the Program.cs

 

Using the options pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0), for example, the configuration values can be used.

 

Use a User Assigned Managed Identity  

When we are running in Azure, it is good to provide access to our Azure App Configuration and Azure Key Vault through Managed Identities. Therefore, as part of our infrastructure deployment, our application will need to be granted access to this resource. To gain access through a User Assigned Managed Identity, our Program.cs will need to be modified. The 'Client Id' of our Managed Identity must therefore be added to our configuration. 

var connectionString = builder.Configuration.GetValue<string>("AppConfigurationConnectionString"); 
var userAssignedManagedIdentityClientId = builder.Configuration.GetValue<string>(“UserAssignedManagedIdentityClientId”); 
builder.Configuration.AddAzureAppConfiguration(options => 
{ 
  options.Connect(connectionString).ConfigureKeyVault(kv => 
  { 
     DefaultAzureCredential credentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions 
     { 
        ManagedIdentityClientId = userAssignedManagedIdentityClientId 
     }); 
      
     kv.SetCredential(credentials); 
   }) 
   .Select("*"); 
});

Figure 5, Reading App Configuration through UMI in the Program.cs

 

To conclude 

In this article, we have shown how configuration-as-code can be achieved using Azure App Configuration and Azure Key Vault. Using Github Actions we are able to deploy this configuration automatically.  

If you are wondering how we can help you with the configuration of Cloud Native applications? Please contact us:

Discover our CNSD approach

Contact us

 

Useful links: 

https://learn.microsoft.com/en-us/azure/azure-app-configuration/  

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0   

https://github.com/Azure/AppConfiguration-Sync