Azure Confidential Computing: Azure RBAC for Secure Key Release

📣 Dive into the code samples from this blog on GitHub!

A few months ago, I wrote a post detailing how to leverage Azure Key Vault’s Secure Key Release feature. Upon revisiting that post, I realized that I had utilized Key Vault’s access policy feature to enable a specific security principal for the release operation. While Key Vault access policies are an older method of managing permissions, they have the advantage of immediate propagation, unlike Azure Role-Based Access Control assignments which may take a minute to process.

💡 I distinctly recall a scenario where I had to include a time_sleep resource in a Terraform infrastructure-as-code configuration when configuring an azurerm_role_assignment. Initially, I considered it a somewhat hacky approach, but over time, I have come to accept it as a practical solution. In fact, I would likely script something similar if the need arose."

At any rate, this prompted me to pause and reconsider how I could configure the release operation using a more modern approach through Azure RBAC.

Secure Key Release refresher

What if we want to release an Azure Key Vault key to an application that we have verified to be running in a particular Azure region or a particular firmware version. Can we release a key once we have verified if the request originates from within a compliant Azure Confidential Virtual Machine? That’s where secure key release can help, it takes a policy-based approach to releasing keys to trusted execution environments. Azure Key Vault Premium SKU and Managed-HSM can release HSM keys.

Performing the release key operation is similar to performing the get key operation on an Azure Key Vault, except we will need to change:

  • Key Vault RBAC settings will need to be modified
  • The key will need some specific properties:
    • A release policy, since we only want a trusted confidential virtual machine to be able to access our key.
    • Must be marked as exportable.
  • The HTTP request will target a slightly different URL since we are performing a different operation.
    • Additionally, the Confidential VM’s attested platform report (aka the environment assertion) must be included the request body. We can use the Microsoft Azure Attestation service to to attest to the trustworthiness of the state of a Trusted Execution Environment-enabled platform, such as our Confidential VM.

📖 From the documentation: “An Environment Assertion is a signed assertion, in JSON Web Token form, from a trusted authority. An environment assertion contains at least a key encryption key and one or more claims about the target environment (for example, TEE type, publisher, version) that are matched against the Key Release Policy.”

After all of those steps have been done, we send the request off to Key Vault and it releases the key.

Image of the aforementioned operations that we will be performing.

💡 I highly recommend you read through my earlier blog post on Secure Key Release as I walk through the process in much greater detail compared to this section.

RBAC 101

In 2019, Micha Wets and I conducted a presentation on Azure Governance mechanisms, starting with an introduction to Azure RBAC. Even today, many of the concepts we discussed about RBAC remain relevant. If you’re familiar with Azure, you might have come across RBAC, which stands for Role-Based Access Control. It serves as a means to regulate and manage the actions users can perform on the Azure platform.

To leverage RBAC effectively, you’ll need to perform a role assignment, which requires three key ingredients.

Step 1: Pick a role

Azure offers a wide range of roles, but I consider the following four roles to be fundamental:

  • Owner: This role grants full access to all resources, including the authority to delegate access to others.
  • User Access Administrator: With this role, you can manage user access to Azure resources effectively.
  • Contributor: As a contributor, you gain the ability to create and manage various Azure resources, but you cannot grant access to others.
  • Reader: A reader role allows you to view existing Azure resources.

💡 The role definition resource, managed by the Microsoft.Authorization/roleDefinitions provider, defines the characteristics of each role.

Azure offers additional built-in roles that are more specific in terms of the actions they permit. Some roles enable you to manage CDN endpoints, while others allow you to read messages from specific queues within a Service Bus namespace. If none of the built-in roles fit your requirements, you always have the option to create a custom role. For an extensive list of the built-in roles, Microsoft maintains a comprehensive overview of all of the built-in roles.

Step 2: Define the Scope

A scope refers to the set of resources to which the access permissions apply. When assigning roles, you have the flexibility to narrow down the actions allowed by specifying a scope. This is particularly useful if you want to grant someone the “Website Contributor” role but restrict it to a specific resource group.

Azure enables you to define a scope at multiple levels:

  • Management group
  • Subscription
  • Resource group
  • Resource

