A KeyVault for the Power Platform

sandr0p

sandr0-p

Posted on November 25, 2024

A KeyVault for the Power Platform

My friend and Microsoft MVP David Wyatt recently asked me to provide him with an Azure Key Vault for his Power Apps. "Easy", I thought, but everyone who ever worked with Microsoft products knows that "easy" is a relative term.


We started by creating a standalone Key Vault. I added David and the Power Platform SPN with Secret Get/List permissions, and we gave it a go. It will not surprise you that it didn't work (otherwise I wouldn't write, right?!). The Key Vault Firewall rejected the traffic.
Fair, but thanks to our Company policies, we couldn't just allow public traffic. Which admittedly makes sense if you want to keep your secrets safe 🤷‍♂️


In the next step, we added a couple of Power Platform IP addresses to the Key Vault Firewall. Unfortunately, it had the same outcome. The firewall rejected the traffic. It was at this moment we knew this was getting out of hand. It became actual work.
As it turns out, we needed an entire Virtual Network with a Subnet and Network Security Group. If you ask me, this is too much work, but David insisted... and this is what it looks like:

Network Architecture


As just mentioned, we need four resources. A Key Vault (kv), a Virtual Network (vnet), a Subnet (subnet) and a Network Security Group (nsg). To make matters worse, they have dependencies all over the place. If you are, like me, not a network specialist, this is a bit confusing. But here is what it looks like.

Resource depencies


One of the resources without any dependencies is the nsg, which makes it a prime starting point.

