Add certificate from Azure Keyvault to Azure Container Environment with Bicep - azure

I need a mechanism to download a .pfx certificate from Keyvault and to then upload it to an Azure Container Environment, all via Bicep. This will minimise any manual intervention when the certificate is updated.
I am currently adding a certificate to my Azure Container Environment using the base64 encoded value I manually converted using powershell. As follows:
resource certificate 'Microsoft.App/managedEnvironments/certificates#2022-06-01-preview' = {
parent: env
location: location
name: 'ta-cert'
properties: {
password: certificatePassword
value: '<base64>'
}
}
What I would like to try and achieve is to download the pfx file from Keyvault and convert to base64 (maybe by using a powershell command embedded in bicep) all within the Bicep file, which can then be used in the code above.
If anyone has done this before would be really grateful to see the implementation.

If your certificate is stored as a certificate in key vault, it is already base64 encoded and accessible as a key vault secret (see Composition of a Certificate).
You can use the bicep getSecret function to pass the certificate to the container app environment:
Use Azure Key Vault to pass secure parameter value during Bicep deployment
containerapp-env-certificate.bicep module:
param containerAppEnvName string
param location string = resourceGroup().location
param certificateName string
#secure()
param certificateValue string
resource containerAppEnv 'Microsoft.App/managedEnvironments#2022-03-01' existing = {
name: containerAppEnvName
}
resource certificate 'Microsoft.App/managedEnvironments/certificates#2022-06-01-preview' = {
parent: containerAppEnv
location: location
name: certificateName
properties: {
// Dont need password here
value: certificateValue
}
}
From your main.bicep template, you can invoke it like that:
param containerAppEnvName string
param location string = resourceGroup().location
param keyVaultName string
param keyVaultCertificateName string
// Get a reference to key vault
resource keyVault 'Microsoft.KeyVault/vaults#2019-09-01' existing = {
name: keyVaultName
}
module certificate 'containerapp-env-certificate.bicep' = {
name: 'containerapp-env-certificate'
params: {
containerAppEnvName: containerAppEnvName
certificateName: 'ta-cert'
location: location
// Get the certificate as a base64 secret
certificateValue: keyVault.getSecret(keyVaultCertificateName)
}
}

Related

Assign Data Reader Role between App Confiugration and App Service in Azure

i would like to do this steps App Configuration -> Access control (IAM) -> Add role assigment -> App Configuration Data Reader -> Assign access to Managed identity -> Select Members (choose my app service) -> Save but instead of using Azure Portal for that, I wanted to use ARM/Bicep template,
I tried something like this:
targetScope = 'resourceGroup'
param principalId string = 'x-x-x-x-x-x-x-x-x'
param roleDefinitionId string = 'x-x-x-x-x-x'
var roleAssignmentName = guid('/', principalId, roleDefinitionId)
resource roleAssignment 'Microsoft.Authorization/roleAssignments#2020-03-01-preview' = {
name: roleAssignmentName
properties: {
roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
principalId: principalId
}
}
But there are 2 problems with this solutions. Firstly, I am using this targetScope = resourceGroup which creates this Role inside RG, and then my App Confiugration just inherit it from RG. Probably, the proper solution would be to provide App Configuration name somewhere, so it would be used instead of scoping it to Resource Group.
Also, hard-coding principalId and roleDefinitionId like this feels pretty bad, but f.e I can't access principalID of my Web App by doing something like this:
resource webApp 'Microsoft.Web/sites#2022-03-01' existing = {
name: 'myUniqueWebAppName'
}
param principalId string = webApp.identity.principalId
as it says that This symbol cannot be referenced here. Only other parameters can be referenced in parameter default values.
Also, I don't know how to access roleDefinitionId, I know where to find it in Azure Portal, but no idea how to access it without hard-coding.
Few things :
You can specify the scope fo the roleAssignment using the scope property.
Role Id won't change so hardcoding roleId is not really an issue, you could alway pass it as a parameter as well.
If you create a module to do the role assignment, you would be able to inject the webapp principalId
you can create a module like that:
// app-configuration-role-assignment.bicep
param appConfigurationName string
param principalId string
param roleId string
// Get a reference to app config
resource appConfiguration 'Microsoft.AppConfiguration/configurationStores#2022-05-01' existing = {
name: appConfigurationName
}
// Grant permission
resource appConfigurationRoleAssignment 'Microsoft.Authorization/roleAssignments#2022-04-01' = {
name: guid(appConfiguration.id, roleId, principalId)
scope: appConfiguration
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
principalId: principalId
}
}
Then from your main you could invoke it and pass the webapp principalId:
// main.bicep
param appConfigurationName string
param webAppName string
// get a reference to webapp
resource webApp 'Microsoft.Web/sites#2022-03-01' existing = {
name: webAppName
}
module roleAssignment 'app-configuration-role-assignment.bicep' = {
name: 'app-configuration-role-assignment-to-webapp'
scope: resourceGroup()
params: {
appConfigurationName: appConfigurationName
principalId: webApp.identity.principalId
roleId: '516239f1-63e1-4d78-a4de-a74fb236a071' // App Configuration Data Reader
}
}

