Logo

dev-resources.site

for different kinds of informations.

A KeyVault for the Power Platform

Published at
11/25/2024
Categories
powerplatform
azure
devops
bicep
Author
sandr0p
Categories
4 categories in total
powerplatform
open
azure
open
devops
open
bicep
open
Author
7 person written this
sandr0p
open
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

bicep Article's
30 articles in total
Favicon
Deploying and Configuring a Hybrid Identity Lab Using Bicep - Part 1: Active Directory Setup and Sync
Favicon
How to setup an Azure Machine Learning Workspace securelyπŸ›‘οΈπŸ”’πŸ”‘
Favicon
Creating a Custom Role for Secure Bicep Deployments in Azure
Favicon
Create a GitHub pipeline to test, review, and deploy a Bicep template.
Favicon
A KeyVault for the Power Platform
Favicon
Kickstart projects with azd Templates
Favicon
Conditional deployment in Azure Bicep
Favicon
Rush configuration
Favicon
user-defined type in Azure Bicep, an introduction
Favicon
Set version numbers in Bicep templates
Favicon
Securing your Azure deployments with PSRule
Favicon
Versioned Bicep templates- Deployment
Favicon
Change Management in Infrastructure as a Code (IaC)
Favicon
Azure Verified Modules: Consolidated Standards for a Good IaC
Favicon
Getting Started with Azure Bicep
Favicon
Using Azure Bicep to deploy MS Graph resources
Favicon
Deploying static webs apps with the Azure cli and bicep
Favicon
Azure API Management: Harnessing Bicep for Effortless User and Subscription Creation
Favicon
User-defined function in Azure Bicep
Favicon
Expose your Open API specs with Azure API management
Favicon
Deploy multiple APIs in Azure API management, hosted in the same App service.
Favicon
Add Azure Developer CLI deployment ID and UTC timestamp to Bicep files
Favicon
🦾 Top 5 Azure Bicep tips & tricks to get started πŸš€
Favicon
Exploring the awesome Bicep Test Framework πŸ§ͺ
Favicon
The issue of recursive module calls in declarative infrastructure-as-code
Favicon
Azure Bicep - Finally functions to manipulate CIDRs
Favicon
Multi Scopes Deployment with Azure Bicep
Favicon
Azure Deployment Stacks, deploy and manage a landing zone with Bicep
Favicon
Azure Open AI: handling capacity and quota limits with Bicep
Favicon
Learn bicep based on the GUI of Azure Portal

Featured ones: