Azure synapse linked service for Azure Function in Terraform - azure

I'm writing a terraform script to create a azure synapse workspace.
I've created a linked service for Azure Function but I'm unable to use it in pipeline, where it gives me an error of missing function key.
This is what i'm using now. I'm sure the problem is in the type_properties_json parameter.
resource "azurerm_synapse_linked_service" "FunctionName" {
name = "FunctionName"
synapse_workspace_id = azurerm_synapse_workspace.synapse.id
type = "AzureFunction"
type_properties_json = <<JSON
{
"functionAppUrl": "https://${data.azurerm_function_app.FunctionName.default_hostname}",
"authentication": "Anonymous",
"functionKey": "${data.azurerm_function_app_host_keys.FunctionName.default_function_key}"
}
JSON
depends_on = [
azurerm_synapse_firewall_rule.allowAll,
data.azurerm_function_app.FunctionName,
data.azurerm_function_app_host_keys.FunctionName
]
}
And this does create a linked service but when i use it in a pipeline, the run fails with the error
Azure function activity missing function key.
It appears to me after checking the output for azurerm_function_app there is no export for connectionString.

I tried to reproduce the scenario in my environment.
Tried below code:
resource "azurerm_synapse_linked_service" "example" {
name = "kavya-fnapplinked"
synapse_workspace_id = azurerm_synapse_workspace.example.id
type = "AzureFunction"
type_properties_json = <<JSON
{
"functionAppUrl": "https://${data.azurerm_function_app.example.default_hostname}",
"authentication": "Anonymous",
"functionKey": "${data.azurerm_function_app_host_keys.example.default_function_key}"
}
JSON
depends_on = [
azurerm_synapse_firewall_rule.allowAll,
data.azurerm_function_app.example,
data.azurerm_function_app_host_keys.example
]
}
Got errors due to the json property and function key not being generated as expected as they are not in correct format.
For this ,note two important points:
Function key is only generated after the function app is created first and is a sensitive value.
Json format for sensitive values must be in below format
"secret":
{
"type": "SecureString",
"value": “{value}"
}
I have first stored the function key in keyvalut , so that it takes time to generate and get stored in secret.
Code:
resource "azurerm_role_assignment" "role_assignment" {
scope = azurerm_storage_account.stfn.id
role_definition_name = "Storage Blob Data Owner"
principal_id = data.azurerm_client_config.current.object_id
}
# used Sleep to wait for role assignment to take its time to propagate
resource "time_sleep" "role_assignment_sleep" {
create_duration = "60s"
triggers = {
role_assignment = azurerm_role_assignment.role_assignment.id
}
}
resource "azurerm_storage_data_lake_gen2_filesystem" "example" {
name = "kavdatalakexample123"
storage_account_id = azurerm_storage_account.stfn.id
depends_on = [time_sleep.role_assignment_sleep]
}
resource "azurerm_synapse_workspace" "example" {
name = "exmple-workspace"
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_storage_account.example.location
storage_data_lake_gen2_filesystem_id = azurerm_storage_data_lake_gen2_filesystem.example.id
sql_administrator_login = "sqladminuser"
sql_administrator_login_password = "H#Sh1CoR3!"
managed_virtual_network_enabled = true
identity {
type = "SystemAssigned"
}
}
resource "azurerm_synapse_firewall_rule" "allowAll" {
name = "allowAll"
synapse_workspace_id = azurerm_synapse_workspace.example.id
start_ip_address = "0.0.0.0"
end_ip_address = "255.255.255.255"
}
resource "azurerm_storage_account" "stfn" {
name = "kaexpleaccforfunct"
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_storage_account.example.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_app_service_plan" "example" {
name = "exm-kavya-app-service-plan"
resource_group_name = data.azurerm_resource_group.example.name
location =data.azurerm_storage_account.example.location
kind = "FunctionApp"
sku {
tier = "Dynamic"
size = "Y1"
}
}
resource "azurerm_function_app" "example" {
name = "exm-kavya-function-app"
resource_group_name = data.azurerm_resource_group.example.name
location = data.azurerm_resource_group.example.location
// storage_connection_string = azurerm_storage_account.stfn.primary_connection_string
storage_account_name = azurerm_storage_account.stfn.name
storage_account_access_key = azurerm_storage_account.stfn.primary_access_key
app_service_plan_id = azurerm_app_service_plan.example.id
}
data "azurerm_function_app_host_keys" "example" {
resource_group_name = data.azurerm_resource_group.example.name
name= azurerm_function_app.example.name
}
output "function_key" {
value = data.azurerm_function_app_host_keys.example.default_function_key
sensitive = true
}
output "function_appurl" {
value = "https://${azurerm_function_app.example.default_hostname}"
}
resource "azurerm_key_vault" "example" {
name = "kavyaexamplekeyvault"
location = data.azurerm_resource_group.example.location
resource_group_name = data.azurerm_resource_group.example.name
enabled_for_disk_encryption = true
tenant_id = data.azurerm_client_config.current.tenant_id
soft_delete_retention_days = 7
purge_protection_enabled = false
sku_name = "standard"
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"Create",
"Get",
]
secret_permissions = [
"Set",
"Get",
"Delete",
"Purge",
"Recover",
"List"
]
storage_permissions = [
"Get","Set"
]
}
}
resource "azurerm_key_vault_secret" "example" {
name = "functionkey"
value = data.azurerm_function_app_host_keys.example.default_function_key
key_vault_id = azurerm_key_vault.example.id
}
resource "azurerm_synapse_linked_service" "example" {
name = "kav-fnapplinked"
synapse_workspace_id = azurerm_synapse_workspace.example.id
type = "AzureFunction"
type_properties_json = <<JSON
{
"functionAppUrl": "https://${azurerm_function_app.example.default_hostname}",
"authentication": "Anonymous",
"functionKey":
{
"type": "SecureString",
"value": "${azurerm_key_vault_secret.example.value}"
}
"authentication": "Anonymous",
"functionKey":
{
"type": "SecureString",
"value": "${azurerm_key_vault_secret.example.value}"
}
}
JSON
depends_on = [
azurerm_synapse_firewall_rule.allowAll,
azurerm_function_app.example,
data.azurerm_function_app_host_keys.example
]
}
With abovecode I could successfully, create linked service.
Linked service for azure synapse workspace:
Reference: azure - Terraform issue creating the resource "azurerm_synapse_linked_service" specifically with the "type_properties_json" field - Stack Overflow

