DEV Community

Christos Matskas for The 425 Show

Posted on • Updated on

Secure Azure deployments with Bicep and Azure Key Vault

Bicep is a new (experimental) way for building and deploying infrastructure to Azure. It's a language that compiles down to standard Azure Resource Manager json templates. So instead of handcrafting hundreds (if not thousands) of lines of json, you can code it in Bicep and then let the compiler do the hard work :)

To start with Bicep, you need to install the Bicep CLI tool and ideally the VS Code extension so that you can get Intellisense and code syntax colorization. Side note: not all theme extensions support Bicep yet.

If you want to see a video of me setting it all up and writing my first Bicep program, check out this recording:

Our first Bicep template

For the purpose of this blog, we'll use Bicep to create a SQL Server and SQL database resource on Azure. Create a new directory and add a main.bicep file:

param subscriptionId string = 'e42acc2b-8462-4fb5-bf0d-d983c0017584'
param kvResourceGroup string = 'identity'
param kvName string = 'cm-identity-kv'

resource kv 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name: kvName
  scope: resourceGroup(subscriptionId, kvResourceGroup )
}

module sql './sql.bicep' = {
  name: 'deploySQL'
  params: {
    administratorLogin: kv.getSecret('sqlAdministratorLogin')
    administratorLoginPassword: kv.getSecret('sqlAdministratorLoginPassword')
  }
}

Enter fullscreen mode Exit fullscreen mode

In this file, we define a few parameters that mainly point to our KeyVault resource and then we call into the SQL module.

Next, we need to create a new Bicep file for our SQL Server and database. Name the new file sql.bicep and add the following code:

param serverName string = uniqueString('sql125486asdf2')
param sqlDBName string = 'SampleDB'
param location string = resourceGroup().location

@secure()
param administratorLogin string
@secure()
param administratorLoginPassword string

resource server 'Microsoft.Sql/servers@2019-06-01-preview' = {
  name: serverName
  location: location
  properties: {
    administratorLogin: administratorLogin
    administratorLoginPassword: administratorLoginPassword
  }
}

resource sqlDB 'Microsoft.Sql/servers/databases@2020-08-01-preview' = {
  name: '${server.name}/${sqlDBName}'
  location: location
  sku: {
    name: 'Standard'
    tier: 'Standard'
  }
}
Enter fullscreen mode Exit fullscreen mode

We will use a random name for the SQL Server and we'll inherit some properties (i.e the location) from the Azure resource group we're deploying to. uniqueString() and resourceGroup() are built-in Bicep functions. You can find more about Bicep functions here

You'll notice that we also have two uninitialized secure parameters:

  • administratorLogin
  • administratorLoginPassword

We left them blank for a good reason! We don't want to store secrets in our code as this is a security attack vector. The values for these properties will be passed from the main.bicep file which will retrieve them from Azure KeyVault making our deployment code secure end-to-end.

Let's build our code:

bicep build main.bicep
Enter fullscreen mode Exit fullscreen mode