Azure bicep dependsOn for existing resource

From my "main" bicep module, I would like to reference an existing function that is created by a module called from the same "main" bicep. So used the following code:
resource functionApp 'Microsoft.Web/sites#2021-02-01' existing = {
name: functionAppName
scope: resourceGroup(subscriptionId, 'rg-365response-${env}-001')
}
I am then able to use properties from the "functionApp" resource variable to obtain the function key and store as a key vault secret as follows:
resource funcSecret 'Microsoft.KeyVault/vaults/secrets#2021-04-01-preview' = {
name: '${kvName}/funcAppKey'
properties: {
value: listKeys('${functionApp.id}/host/default', functionApp.apiVersion).functionKeys.default
}
}
However, when I run a resource group deployment and see the following error:
The Resource 'Microsoft.Web/sites/func-365response-int-001' under
resource group 'rg-365response-int-001' was not found
This is some kind of timing issue, I guess it's checking for the function app before the call to the module that creates it has had chance to complete.
If I run the "main" bicep module a second time, everything works okay.
It seems it's not possible to use the "dependsOn" syntax for a resource that is "existing".
Is there an alternative?
DependOns can only be used for resources defined in the same bicep file (ARM template).
When you use the existing keyword, it will compiled to a resourceId() or reference() by Bicep
You could create a module to create secret:
// key-vault-secret.bicep
param kvName string
param secretName string
#secure()
param secretValue string
resource kvSecret 'Microsoft.KeyVault/vaults/secrets#2021-04-01-preview' = {
name: '${kvName}/${secretName}'
properties: {
value: secretValue
}
}
Then from where you are creating your function, you could invoke it like that:
resource functionApp 'Microsoft.Web/sites#2021-03-01' = {
name: functionAppName
location: location
kind: 'functionapp'
...
}
// Add secret to KV
module functionKey 'key-vault-secret.bicep' = {
name: 'function-default-host-key'
scope: resourceGroup()
params:{
kvName: kvName
secretName: 'funcAppKey'
secretValue: listKeys('${functionApp.id}/host/default', functionApp.apiVersion).functionKeys.default
}
}
I think you are correct in that the listKeys() is called too early, you can't fix it with dependsOn unfortunately. There is a bit more explanation here: https://bmoore-msft.blog/2020/07/26/resource-not-found-dependson-is-not-working/
The only fix for this is to put the listKeys and the function into different modules and make sure you have dependsOs if the second module doesn't consume an input from the first.
The part that's not adding up for me is that you have an existing keyword on the resource in the sample above but you say you're creating it. The symptoms you describe also suggest you're creating it in the same deployment. If you are, they you don't need the `existing' keyword.
If all else fails - post all the code.

update vault secret using terraform

I'm trying to push terraform variable to vault using this recource
resource "vault_generic_secret" "secret" {
path = "mxv/terraform/machines/test"
data_json = <<EOT
{
"ip": "aws_instance.app_server.public_ip"
}
EOT
}
but in vault pushed variable name not value. Is there any way to push value from terraform to vault?

Bicep Generate GUID

I am learning about bicep and how to use to deploy azure resources.
I have been working on key vault deployment as follow:
resource keyVault 'Microsoft.KeyVault/vaults#2021-06-01-preview' existing = {
name: keyVaultName
}
// Create key vault keys
resource keyVaultKeys 'Microsoft.KeyVault/vaults/keys#2021-06-01-preview' = [for tenantCode in tenantCodes: {
name: '${keyVault.name}/${keyVaultKeyPrefix}${tenantCode}'
properties: {
keySize: 2048
kty: 'RSA'
// storage key should only needs these operations
keyOps: [
'unwrapKey'
'wrapKey'
]
}
}]
what I would like to do now, is create a GUID for each deployment in this format for example:
b402c7ed-0c50-4c07-91c4-e075694fdd30
I couldn't find any source to achieve this.
Can anyone please direct me on the right path.
Thank you very much for any help and for your patience with a beginner
You can use the newGuid function, as per documentation:
Returns a value in the format of a globally unique identifier. This function can only be used in the default value for a parameter.
// parameter with default value
param deploymentId string = newGuid()
...
output deploymentId string = deploymentId

Terraform Incapsula provider fails to create custom certificate resource

We are trying to use Terraform Incapsula privider to manage Imperva site and custom certificate resources.
We are able to create Imperva site resources but certificate resource creation fails.
Our use-case is to get the certificate from Azure KeyVault and import it to Imperva using Incapsula Privider. We get the certificate from KeyVault using Terraform "azurerm_key_vault_secret" data source. It returns the certificate as Base64 string that we pass as "certificate" parameter into Terraform "incapsula_custom_certificate" resource along with siteID that was created using Terraform "incapsula_site" resource. When we run "terraform apply" we get the error below.
incapsula_custom_certificate.custom-certificate: Creating...
Error: Error from Incapsula service when adding custom certificate for site_id ******807: {"res":2,"res_message":"Invalid input","debug_info":{"certificate":"invalid certificate or passphrase","id-info":"13007"}}
on main.tf line 36, in resource "incapsula_custom_certificate" "custom-certificate":
36: resource "incapsula_custom_certificate" "custom-certificate" {
We tried reading the certificate from PFX file in Base64 encoding using Terraform "filebase64" function, but we get the same error.
Here is our Terraform code:
provider "azurerm" {
version = "=2.12.0"
features {}
}
data "azurerm_key_vault_secret" "imperva_api_id" {
name = var.imperva-api-id
key_vault_id = var.kv.id
}
data "azurerm_key_vault_secret" "imperva_api_key" {
name = var.imperva-api-key
key_vault_id = var.kv.id
}
data "azurerm_key_vault_secret" "cert" {
name = var.certificate_name
key_vault_id = var.kv.id
}
provider "incapsula" {
api_id = data.azurerm_key_vault_secret.imperva_api_id.value
api_key = data.azurerm_key_vault_secret.imperva_api_key.value
}
resource "incapsula_site" "site" {
domain = var.client_facing_fqdn
send_site_setup_emails = true
site_ip = var.tm_cname
force_ssl = true
}
resource "incapsula_custom_certificate" "custom-certificate" {
site_id = incapsula_site.site.id
certificate = data.azurerm_key_vault_secret.cert.value
#certificate = filebase64("certificate.pfx")
}
We were able to import the same PFX certificate file using the same Site ID, Imperva API ID and Key by calling directly Imperva API from a Python script.
The certificate doesn't have a passphase.
Are we doing something wrong or is this an Incapsula provider issue?
Looking through the source code of the provider it looks like it is already performing a base64 encode operation as part of the AddCertificate function, which means using the Terraform filebase64 function is double-encoding the certificate.
Instead, I think it should look like this:
resource "incapsula_custom_certificate" "custom-certificate" {
site_id = incapsula_site.site.id
certificate = file("certificate.pfx")
}
If the returned value from azure is base64 then something like this could work too.
certificate = base64decode(data.azurerm_key_vault_secret.cert.value)
Have you tried creating a self-signed cert, converting it to PFX with a passphrase, and using that?
I ask because Azure's PFX output has a blank/non-existent passphrase, and I've had issues with a handful of tools over the years that simply won't import a PFX unless you set a passphrase.

Resources