Jan 25, 2022 9 min read

Power Platform environment variable secrets from Azure Key Vault: an improvement?

A look at the brand new Environment Variable of type secret in the Power Platform. Does it fulfill the expectations or does it have a little too much gotcha's?

Power Platform environment variable secrets from Azure Key Vault: an improvement?

A brand new feature surfaced recently on Power Platform: environment variables of type Secret which couple with an Azure Key Vault to store the actual value. This seems to be an attempt to make working with Power Automate more enterprise-ready than before, so I was curious to validate this.

The root of the problem is that flows in Power Automate run in the context of the signed-in user and also can’t work with a Managed Identity to access external resource. This means we also need to take care of storing, rotating and handling a secret or password, which would be abstracted away if we'd get Managed Identity support.

When Managed Identity support is lacking, first thought is " just store it in Azure Key Vault, it's safe and secure". It is for sure the best and most secure way to store the secret value, but now it's in an external system and you also need a secure way to access this and retrieve it so you can use it in your application. In Power Automate, this would be done with the Azure Key Vault connector but you still need a connection. This connection requires either a username/password or Service Principal (client id/client secret), and the same challenges exist: you are again responsible for managing the expiration/rotation of the secret (or password) and adjusting the connections accordingly. Not the favourite pass time of most administrators.

Just to mention it for the sake of a complete analysis, theoretically you could also:

  • Use the secret in plain text, worst option ever. The value of the secret would show in edit mode, as well as in the run history of the flow.
  • Store your secret value in a local variable, and mark it as Secure Input and Secure Output. Not a great option, everyone with edit access to the flow itself would still be able to see the value of the secret.

If Power Platform wants to be more enterprise-ready and not only personal productivity scoped, these options aren’t sufficient at all. All options still require the management of a secret value (password or client secret), and that's far from ideal.
I'm a big fan of Managed Identities (both system and user assigned), and the lack of support for this in Power Automate has me favouring Logic Apps for enterprise flows. Let’s check if Environment Variable Secrets can change my preference by offering a more hustle-free way to handle secrets and also still be secure.

What is new: Environment Variable Secrets

Configuration and usage

For a detailed walkthrough on how to make use of environment variables of type secret, there is the official documentation or the video Daniel Laskewitz made to demo it. I will not go through that again, but it is important to understand it a little bit before you can understand the remainder of the post.

Steps:

  1. Have an Azure Key Vault with a secret, assign an Get secret access policy to the Dataverse service principal (00000007-0000-0000-c000-000000000000)
  1. Create an environment variable of type secret, enter the correct information for the above Key Vault (subscription id, key vault name, resource group name and secret name)
  1. Use the Perform an unbound action action from the Dataverse connector to call the RetrieveEnvironmentVariableSecretValue action, and pass in the name of the Environment Value of type secret that you want to fetch
  1. Turn on Secure output to make sure the value of the secret does not show in the run history of the flow

How it works

Disclaimer: The actual innerworkings of this feature is not described anywhere, so all that follows is speculation derived from experience and educated guesses. It might not be correct at all 😉