Related

Error Key Vault object_id is an invalid UUID - Terraform/Azure

I'm deploying an Azure Application Gateway in Terraform and I want to store my SSL private certificate for the https between Internet and my App-gtw in an Azure Key Vault.
The code, omitting useless information in the application gateway module, is:
module "agw_user_assigned_identity" {
source = "../modules/resources-blocks/user_assigned_identity"
user_assigned_identity_name = "agw-user-signed-id"
resource_group_name = module.resource_group.name
resource_group_location = module.resource_group.location
}
module "key_vault" {
source = "../modules/resources/key_vault"
key_vault_name = local.key_vault_name
resource_group_location = module.resource_group.location
resource_group_name = module.resource_group.name
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = module.agw_user_assigned_identity.id
soft_delete_retention_days = 90
log_analytics_workspace_id = module.log_analytics_workspace.id
enable_diagnostic_setting = true
}
module "key_vault_private_certificate" {
source = "../modules/resources-blocks/key_vault_certificate"
key_vault_id = module.key_vault.id
certificate_name = local.agw_certificate_name
certificate_path = "./certificates/xxxxx.pfx"
certificate_password = var.SSL_CERTIFICATE_PASSWORD
}
resource "null_resource" "previous" {}
module "agw_time_sleep" {
source = "../modules/resources-blocks/time_sleep"
select_module = module.key_vault
seconds = "200s"
}
module "application_gateway" {
source = "../modules/resources-hub/application_gateway"
resource_group_name = module.resource_group.name
resource_group_location = module.resource_group.location
application_gateway_name = local.agw_name
key_vault_private_certificate_id = module.key_vault_private_certificate.certificate_id
key_vault_private_certificate_id = azurerm_key_vault_certificate.kv_certificate.secret_id
ssl_certificate_name = local.agw_certificate_name
agw_time_sleep = module.agw_time_sleep.id
frontend_ports = [
{
name = "myFrontendPort"
port = 443
}
]
http_listeners = [
{
name = "devListener"
frontend_ip_configuration = local.frontend_ip_configuration_name
frontend_port_name = "myFrontendPort"
protocol = "Https"
hostname = "xxxxxxxxxxxxx.be"
ssl_certificate_name = local.agw_certificate_name
}
]
}
The key_vault resource is:
resource "azurerm_key_vault" "kv" {
name = var.key_vault_name
location = var.resource_group_location
resource_group_name = var.resource_group_name
enabled_for_disk_encryption = true
tenant_id = var.tenant_id
soft_delete_retention_days = var.soft_delete_retention_days
purge_protection_enabled = false
sku_name = "standard"
access_policy {
tenant_id = var.tenant_id
object_id = var.object_id
key_permissions = ["Get", "List", "Update", "Create", "Import", "Delete", "Recover", ]
secret_permissions = ["Get", "List", "Set", "Delete", "Recover", "Backup", "Restore", "Purge"]
storage_permissions = ["Get", "Set", "Delete", "Recover", "Backup", "Restore"]
certificate_permissions = ["Get", "List", "Update", "Create", "Import", "Delete", "Recover", "Backup", "Restore", "Purge"]
}
lifecycle {
ignore_changes = [access_policy]
}
}
The key_vault_certificate resource is:
resource "azurerm_key_vault_certificate" "kv_certificate" {
name = var.certificate_name
key_vault_id = var.key_vault_id
certificate {
contents = filebase64(var.certificate_path)
password = var.certificate_password
}
certificate_policy {
issuer_parameters {
name = "Self"
}
key_properties {
exportable = true
key_size = 2048
key_type = "RSA"
reuse_key = false
}
secret_properties {
content_type = "application/x-pkcs12"
}
}
}
The application gateway resource is (omitting useless information):
resource "azurerm_application_gateway" "app_gw" {
name = var.application_gateway_name
resource_group_name = var.resource_group_name
location = var.resource_group_location
identity {
type = "UserAssigned"
identity_ids = [var.user_assigned_identity_id]
}
ssl_certificate {
name = var.ssl_certificate_name
key_vault_secret_id = var.key_vault_private_certificate_id
}
dynamic "http_listener" {
for_each = var.http_listeners
content {
name = http_listener.value.name
frontend_ip_configuration_name = http_listener.value.frontend_ip_configuration
frontend_port_name = http_listener.value.frontend_port_name
protocol = http_listener.value.protocol
host_name = http_listener.value.hostname
firewall_policy_id = var.firewall_policy_id
ssl_certificate_name = http_listener.value.ssl_certificate_name
}
}
depends_on = [var.agw_time_sleep]
}
The error that I get when I use the command terraform plan -var-file="variables.tfvars" is:
Error: expected "access_policy.0.object_id" to be a valid UUID, got /subscriptions/xxxxxxxx/resourceGroups/xxxxxxxxx/providers/Microsoft.ManagedIdentity/userAssignedIdentities/agw-user-signed-id
│
│ with azurerm_key_vault.kv,
│ on main.tf line 344, in resource "azurerm_key_vault" "kv":
│ 344: object_id = module.agw_user_assigned_identity.id
Apparently seems that the problem is related to the object_id that I specify in the key_vault, but I don't know how to solve it.
Error Explanation
Error: expected "access_policy.0.object_id" to be a valid UUID .......
with azurerm_key_vault.kv,
│ on main.tf line 344, in resource "azurerm_key_vault" "kv":
│ 344: object_id = module.agw_user_assigned_identity.id
This means that in your resource azurerm_key_vault in the access_policy block the object_id attribute is getting the incorrect value more simply the incorrect value that azure API accepts for it.
It expects the principal_id output from the azurerm_user_assigned_identity resource in spite of id.
in azure id would be the URI of the resource itself in azure namespaces /subscriptions/<sub-id>/resourceGroups/<resource-group-name>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<resource-name>
So you need principal_id output in your module module.agw_user_assigned_identity and then use it in your key_vault module as follows
Terraform code
## inside module.agw_user_assigned_identity add an output , ignore if already exists
output "principal_id" {
value = azurerm_user_assigned_identity.base.principal_id
description = "Set accordingly or from terraform documentation"
}
## then use the above the output in key_vault module
module "key_vault" {
source = "../modules/resources/key_vault"
key_vault_name = local.key_vault_name
resource_group_location = module.resource_group.location
resource_group_name = module.resource_group.name
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = module.agw_user_assigned_identity.principal_id
soft_delete_retention_days = 90
log_analytics_workspace_id = module.log_analytics_workspace.id
enable_diagnostic_setting = true
}
That will solve the above error message.