Scopes follow a hierarchical parent-child relationship, allowing for granular access control.

Step 3: Choose a security principal

A security principal represents an entity—a user, group, service principal, or managed identity—that requests access to Azure resources.

  • User: An individual with a profile in Azure Active Directory.
  • Group: A collection of security principals.
  • Service principal: A security identity used by applications or services to access specific Azure resources.
    • Think of it as a user identity (username and password or certificate) for an application.
  • System-assigned Managed Identity: An identity automatically managed by Azure Active Directory, primarily used by applications in authenticating to Azure services.
    • Its lifecycle is linked to the Azure resource that created it, such as an Azure VM. When the VM is deleted, the associated managed identity is also removed.
  • User-assigned Managed Identity: It is an independent Azure resource with its own lifecycle.
    • You can associate it with multiple Azure resources.

By selecting the appropriate security principal, you can precisely control access to Azure resources based on specific needs and permissions.

Key Vault Secure Key Release with RBAC

Building upon the information covered in the previous section, let’s dive into performing a secure key release on an Azure Key Vault Premium. Since this operation involves the data plane of the service (remember that many Azure PaaS services have separate permissions for management and data planes), we require a role that grants us the necessary permissions for the specific data action. Additionally, it’s important to note that Key Vault only performs a secure key release when the requesting trusted execution environment has been attested by the Microsoft Azure Attestation service, as explained in detail in my other article.

To facilitate a secure key release, we need the following components:

  • Role Assignment: We must assign a role that allows the data action Microsoft.KeyVault/vaults/keys/release/action. This role will grant the necessary permissions for the key release operation.
  • Scope: For the purposes of this demonstration, we will set the scope at the resource group level. By specifying the scope, we can control access to the secure key release operation within a defined boundary.
  • Security Principal: In this case, we can leverage the system-assigned identity of the confidential virtual machine as the security principal.

By ensuring that the appropriate role is assigned, setting the scope accordingly, and utilizing the system-assigned identity of the confidential virtual machine as the security principal, we should have everything we need perform secure key releases.

Demo time

Let’s deploy some of the infrastructure to see it in action. I have refactored the Key Vault module from the sample used in my previous article. For the complete version of the module, you can refer to the GitHub repository for this blog post. Below, I have included the contents of the Key Vault module, which will create the premium Key Vault, along with an exportable RSA key that has a release policy.

💡 The release policy contains validation logic to ensure that the JWT claims within the Microsoft Azure Attestation token align with the expected criteria. This process can be compared to how Azure Policy validates ARM deployments by examining the contents of a JSON document and verifying if it meets specific criteria. Similarly, in this scenario, if the contents of the MAA token fail to meet the specified criteria, Key Vault will refrain from releasing the key.

targetScope = 'resourceGroup'

@description('Required. Specifies the name of the key vault.')
param keyVaultName string

@description('Required. Specifies the Azure location where the key vault should be created.')
param location string = resourceGroup().location

@description('Optional Specifies whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.')
param enabledForDeployment bool = false

@description('Specifies whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.')
param enabledForDiskEncryption bool = false

@description('Specifies whether Azure Resource Manager is permitted to retrieve secrets from the key vault.')
param enabledForTemplateDeployment bool = false

@description('Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet.')
param tenantId string = subscription().tenantId

@description('Required. Specifies the object ID of a user, service principal or security group in the Azure Active Directory tenant for the vault. The object ID must be unique for the list of access policies. Get it by using Get-AzADUser or Get-AzADServicePrincipal cmdlets.')
param objectId string

@description('Specifies whether the key vault is a standard vault or a premium vault.')
@allowed([
  'premium'
])
param skuName string = 'premium'

@description('Specifies the name of the key that you want to create.')
param keyName string

@description('The type of the key. For valid values, see JsonWebKeyType. Must be backed by HSM, for secure key release.')
@allowed([
  'EC-HSM'
  'RSA-HSM'
])
param keyType string

@description('Specifies whether the key should be exportable, "true" is required for secure key release.')
param keyExportable bool = true