An unbound action in Dataverse is a piece of code (much like a plugin), triggered by a Web API call, that accepts a set of inputs. You can create your own custom unbound actions, but in this case the action is provided by Microsoft.
As inputs it will take the name of the environment variable and then the code will:

  • fetch the contained information from that environment variable: Azure subscription id, Resource Group name, Key Vault name and secret name
  • use the Dataverse service principal to get an access token for the Key Vault. The service principal behaves similarly as a system-assigned managed identity, as they have the secret (and we don't)
  • use the access token to fetch the value of the secret from the Key Vault
  • return the value of the secret as a response to the caller

At that point you can make use of the secret value for whatever purpose you need it.

What I think of it

Usage

In my opinion, this new feature has no added value over using the Azure Key Vault connector. Environment variables of type secret swap the requirement for Key Vault connection reference with a Dataverse connection reference. Granted, you probably will need that Dataverse connection anyways for other functionality you are using but it still feels a little wrong to handle it like this. Both connection types support Service Principal connections: what is the added advantage of using a Service Principal to go to a backend that will use a system assigned managed identity to connect to Key Vault when you could connect directly with that same Service Principal?

Additionally, you still need (some type of) Get Secret action inside your flow so it's not taking care of this transparently and thus deals with the same issues as the Key Vault connector (see above). It would've been a different story if the Environment Variable would be populated on the fly on flow execution in such a way that you could use it as any other Environment Variable, and you don't have to bother with fetching the value with a separate action. In the perfect world, the secret value would be hidden to both the editor and the runner of the flow so the secret can not leak at all.

The Environment Variable way to fetch the secret value is, in my opinion, also inferior to using the Key Vault connector: in the Key Vault connector it shows you a list of available secrets to fetch, but the Dataverse action expects just a plain text input for the Environment Variable. This means that validation of the input doesn't happen at design time, only at runtime. It's a less-than-ideal UX that could lead to mistakes and frustration, for the Power Automate developer.

Configuration

Before you can use such an Environment Variable you will need to create it, and this process has it's own quirks and (undocumented) challenges as well.

The user (on the Power Platform side) that is creating the Key Vault secret reference needs to have at least Key Vault Reader, otherwise the reference cannot be saved and gives an error message: This variable didn't save properly. Could not verify the user permission on 'Azure Key Vault path' resource. Make sure that Microsoft.PowerPlatform provider is registered in the Azure subscription. It's a security measure to make sure that not everyone can create the reference to a secret by "guessing" the configuration values, but to make this a deliberate action. Afterwards, this permission isn't checked anymore at all, and everyone can use a flow to fetch the secret value. Again, this is no improvement over the Key Vault connector.

The creation process validates the inputted Azure Subscription Id, Resource Group Name and Azure Key Vault Name but ignores the Secret Name value. You can enter whatever you want in there, it just doesn't get checked. A typo is easily made and even quicker overlooked, having just a simple check to see if the input value exists as a secret in the Key Vault could save many frustrations.

Lastly, this whole thing only works with a Key Vault that exists in a subscription in the same Azure AD tenant as the Power Platform environment. This one is undocumented, and the reasoning for this limitation is unclear. Even inviting the Power Platform user as an Azure AD B2B guest in the other tenant AND assigning it Key Vault Reader permissions doesn't solve the issue. When you try this, you are met with the same message as before:  This variable didn't save properly. Could not verify the user permission on 'Azure Key Vault path' resource. Make sure that Microsoft.PowerPlatform provider is registered in the Azure subscription. . This time it's worse because the provider has nothing to do with the issue that is occurring. The same key vault will work when used in their tenant, it just doesn't work when accessed from a Dataverse environment homed in another tenant.

Application Lifecycle Management (ALM)

Application Lifecycle Management seems to be the unloved stepchild in Environment Variable secret feature as it's missing some basic functionality.
The creation process of the Environment Variable has a nice UI to provide the required information about the secret (in 4 separate fields) but when you import the solution in another environment, there is only one field to provide the value:

The import UI is a lot less intuitive than the creation UI, giving no indication on how the four original fields should be concatenated into one field. This is not in the official documentation, only in a follow-up blogpost. You can find the format by looking in the Environment Variable Value table, where we can determine the correct concatenation: /subscriptions/[SubscriptionId]/resourceGroups/[ResourceGroupName]/providers/Microsoft.KeyVault/vaults/[KeyVaultName]/secrets/[SecretName]

At the time of writing there is also no support for Environment Variables of type secret in the Power Platform Build tools for Azure DevOps and GitHub, which is another limiting factor. Microsoft is aware of this (they mention it in their docs themselves), so we can only hope this will be remediated very soon.

There are some good things too, for this feature, in relation to Application Lifecycle Management and in particular when you include custom connectors in your solutions. In a recent update, Microsoft enabled the possibility of using environment variables when creating a custom connector in the host and basePath fields, as well as in the security settings for fields like Client Id, ClientSecret, Login URL and Resource Url.

It supports the usage of Environment Variables of type secret in the Client Secret field, which finally gives it an added value over the Key Vault connector. When used in this way, the secret value will be retrieved for you transparently (so no additional unbound action call necessary) but only on saving the connector (either at creation or when doing a solution import). Unfortunately this means that when the secret gets updated in the Key Vault, it still requires an "update" of the connector in your environment... Close, but no cigar.

Bonus: Web API

The whole feature is based on using an Unbound Action within Power Platform to fetch the value from the Azure Key Vault. This also means that the usage isn't limited to flows in Power Automate, but the secret can also be retrieved using a Web API call:

I don't completely understand the security permissions required to call an unbound action, but it feels wrong that using Power Platform functionality can potentially expose the secret value in many other locations. It looks like a potential risk...

Conclusion

The current iteration of Environment Variable secrets has a too many issues and limitations to my liking. I can only hope this is the first step in a bigger roadmap that will make clear why this feature exists. As it stands, I don't yet see the added value over the existing Key Vault Connector and only a lot of issues that need to be ironed out before this feature reaches maturity. In conclusion, right now this is adding more potential for harm than value and I wouldn't recommend using it. One could also reason that if a 'Power User' needs to deal with this level of complexity (that's already difficult for experienced developers to understand) that you have much bigger problems.

I guess I am sticking to Logic Apps for a bit longer.

Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Yannick Reekmans.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.