Azure VM: AzureADJoined is "No" even after enabling the AADLoginForWindows extension

I have created a VM in Azure as mentioned below
resource "azurerm_windows_virtual_machine" "virtual_machine_hub" {
name = "vm-hub"
resource_group_name = azurerm_resource_group.ipz12-dat-np-connection-rg.name
location = azurerm_resource_group.ipz12-dat-np-connection-rg.location
size = "Standard_B8ms"
admin_username = "xxxxx"
admin_password = "xxxxx"
network_interface_ids = [
azurerm_network_interface.virtual_machine_hub_nic.id
]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsDesktop"
offer = "Windows-10"
sku = "21h1-pro"
version = "latest"
}
depends_on = [
azurerm_network_interface.virtual_machine_hub_nic
]
}
and enabled the AADLoginForWindows extension
resource "azurerm_virtual_machine_extension" "virtual_machine_hub_ext" {
name = "AADLoginForWindows"
virtual_machine_id = azurerm_windows_virtual_machine.virtual_machine_hub.id
type = "AADLoginForWindows"
type_handler_version = "1.0"
auto_upgrade_minor_version = true
publisher = "Microsoft.Azure.ActiveDirectory"
depends_on = [
azurerm_windows_virtual_machine.virtual_machine_hub
] }
however "dsregcmd /status" command shows that it is not connected with Azure AD domain like AzureADJoined is "No"
In order to register the VM in Azure AD, I don't feel that I have the appropriate permissions. If that's the case, what level of permission is required? and what am I missing?
Note: I have manually joined Azure AD like mentioned below
As discussed here, it is described that aadj private extension also to be created for a virtual machine under path:
HKLM\SOFTWARE\Microsoft\RDInfraAgent\AADJPrivate
So, another Custom script extension was added to add the key AADJPRIVATE for the VM.
Followed this template given by #Ansuman Bal in SO thread for Azure AD VM join and made a few changes to achieve the expected results as per your requirements.
vi main.tf:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.37.0"
}
}
}
provider "azurerm" {
features {}
}
provider "azuread" {}
data "azuread_group" "aad_group" {
display_name = "<ADGroup>"
security_enabled = true
}
data "azurerm_role_definition" "vm_user_login" {
name = "<VM User Login>"
}
resource "azurerm_role_assignment" "vm_user_role" {
scope = azurerm_resource_group.rg-xxx.id
role_definition_id = data.azurerm_role_definition.vm_user_login.id
principal_id = data.azuread_group.aad_group.id
}
data "azurerm_role_definition" "desktop_user" {
name = "xxxxxx User"
}
resource "azurerm_role_assignment" "desktop_role" {
scope = azurerm_virtual_desktop_application_group.desktopapp.id
role_definition_id = data.azurerm_role_definition.desktop_user.id
principal_id = data.azuread_group.aad_group.id
}
resource "azurerm_resource_group" "eg-RG" {
name = "xxxxxtest"
location = "West Europe"
}
resource "azurerm_virtual_network" "vnet" {
name = "xxxx-vnet"
location = azurerm_resource_group.eg-RG.location
resource_group_name = azurerm_resource_group.eg-RG.name
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "xxxxdefaultSubnet" {
name = "xxxxxsubnet"
resource_group_name = azurerm_resource_group.eg-RG.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.0.0/24"]
}
resource "azurerm_network_security_group" "nsg" {
name = "xxxx-nsg"
location = azurerm_resource_group.eg-RG.location
resource_group_name = azurerm_resource_group.eg-RG.name
security_rule {
name = "allow-rdp"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = 3389
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
resource "azurerm_subnet_network_security_group_association" "nsg_association" {
subnet_id = azurerm_subnet.xxxxdefaultSubnet.id
network_security_group_ID = azurerm_network_security_group.<nsg>.id
}
resource "time_rotating" Gen_token" {
rotation_days = 30
}
resource "azurerm_virtual_desktop_host_pool" "new-hp" {
location = azurerm_resource_group.eg-RG.location
resource_group_name = azurerm_resource_group.eg-RG.name
name = "xxxxxhostpool"
friendly_name = "samplepool"
validate_environment = true
start_vm_on_connect = true
custom_rdp_properties = "audiocapturemode:i:1;audiomode:i:0;targetisaadjoined:i:1;"
description = "host-poool demo"
type = "Pooled"
maximum_sessions_allowed = 10
load_balancer_type = "DepthFirst"
}
resource "azurerm_virtual_desktop_host_pool_registration_info" "reginfo" {
hostpool_id = azurerm_virtual_desktop_host_pool.new-hp.id
expiration_date = time_rotating.avd_token.rotation_rfc3339
}
resource "azurerm_virtual_desktop_application_group" "desktopapp" {
name = "xxxx-Desktop"
location = azurerm_resource_group.eg-RG.location
resource_group_name = azurerm_resource_group.eg-RG.name
type = "Desktop"
host_pool_id = azurerm_virtual_desktop_host_pool.new-hp.id
friendly_name = "xxxxx"
description = "xxxxapplications"
}
resource "azurerm_virtual_desktop_workspace" "workspace" {
name = "xxxxxx-WORKSPACE"
location = azurerm_resource_group.eg-RG.location
resource_group_name = azurerm_resource_group.eg-RG.name
friendly_name = "xxxxxxx"
description = "Purporse"
}
resource "azurerm_virtual_desktop_workspace_application_group_association" "sampleworkspaceremoteapp" {
workspace_id = azurerm_virtual_desktop_workspace.workspace.id
application_group_id = azurerm_virtual_desktop_application_group.desktopapp.id
}
resource "azurerm_network_interface" "xxxx_nic" {
count=2
name = "xxxx-${count.index}"
location = azurerm_resource_group.eg-RG.location
resource_group_name = azurerm_resource_group.eg-RG.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.xxxxxdefaultSubnet.id
private_ip_address_allocation = "Dynamic"
}
}
resource "azurerm_windows_virtual_machine" "sessionhost" {
depends_on = [
azurerm_network_interface.sessionhost_nic
]
count=2
name = "xxxvm-${count.index}"
resource_group_name = azurerm_resource_group.eg-RG.name
location = azurerm_resource_group.eg-RG.location
size = "Standard_B2MS"
admin_username = "useradmin"
admin_password = "<Password>"
provision_vm_agent = true
network_interface_ids = [azurerm_network_interface.sessionhost_nic.*.id[count.index]]
identity {
type = "SystemAssigned"
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsDesktop"
offer = "Windows-10"
sku = "20h2-evd"
version = "latest"
}
}
locals {
registration_token = "${azurerm_virtual_desktop_host_pool_registration_info.reginfo.token}"
shutdown_command = "shutdown -r -t 10"
exit_code_hack = "exit 0"
commandtorun = "New-Item -Path HKLM:/SOFTWARE/Microsoft/RDInfraAgent/AADJPrivate"
powershell_command = "${local.commandtorun}; ${local.shutdown_command}; ${local.exit_code_hack}"
}
resource "azurerm_virtual_machine_extension" "xxxAVDModule" {
depends_on = [
azurerm_windows_virtual_machine.xxx_sessionhost
]
count = 2
name = "Microsoft.PowerShell.DSC"
virtual_machine_id = azurerm_windows_virtual_machine.avd_sessionhost.*.id[count.index]
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.73"
settings = <<-SETTINGS
{
"modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_11-22-2021.zip",
"ConfigurationFunction": "Configuration.ps1\\AddSessionHost",
"Properties" : {
"hostPoolName" : "${azurerm_virtual_desktop_host_pool.new-hp.name}",
"aadJoin": true
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"properties": {
"registrationInfoToken": "${azurerm_virtual_desktop_host_pool_registration_info.reginfo.token}"
}
}
PROTECTED_SETTINGS
}
resource "azurerm_virtual_machine_extension" "AADLoginForWindows" {
depends_on = [
azurerm_windows_virtual_machine.xxxx_sessionhost,
azurerm_virtual_machine_extension.AVDModule
]
count = 2
name = "AADLoginForWindows"
virtual_machine_id = azurerm_windows_virtual_machine.avd_sessionhost.*.id[count.index]
publisher = "Microsoft.Azure.ActiveDirectory"
type = "AADLoginForWindows"
type_handler_version = "1.0"
auto_upgrade_minor_version = true
}
resource "azurerm_virtual_machine_extension" "xxxxaadjprivate" {
depends_on = [
azurerm_virtual_machine_extension.AADLoginForWindows
]
count = 2
name = "AADJPRIVATE"
virtual_machine_id = azurerm_windows_virtual_machine.avd_sessionhost.*.id[count.index]
publisher = "Microsoft.Compute"
type = "CustomScriptExtension"
type_handler_version = "1.9"
settings = <<SETTINGS
{
"commandToExecute": "powershell.exe -Command \"${local.powershell_command}\""
}
SETTINGS
}
terraform plan:
terraform apply:
VM Joined in AD after deployment:
Virtual Machine Overview:

Creating Azure Data Factory Linked Service with Terraform Creates Link in Live Mode

When I create a linked service in Azure Data Factory (ADF) for Databricks with terraform (using azurerm_data_factory_linked_service_azure_databricks) the linked service shows up only in live mode.
How can I make the linked service available in GIT mode where all the other ADF pipeline configurations are stored?
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=2.97.0"
}
databricks = {
source = "databrickslabs/databricks"
}
}
}
provider "azurerm" {
features {}
}
provider "databricks" {
host = azurerm_databricks_workspace.this.workspace_url
}
data "azurerm_client_config" "this" {
}
resource "azurerm_data_factory" "this" {
name = "myadf-9182371362"
resource_group_name = "testrg"
location = "East US"
identity {
type = "SystemAssigned"
}
vsts_configuration {
account_name = "mydevopsorg"
branch_name = "main"
project_name = "adftest"
repository_name = "adftest"
root_folder = "/adf/"
tenant_id = data.azurerm_client_config.this.tenant_id
}
}
resource "azurerm_databricks_workspace" "this" {
name = "mydbworkspace"
resource_group_name = "testrg"
location = "East US"
sku = "standard"
}
data "databricks_node_type" "smallest" {
local_disk = true
depends_on = [
azurerm_databricks_workspace.this
]
}
data "databricks_spark_version" "latest_lts" {
long_term_support = true
depends_on = [
azurerm_databricks_workspace.this
]
}
resource "databricks_cluster" "this" {
cluster_name = "Single Node"
spark_version = data.databricks_spark_version.latest_lts.id
node_type_id = data.databricks_node_type.smallest.id
autotermination_minutes = 20
spark_conf = {
"spark.databricks.cluster.profile" : "singleNode"
"spark.master" : "local[*]"
}
depends_on = [
azurerm_databricks_workspace.this
]
custom_tags = {
"ResourceClass" = "SingleNode"
}
}
data "azurerm_resource_group" "this" {
name = "testrg"
}
resource "azurerm_role_assignment" "example" {
scope = data.azurerm_resource_group.this.id
role_definition_name = "Contributor"
principal_id = azurerm_data_factory.this.identity[0].principal_id
}
resource "azurerm_data_factory_linked_service_azure_databricks" "msi_linked" {
name = "ADBLinkedServiceViaMSI"
data_factory_id = azurerm_data_factory.this.id
resource_group_name = "testrg"
description = "ADB Linked Service via MSI"
adb_domain = "https://${azurerm_databricks_workspace.this.workspace_url}"
existing_cluster_id = databricks_cluster.this.id
msi_work_space_resource_id = azurerm_databricks_workspace.this.id
}
result in git mode
result in live mode