@description('Not before date in seconds since 1970-01-01T00:00:00Z.')
param keyNotBefore int = -1

@description('Determines whether or not the object is enabled, "true" is required for secure key release.')
param keyEnabled bool = true

@description('Expiry date in seconds since 1970-01-01T00:00:00Z.')
param keyExpiration int = -1

@description('The elliptic curve name. For valid values, see JsonWebKeyCurveName.')
@allowed([
  'P-256'
  'P-256K'
  'P-384'
  'P-521'
])
param curveName string = 'P-256'

@description('The key size in bits. For example: 2048, 3072, or 4096 for RSA.')
param keySize int = -1

@description('Specifies the key operations that can be perform on the specific key. String array containing any of: "decrypt", "encrypt", "import", "release", "sign", "unwrapKey", "verify", "wrapKey"')
@allowed([
  'decrypt'
  'encrypt'
  'import'
  'sign'
  'unwrapKey'
  'verify'
  'wrapKey'
])
param keyOps array = []

@description('Content type and version of key release policy.')
param releasePolicyContentType string = 'application/json; charset=utf-8'

@description('Blob encoding the policy rules under which the key can be released. Blob must be base64 encoded.')
param releasePolicyData string

resource kv 'Microsoft.KeyVault/vaults@2023-02-01' = {
  name: keyVaultName
  location: location
  properties: {
    enabledForDeployment: enabledForDeployment
    enabledForDiskEncryption: enabledForDiskEncryption
    enabledForTemplateDeployment: enabledForTemplateDeployment
    tenantId: tenantId
    enableRbacAuthorization: true   // 👈 no need to maintain the 'accessPolicies' property
    sku: {
      name: skuName
      family: 'A'
    }
    networkAcls: {
      defaultAction: 'Allow'
      bypass: 'AzureServices'
    }
  }
}

resource key 'Microsoft.KeyVault/vaults/keys@2023-02-01' = {
  parent: kv
  name: keyName
  properties: {
    kty: keyType
    attributes: {
      exportable: keyExportable
      enabled: keyEnabled
      nbf: keyNotBefore == -1 ? null : keyNotBefore
      exp: keyExpiration == -1 ? null : keyExpiration
    }
    curveName: curveName
    keySize: keySize == -1 ? null : keySize
    keyOps: keyOps
    release_policy: {
      contentType: releasePolicyContentType
      data: releasePolicyData
    }
  }
}

Next, we will proceed with deploying a new role definition. This role definition will enable service principals to perform release operations on Key Vault keys.

var releaseKeyRoleName = 'Key Vault Crypto Service Key Release User'
var releaseKeyRoleDescription = 'Perform release operations on a Key Vault Keys.'

resource CustomReleaseKeyRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-04-01' = {
  name: guid(releaseKeyRoleName, resourceGroup().id)
  properties:{
    roleName: releaseKeyRoleName
    description: releaseKeyRoleDescription
    assignableScopes: [ resourceGroup().id ]
    permissions: [
      {
        dataActions: [
          'Microsoft.KeyVault/vaults/keys/release/action'
        ]
      }
    ]
  }
}

By deploying this code, you will create a new custom role definition called ‘Key Vault Crypto Service Key Release User’. This role definition provides users with the necessary permissions to perform release operations on Key Vault keys. Initially, the role definition is scoped to the current resource group. However, you have the flexibility to adjust the scope to a different level, such as a specific resource, subscription, or management group, according to your requirements. At the time of writing, there is a limit of 5000 custom roles per tenant.

With that done, we can perform the role assignment, to assign the newly created custom role to a specific security principal. The objectId in this case will be the system-assigned managed identity of the confidential virtual machine.

resource ReleaseKeyRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(key.id, objectId, CustomReleaseKeyRoleDefinition.id)
  scope: key
  properties: {
    principalId: objectId
    roleDefinitionId: CustomReleaseKeyRoleDefinition.id
  }
}

The scope of this role assignment is interesting because Azure Key Vault offers the flexibility to perform role assignments and define roles on specific objects within the Key Vault, such as a key. However, it is important to be mindful of the fact that, similar to role definitions, there is a maximum limit on the number of role assignments that can be created.