resource nsg 'Microsoft.Network/networkSecurityGroups@2024-03-01' = {
  location: 'eastus2'
  name: 'nsg'
  properties: {
    securityRules: [
      {
        name: 'AllowPP'
        properties: {
          access: 'Allow'
          destinationAddressPrefix: 'VirtualNetwork'
          destinationPortRange: '*'
          direction: 'Inbound'
          priority: 100
          protocol: '*'
          sourceAddressPrefix: 'PowerPlatformInfra'
          sourcePortRange: '*'
        }
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The main focus here is on the sourceAddressPrefix and sourceAddressPrefix. These indicate from where to where we want to allow traffic. Usually, we would enter a list of IP addresses here, but Microsoft was so kind to provide us with Virtual Network Service Tags, which we can use instead of IP addresses.


Next up, the vnet. This is the second resource without a dependency.

resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' = {
  name: 'vnet'
  location: 'eastus2'
  properties: {
    addressSpace: {
      addressPrefixes: [
        '10.0.0.0/16'
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

You can choose your addressPrefixes pretty much at random if this is an isolated network. If you intend to connect this network to another, make sure that you use a compatible IP range. The IP range in the example is a CIDR notation and means that we can use the 65,536 IP addresses between 10.0.0.0 and 10.0.255.255.


Let's continue with the subnet, which depends on the vnet and nsg which we have just defined.

resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' existing = {
  name: 'vnet'
}

resource nsg 'Microsoft.Network/networkSecurityGroups@2024-03-01' existing = {
  name: 'nsg'
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' = {
  name: 'default'
  parent: vnet
  properties: {
    addressPrefix: '10.0.1.0/24'
    networkSecurityGroup: {
      id: nsg.id
    }
    serviceEndpoints: [
      {
        service: 'Microsoft.KeyVault'
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

We use the existing keyword to access the vnet and nsg, which should already exist when the subnet is being deployed. The vnet is used as the parent and the nsg.id for the networkSecurityGroup. We must also decide which IPs available on the vnet should be assigned to this subnet. In the example, we assigned all IPs from 10.0.1.0 to 10.0.1.255. And very importantly, we need to make Microsoft.KeyVault available in the serviceEndpoints. Otherwise, the traffic will be blocked once again.

💡 To be safe, ensure your related resources use the same API version. In the above, we have three Microsoft.Network resources using API version 2024-03-01. More often than not, mixed versions will work, but sometimes you end up with bizarre behaviour. Using the same API version will increase the chance that everything works.


And last but not least, we define the resource that has kicked off this odyssey, the Key Vault.

resource vnet 'Microsoft.Network/virtualNetworks@2024-03-01' existing = {
  name: 'vnet'
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2024-03-01' existing = {
  name: 'default'
  parent: vnet
}

resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = {
  name: 'kv'
  location: 'eastus2'
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    networkAcls: {
      bypass: 'AzureServices'
      defaultAction: 'Deny'
      virtualNetworkRules: [
        {
          id: subnet.id
        }
      ]
    }
    enabledForTemplateDeployment: true
    accessPolicies: [
      {
        tenantId: subscription().tenantId
        objectId: '00000000-0000-0000-0000-000000000000'
        permissions: {
          certificates: []
          keys: []
          secrets: [
            'get'
            'list'
          ]
          storage: []
        }
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Once again, we use the existing keyword to access the already deployed vent and subnet. The points of interest here are the networkAcls and accessPolicies.
The defaultAction: Deny property of networkAcls prohibits all traffic by default. With bypass: AzureServices we overrides= the defaultAction and allow Azure-hosted services, such as a Web App, direct access to the kv. The same is true for all IPs and rules defined in the subnet andnsg, which we assign to thevirtualNetworkRules.
In
accessPolicieswe which identities (Users, Applications, Resources can perform which actions against Key Vault items. In the example, we grant get/list access to the given objectId. Where list allows to fetch a list of the name of all stored secrets and get allows to read the secret value.


To wrap things up, let's write the pipeline we can run in Azure DevOps.

trigger:
  - none

parameters:
  - name: TargetEnvironment
    type: string
    values: 
      - 'your-azure-resource-group'
  - name: kv01
    displayName: "Deploy Key Vault"
    type: boolean
    default: false
  - name: vnet01
    displayName: "Deploy Virtual Network"
    type: boolean
    default: false
  - name: nsg01
    displayName: "Deploy Network Security Group"
    type: boolean
    default: false
  - name: subnet01
    displayName: "Deploy Subnet"
    type: boolean
    default: false

variables:
  - name: serviceConnection
    value: "your-azure-service-connection"


stages:
  - stage: 'Infrastructure'
    displayName: 'Infrastructure'
    pool:
      vmImage: ubuntu-latest
    # Deploy kv01
    - job: kv01
      displayName: 'Create Key Vault'
      condition: eq('${{ parameters.kv01 }}', true)
      dependsOn:
        - vnet01
        - subnet01
      steps:
        - task: AzureCLI@2
          inputs:
            azureSubscription: $(serviceConnection)
            scriptType: "bash"
            scriptLocation: "inlineScript"
            inlineScript: |
              az deployment group create \
              --resource-group ${{parameters.TargetEnvironment}} \
              --template-file  $(Build.SourcesDirectory)/KeyVault.bicep
    # Deploy vnet01
    - job: vnet01
      displayName: 'Create Virtual Network'
      condition: eq('${{ parameters.vnet01 }}', true)
      steps:
        - task: AzureCLI@2
          inputs:
            azureSubscription: $(serviceConnection)
            scriptType: "bash"
            scriptLocation: "inlineScript"
            inlineScript: |
              az deployment group create \
              --resource-group ${{parameters.TargetEnvironment}} \
              --template-file  $(Build.SourcesDirectory)/VirtualNetwork.bicep
    # Deploy nsg01
    - job: nsg01
      displayName: 'Create Network Security Group'
      condition: eq('${{ parameters.nsg01 }}', true)
      steps:
        - task: AzureCLI@2
          inputs:
            azureSubscription: $(serviceConnection)
            scriptType: "bash"
            scriptLocation: "inlineScript"
            inlineScript: |
              az deployment group create \
              --resource-group ${{parameters.TargetEnvironment}} \
              --template-file  $(Build.SourcesDirectory)/NetworkSecurityGroup.bicep
    # Deploy subnet01
    - job: subnet01
      displayName: 'Create Subnet'
      condition: eq('${{ parameters.subnet01 }}', true)
      dependsOn:
        - vnet01
        - nsg01
      steps:
        - task: AzureCLI@2
          inputs:
            azureSubscription: $(serviceConnection)
            scriptType: "bash"
            scriptLocation: "inlineScript"
            inlineScript: |
              az deployment group create \
              --resource-group ${{parameters.TargetEnvironment}} \
              --template-file  $(Build.SourcesDirectory)/Subnet.bicep
Enter fullscreen mode Exit fullscreen mode

With trigger: -none, we indicate that we want to execute the pipeline manually and not, for example, as part of a PR or merge. The following block allows to select which resources should be deployed on a run and to which resource group.
For the deployment of the resources, we have four identical blocks, one for each resource. With condition: eq('${{ parameters.kv01 }}', true), we instruct Azure to execute the job only if we have selected it. Otherwise, skip it.
dependsOn: - vnet01 - subnet01 defines our dependicies. Azure will attempt to execute the jobs in a way that all dependencies are finished before the job runs.


So yeah, that was our attempt to "quickly" spin up a Key Vault for the Power Platform. I hope this helps when your friend asks you for help.


If you are interested in Microsoft PowerPlatform, I highly recommend to check out David's Blog and Power DevBox.


Resources

💖 💪 🙅 🚩
sandr0p
sandr0-p

Posted on November 25, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

A KeyVault for the Power Platform
powerplatform A KeyVault for the Power Platform

November 25, 2024