Terraform: How to create block with dynamic and static content

For a resource, how can I create a block that has both dynamic and static content? For the example below, all my azure key vaults will have a standard set of access policies, and a few have one or more additional policies. For this test key vault, I want to apply the dynamic block of access policies, as well as add a specific policy unique to this key vault only.
I've tried various ways to combine the two, but no luck.
resource "azurerm_key_vault" "key_vault-test" {
name = "kv-test"
location = azurerm_resource_group.rg-webapps.location
resource_group_name = azurerm_resource_group.rg-webapps.name
sku_name = "standard"
tenant_id = data.azurerm_client_config.current.tenant_id
dynamic "access_policy" {
for_each = var.keyvault_accesspolicies
content {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = access_policy.value["object_id"]
certificate_permissions = access_policy.value["certificate_permissions"]
key_permissions = access_policy.value["key_permissions"]
secret_permissions = access_policy.value["secret_permissions"]
}
}
access_policy = [
{
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = "<some guid>"
application_id = ""
certificate_permissions = []
key_permissions = []
secret_permissions = [
"Get"
]
storage_permissions = []
}
]
}
You are declaring static access policy in a wrong way . There shouldn't be an "=[" after access policy .
I tried with the below code and it successfully got added :
provider "azurerm" {
features {}
}
variable "keyvault_accesspolicies" {
default={
one ={
object_id="objectID1"
certificate_permissions=["Get"]
key_permissions=["Get"]
secret_permissions=["Get"]
},
second={
object_id="objectid2"
certificate_permissions=["Get","List"]
key_permissions=["Get","List"]
secret_permissions=["Get","List"]
}
}
}
data "azurerm_client_config" "current" {}
data "azurerm_resource_group" "name" {
name = "ansumantest"
}
resource "azurerm_key_vault" "key_vault-test" {
name = "ansumankvtest12"
location = data.azurerm_resource_group.name.location
resource_group_name = data.azurerm_resource_group.name.name
sku_name = "standard"
tenant_id = data.azurerm_client_config.current.tenant_id
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
secret_permissions = ["Get"]
}
dynamic "access_policy" {
for_each = var.keyvault_accesspolicies
content {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = access_policy.value["object_id"]
certificate_permissions = access_policy.value["certificate_permissions"]
key_permissions = access_policy.value["key_permissions"]
secret_permissions = access_policy.value["secret_permissions"]
}
}
}
Output:

terraform on azure - create keyvault with private connection

Would like to get some pointers on setting up a key vault with a private connection. Looking at the examples on the TF site and other sites I put this together but it crashes.
In short, it creates the KV, assigns some policies, and then creates the private link which is in turn associated with the service endpoint. Any help would be greatly appreciated.
locals {
prefix = "kv01am"
}
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "sandbox" {
name = "${local.prefix}-KV"
location = "eastus2"
resource_group_name = "rg-hsc-uscodappname01-137941ad"
enabled_for_disk_encryption = true
tenant_id = data.azurerm_client_config.current.tenant_id
# soft_delete_enabled = true
# purge_protection_enabled = false
sku_name = "standard"
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"get",
]
secret_permissions = [
"get",
]
storage_permissions = [
"get",
]
}
network_acls {
default_action = "Deny"
bypass = "AzureServices"
}
}
resource "azurerm_private_link_service" "example" {
name = "kv-privatelink"
location = "eastus2"
resource_group_name = "rg-hsc-uscodappname01-137941ad"
nat_ip_configuration {
name = azurerm_public_ip.example.name
primary = true
subnet_id = "zzzzzzzzzzzzzzzzzzzzzzzz"
}
}
resource "azurerm_private_endpoint" "sandbox_kv" {
name = azurerm_key_vault.sandbox.name
location = "eastus2"
resource_group_name = "rg-hsc-uscodappname01-137941ad"
#subnet_id = azurerm_subnet.sandbox["PrivateLink"].id
subnet_id = "zzzzzzzzzzzzzzzz"
private_service_connection {
name = azurerm_key_vault.sandbox.name
private_connection_resource_id = azurerm_key_vault.sandbox.id
is_manual_connection = false
subresource_names = ["Vault"]
}
}
Instead of creating dns record "manually" you could have a private_dns_zone_group declared.
# ============PrivateLink==========================
resource "azurerm_private_endpoint" "pe_kv" {
name = format("pe-2%s", var.name)
location = data.azurerm_resource_group.main.location
resource_group_name = data.azurerm_resource_group.main.name
subnet_id = data.azurerm_subnet.main.id
private_dns_zone_group {
name = "privatednszonegroup"
private_dns_zone_ids = [azurerm_private_dns_zone.main.id]
}
private_service_connection {
name = format("pse-2%s", var.name)
private_connection_resource_id = azurerm_key_vault.main.id
is_manual_connection = false
subresource_names = ["Vault"]
}
}
resource "azurerm_private_dns_zone" "main" {
name = "privatelink.vaultcore.azure.net"
resource_group_name = data.azurerm_resource_group.main.name
}
This is what I ended up doing. Could not find a good way to derive the ip address for the private link endpoint so I just hard coded it, if someone has a better way to handle this that would be great, not too much literature on that subject. Also, added a section to register the A record in private DNS but beware this creates a DNS Private zone in the same subnet as the kv.
data "azurerm_resource_group" "main" {
name = var.resource_group_name
}
data "azurerm_subnet" "main" {
name = var.virtual_network_subnet_name
virtual_network_name = var.virtual_network_name
resource_group_name = var.vnet_resource_group_name
}
data "azurerm_client_config" "main" {}
resource "azurerm_key_vault" "main" {
name = var.name
location = data.azurerm_resource_group.main.location
resource_group_name = data.azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.main.tenant_id
enabled_for_deployment = var.enabled_for_deployment
enabled_for_disk_encryption = var.enabled_for_disk_encryption
enabled_for_template_deployment = var.enabled_for_template_deployment
# soft_delete_enabled = false
# purge_protection_enabled = false
sku_name = var.sku
network_acls {
default_action = "Deny"
bypass = "AzureServices"
# ip_rules = var.ip_rules
}
# ============PrivateLink==========================
resource "azurerm_private_endpoint" "pe_kv" {
name = format("pe-2%s", var.name)
location = data.azurerm_resource_group.main.location
resource_group_name = data.azurerm_resource_group.main.name
subnet_id = data.azurerm_subnet.main.id
private_service_connection {
name = format("pse-2%s", var.name)
private_connection_resource_id = azurerm_key_vault.main.id
is_manual_connection = false
subresource_names = ["Vault"]
}
}
resource "azurerm_private_dns_zone" "main" {
name = "privatelink.vaultcore.azure.net"
resource_group_name = data.azurerm_resource_group.main.name
}
resource "azurerm_private_dns_a_record" "pe_kv" {
name = var.name
zone_name = azurerm_private_dns_zone.main.name
resource_group_name = data.azurerm_resource_group.main.name
ttl = 300
records = ["1.2.3.4"]
}
output kv_private_ip {
value = ["1.2.3.4"]
}
This is how I get fqdn and private IP:
resource "azurerm_private_endpoint" "private_endpoint" {
count = var.private_link_subnet != null ? 1 : 0
name = "${var.private_link_subnet.virtual_network_name}-${var.name}"
location = var.location
resource_group_name = var.resource_group
subnet_id = var.private_link_subnet.id
private_service_connection {
is_manual_connection = false
name = "${var.private_link_subnet.virtual_network_name}-${var.name}"
private_connection_resource_id = azurerm_key_vault.vault.id
subresource_names = ["vault"]
}
lifecycle { ignore_changes = [tags] }
}
resource "null_resource" "dns_update" {
triggers = {
priv_fqdn = "${azurerm_private_endpoint.private_endpoint[0].custom_dns_configs[0].fqdn}"
priv_ip = "${azurerm_private_endpoint.private_endpoint[0].custom_dns_configs[0].ip_addresses[0]}"
}
provisioner "local-exec" {
when = destroy
command = <<EOF
echo ${self.triggers.priv_fqdn}
bash ${path.module}/dns_update.sh destroy ${self.triggers.priv_fqdn}
EOF
}
provisioner "local-exec" {
command = <<EOF
echo ${self.triggers.priv_fqdn}
echo ${self.triggers.priv_ip}
bash ${path.module}/dns_update.sh apply ${self.triggers.priv_fqdn} ${self.triggers.priv_ip}
bash ${path.module}/dns_update.sh get ${self.triggers.priv_fqdn}
EOF
}
}
then I have:
self.triggers.priv_fqdn >> szp.vaultcore.azure.net
self.triggers.priv_ip >> 10.10.8.205

Resources