This should generate an output file main.json that contains the compiled ARM template json:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "serverName": {
      "type": "string",
      "defaultValue": "[uniqueString('sql125486asdf')]"
    },
    "sqlDBName": {
      "type": "string",
      "defaultValue": "SampleDB"
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "administratorLogin": {
      "type": "string"
    },
    "administratorLoginPassword": {
      "type": "secureString"
    }
  },
  "functions": [],
  "resources": [
    {
      "type": "Microsoft.Sql/servers",
      "apiVersion": "2019-06-01-preview",
      "name": "[parameters('serverName')]",
      "location": "[parameters('location')]",
      "properties": {
        "administratorLogin": "[parameters('administratorLogin')]",
        "administratorLoginPassword": "[parameters('administratorLoginPassword')]"
      }
    },
    {
      "type": "Microsoft.Sql/servers/databases",
      "apiVersion": "2020-08-01-preview",
      "name": "[format('{0}/{1}', parameters('serverName'), parameters('sqlDBName'))]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard",
        "tier": "Standard"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Sql/servers', parameters('serverName'))]"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Let's add the Sec in DevSecOps

First and foremost, we need to ensure that the account deploying this ARM template has the right enough privilege to do so. You don't want to use a user account as these tend to have elevated permissions and can cause some serious damage. The recommended practice is to use a Service Principal account. You can create these via the Azure Portal, PowerShell or the CLI. In this instance, we'll use the Azure CLI

az login
az ad sp create-for-rbac --role Contributor --scopes /subscriptions/<Your Subscription ID>/resourceGroups/<Your Resource Group Name> --name <Give it a nice name> 
Enter fullscreen mode Exit fullscreen mode

Take a note of the output.

For our account to also be able to retrieve the necessary secrets from Azure Key Vault, we need to create a very specific role. You can find more information about how to create this very custom RBAC role here.

Following the above docs, we'll create a new keyvaultrole.json file and paste the following json:

{
  "Name": "Key Vault CM resource manager template deployment operator",
  "IsCustom": true,
  "Description": "Lets you deploy a resource manager template with the access to the secrets in the Key Vault.",
  "Actions": [
    "Microsoft.KeyVault/vaults/deploy/action"
  ],
  "NotActions": [],
  "DataActions": [],
  "NotDataActions": [],
  "AssignableScopes": [
"/subscriptions/<Your Subscription ID>/resourceGroups/<Your Resource Group Name>/providers/Microsoft.KeyVault/vaults/<Your Key Vault name>"
  ]
}
Enter fullscreen mode Exit fullscreen mode

We now need to create this role in our Azure Subscription (if one doesn't exist already) and assign it to our Service Principal. In the Azure CLI, type the following:

az role definition create --role-definition ./keyvaultrole.json
az role assignment create --role "Key Vault CM resource manager template deployment operator" --assignee <your SP client Id> --resource-group bicep-demo
Enter fullscreen mode Exit fullscreen mode

Now that we have an account with the right privileges, we need to sign out (as ourselves) and sign in with the newly created Service Principal account:

az account clear
az login --service-principal -u <Service Principal Id> -p <Service Principal password> --tenant <Azure AD Tenant ID>
az account show
Enter fullscreen mode Exit fullscreen mode

Alt Text

The final step is to configure our Key Vault to allow ARM deployments as well as create the necessary secrets that will be referenced by our Bicep-generated ARM templates

In Key Vault, navigate to the Access Policies tab and ensure that the Azure Resource Manager for template deployment option under Enable Access to: is checked. Make sure to press the Save button if you make any changes:

Alt Text

Finally, we want to check that our Service Principal has been assigned the right role to be able to read secrets from Key Vault. Navigate to the Access Control (IAM) tab and check under Role Assignments to ensure that the script we run earlier worked:

Alt Text

We now have a locked down account and the all the secrets stored in Key Vault. Let's finalize our Bicep/ARM deployment. We can now go ahead and deploy our infrastructure as code using the Azure CLI:

az deployment group create --resource-group bicep-demo --template-file main.bicep
Enter fullscreen mode Exit fullscreen mode

The output will be a bunch of json with information about the deployment like the heavily omitted output below:

{
  "id": "/subscriptions/e42acc2d-8462-4fb5-bf0d-d983c0017584/resourceGroups/bicep-demo/providers/Microsoft.Resources/deployments/cmdeployment425show",
  "location": null,
    ...
    "provisioningState": "Succeeded",
    "templateHash": "18120650064530837360",
    "templateLink": null,
    "timestamp": "2021-03-02T00:16:56.630918+00:00",
    "validatedResources": null
  },
  "resourceGroup": "bicep-demo",
  "tags": null,
  "type": "Microsoft.Resources/deployments"
}
Enter fullscreen mode Exit fullscreen mode

Building more elaborate Bicep code

The GitHub repo is thin at the moment and for a moment I thought I was stuck with the tutorial sample code. But then, I came across the glorious Bicep Playground and all was great again. The Playground displays Bicep and compiled json side by side and provides you with a lot of sample code to help you build your own templates. My suspicion is that this is all driven by the Azure ARM Samples repo:

Alt Text

Source Code

This GitHub repo contains all the files and code referenced in this blob post.

Summary

I love Bicep because it makes working with Azure Resources more straightforward and concise. And unlike Pulumi or Terraform, it's native Azure and therefore provides better support for Azure resources. However, there are pros and cons with every tool so choose the one that truly makes you happy (or productive).

Top comments (0)