This level of granularity enables us to be highly specific about granting access to keys for particular virtual machines (VMs). With Azure Key Vault, we have the capability to precisely control which VMs are granted access to which keys, ensuring a fine-tuned security posture.

⚠️ When working with key vault secret, certificate, and object-scope role assignments, it is crucial to exercise caution and employ them only in specific and limited scenarios. One of the key considerations is minimizing the potential blast radius of any security incidents. To achieve this, it is advised not to consolidate all secrets into a single Key Vault and rely solely on RBAC for separation. In the event of a misconfiguration, this could result in all secrets being at risk of compromise. It is important to follow best practices to ensure secure usage. If you want to read up on Key Vault best practices, you can refer to them here.

Performing a key release

Once the RBAC assignment has been successfully propagated, you can proceed with executing the Invoke-SecureKeyRelease.ps1 script to perform the key release.

💡 I wrote the Invoke-SecureKeyRelease.ps1 script as a part of my earlier blog post on Secure Key Release, though you can find it in own dedicated GitHub repository under the scripts folder.

It is important to ensure that the -AttestationTenant parameter provided matches the authority claim specified in the release policy. If the attestation tenant endpoint differs from what is expected, the Key Vault will reject the MAA token, and the key release operation will not be performed.

./Invoke-SecureKeyRelease.ps1 -AttestationTenant "https://sharedweu.weu.attest.azure.net" -VaultBaseUrl "https://skr-kvq6srllol2jntw.vault.azure.net/" -KeyName "myskrkey"

# value
# -----
# eyJhbGciOiJSUzI1NiIsImtpZC...

If you were to remove the RBAC assignment, you would encounter an error message indicating that the caller is not authorized to perform the action on the resource. This error message emphasizes the importance of having the appropriate RBAC assignment in place for successful execution.

./Invoke-SecureKeyRelease.ps1 -AttestationTenant "https://sharedweu.weu.attest.azure.net" -VaultBaseUrl "https://skr-kvq6srllol2jntw.vault.azure.net/" -KeyName "myskrkey"

Invoke-WebRequest: /home/superadm/Invoke-SecureKeyRelease.ps1:86:25
Line |
  86 |  … yResponse = Invoke-WebRequest -Method POST -Uri $kvReleaseKeyUrl -Hea …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | {"error":{"code":"Forbidden","message":"Caller is not authorized to perform action on resource.\r\nIf role assignments, deny assignments or role definitions were changed recently, please observe propagation time.\r\nCaller:
     | appid=e37732c2-434a-4a2c-8b0b-767f22293dbd;oid=0f7dbe79-e24e-4753-8608-8ab15540550f;iss=https://sts.windows.net/00000000-0000-0000-0000-000000000000/\r\nAction: 'Microsoft.KeyVault/vaults/keys/release/action'\r\nResource:
     | '/subscriptions/a8ee6785-5b8a-464b-9a31-bdd0e4a4e6c5/resourcegroups/securekeyrelease-blog-rg/providers/microsoft.keyvault/vaults/skr-kvq6srllol2jntw/keys/myskrkey'\r\nAssignment: (not found)\r\nDecisionReason: 'DeniedWithNoValidRBAC' \r\nVault:
     | skr-kvq6srllol2jntw;location=westeurope\r\n","innererror":{"code":"ForbiddenByRbac"}}}
Write-Error: Unable to perform release key operation.

In closing

Azure RBAC has demonstrated its reliability and effectiveness in managing access control within Azure. Its versatile features, such as role assignment, scope definition, and security principal specification, offer a pretty solid foundation for controlling access to Azure resources.

Whether it’s granting extensive access as an Owner or providing restricted permissions as a Reader, RBAC can ensure flexibility in aligning access privileges with your organisation’s specific needs. This flexibility is particularly useful in the context of our SKR (Secure Key Release) use case, since RBAC plays a crucial role in granting appropriate access to Key Vault resources.

Thanks for joining me in this exploration of RBAC and Secure Key Release. 😊 Should you have any further questions or if you’ve spotted a mistake, please don’t hesitate to reach out.