Access Azure Key Vault secrets in the Azure DevOps Release Pipelines

How to inject Azure Key Vault secrets in the Azure DevOps CI/CD pipelines

Managing secrets in the application is crucial part of the whole development process. Please look at the picture. There are two loops:

Image not found

  • Inner - Focused on the developer teams iterating over their solution development (they consume the configuration published by the outer loop)
  • Outer - The Ops Engineer govern the Configuration management and push changes (including Azure KeyVault secrets management)

With such approach you are able keep clear separation of concerns and clean code. What is more, application configuration is much easier to maintain.

In this article I would like to present how integrate Azure Key Vault with Azure DevOps Release pipelines and how to inject secrets per specific environment (in this case Development, QA and Production).

Prerequisites

Before we start I assume that there are already three separate resource groups created for each enfironment:

  • dev-island-app-dev-rg: for Development
  • dev-island-app-qa-rg: for QA
  • dev-island-app-prod-rg: Production

Image not found

Of course there resource groups creation can be also automated. But for this article I created them manually. In each of above resource group I created Key Vault:

  • dev-island-app-dev-kv
  • dev-island-app-qa-kv
  • dev-island-app-prod-kv

Image not found

I created separate Key Vault per environment because it is recommended approach by Microsoft:

“Our recommendation is to use a vault per application per environment (Development, Pre-Production and Production).”

You can read more here.

In each Key Vault I created one secret with different values for each environment:

  • Name: “DbConnectionString”

  • Value for the Development environment: “Server=(localdb)\mssqllocaldb;Database=CarsIsland;Trusted_Connection=True;”

  • Value for the QA environment: “Server=(localdb)\mssqllocaldb;Database=CarsIslandQA;Trusted_Connection=True;”

  • Value for the Production environment: “Server=(localdb)\mssqllocaldb;Database=CarsIslandProd;Trusted_Connection=True;”

Image not found

Add new service connection so you can access Azure resources from the Azure DevOps

Image not found

Prepare release pipeline with Development, QA and Production stages

First of all we have to prepare release pipeline for all three environments: Development, QA and Production. Follo below steps:

Image not found

Image not found

Image not found

Image not found

Image not found

Image not found

Image not found

Setup Azure Key Vault integration in the Release pipeline

First of all we have to integrate Key Vault in the Release pipeline so secrets are available through variable group. Each stage in the release pipeline has its own variable group. Lets see how to do it.

Setup variable group for the Development environment

Select “Manage variable groups”:

Image not found

Click “+ Variable group”:

Image not found

Provide details about this specific variable group:

You have to auhorize Azure DevOps to access Azure subscription and Key Vault:

Image not found

Image not found

Now select which secrets you would like to use as variable in the release pipeline:

Image not found

In our case we have to select the secret created before called “DbConnectionString”:

Image not found

Once you select the secret, click “Save” button:

Image not found

Now get back to the “variable groups” tab and click “Link variable group”:

Image not found

Select the group we created above so “dev-island-app-dev-kv-vg “. Set “Variable group scope” to “Stages” and select only “Development”:

Image not found

Click “Save” button:

Image not found

Repeat above steps for the QA and Production environments

Create variable groups for the QA and Production like presented below:

  • dev-island-app-qa-kv-vg
  • dev-island-app-prod-kv-vg

Image not found

Image not found

Image not found

Image not found

Finally it looks like below:

Image not found

Below three variable groups should be linked:

Image not found

Setup Development environment release

We will use ARM template to deploy sample web app with the database connection string added to the configuration. For the Development environment we would like to use database connection string from the “dev-island-app-dev-kv-vg” variable group.

“azuredeploy.json” file looks like below:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "TestAppServicePlanName": {
      "type": "string",
      "minLength": 1
    },
    "TestWebAppName": {
      "type": "string",
      "minLength": 1
    },
    "DbConnectionString": {
      "type": "string",
      "minLength": 1
    },
    "TestAppServicePlanSkuName": {
      "type": "string",
      "defaultValue": "D1",
      "allowedValues": [
        "F1",
        "D1",
        "B1",
        "B2",
        "B3",
        "S1",
        "S2",
        "S3",
        "P1",
        "P2",
        "P3",
        "P4"
      ],
      "metadata": {
        "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
      }
    }
  },
  "variables": {
    "TestWebAppName": "[concat(parameters('TestWebAppName'), uniqueString(resourceGroup().id))]"
  },
  "resources": [
    {
      "name": "[parameters('TestAppServicePlanName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-08-01",
      "sku": {
        "name": "[parameters('TestAppServicePlanSkuName')]"
      },
      "dependsOn": [],
      "tags": {
        "displayName": "TestAppServicePlan"
      },
      "properties": {
        "name": "[parameters('TestAppServicePlanName')]",
        "numberOfWorkers": 1
      }
    },
    {
      "name": "[variables('TestWebAppName')]",
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "apiVersion": "2015-08-01",
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms', parameters('TestAppServicePlanName'))]"
      ],
      "tags": {
        "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('TestAppServicePlanName')))]": "Resource",
        "displayName": "TestWebApp"
      },
      "properties": {
        "name": "[variables('TestWebAppName')]",
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('TestAppServicePlanName'))]",
        "siteConfig": {
          "connectionStrings": [
            {
              "name": "DbTestConnectionString",
              "connectionString": "[parameters('DbConnectionString')]",
              "type": "string"
            }
          ]
        }
      },
      "resources": [
        {
          "name": "appsettings",
          "type": "config",
          "apiVersion": "2015-08-01",
          "dependsOn": [
            "[resourceId('Microsoft.Web/sites', variables('TestWebAppName'))]"
          ],
          "tags": {
            "displayName": "TestWebAppSettings"
          },
          "properties": {
            "key1": "value1",
            "key2": "value2"
          }
        }
      ]
    }
  ],
  "outputs": {}
}

“azuredeploy.parameters.json” file looks like below:

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "TestAppServicePlanName": {
      "value": "TestAppServicePlan"
    },
    "TestWebAppName": {
      "value": "TestWebAppCreatedWithARM"
    },
    "DbConnectionString": {
      "value": "#"
    }
  }
}

Image not found

Please note that in the section called “Override template parameters” we have to replace parameter with the value for the database connection string from the Key Vault:

-DbConnectionString $(DbConnectionString)

Setup QA environment release

For the QA environment we will have the same steps like presented above. In this case we are creating web app in the QA resource group in the Azure:

Image not found

Setup Production environment release

For the Production environment we will have the same steps like presented above. In this case we are creating web app in the Production resource group in the Azure:

Image not found

Create new release and check web apps configuration

Create new release. Once web app is created in each of the resource groups, check “Configuration” tab. You should see that each app has different database connection string retrieved from the Key Vault related with specific environment:

Image not found

Image not found

Image not found

Image not found

Image not found

Summary

In this article I explained how to inject Azure Key Vault secrets in the Azure DevOps release pipelines for multiple environments using variable groups. I hope you will find it valuable. Of course instead of variable groups you could use “Add Azure Key Vault” task in the release definition but I wanted to show another approach.

Updated: