hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.49k stars 4.59k forks source link

How to add cert from Key vault to api manger #3635

Closed jabba2324 closed 3 years ago

jabba2324 commented 5 years ago

Hello, question & possible bug.

How do you correctly add a certificate to an api manager hostname block from key vault. I've noticed there isn't certificate data source so I've tried using the secret data source instead as advised here: https://github.com/terraform-providers/terraform-provider-azurerm/issues/2898#issuecomment-465098754

This doesn't seem to work as I'm met with a forbidden error (which is not the case, I can see the certificate in Azure). Terraform attempts to the the following url: https://product-dev.vault.azure.net/secrets/my-cert/1234?api-version=7.0'

Manually adding this URL for the cert https://product-dev.vault.azure.net/certificates/my-cert/1234 Given me this error:

ApiManagement service only accepts KeyVault Secrets type.

My terraform looks something like this.

data "azurerm_key_vault" "my_key_vault" {
  name                = "${var.name}"
  resource_group_name = "${var.resource_group}"
}

data "azurerm_key_vault_secret" "my_key_vault_secret" {
  name      = "my-cert"
  key_vault_id = "${data.azurerm_key_vault.my_key_vault.id}"
} 

.....

hostname_configuration {
    management {
      host_name = "${var.host}"
      key_vault_id = "${data.azurerm_key_vault_secret.my_key_vault_secret.id}"
    }
  }
jabba2324 commented 5 years ago

Issue described here too https://github.com/terraform-providers/terraform-provider-azurerm/issues/3058

wasfree commented 5 years ago

Hi @cdesewell, please see my comment in #3058. The issue is that if we use managed identity in api_management this will create a service principal which has no access to keyvault. In case we define hostname_configuration with key_vault_id this will fail due to missing permissions (401 Forbidden).

This are the steps to get the required permissions:

  1. Create api_management with managed identity enabled
  2. Add service principal to your keyvault in access policy. ${your_key_vault} > Access policies > Add access policy. You should find it under the same name as your api_management, it required at least certificate "get" permissions.
  3. Now it should be possible to use key_vault_id in api_management

The above 3 steps are not a good solution. Because if we delete and recreate api_management, permissions for service principal are gone.

As a temporary workaround we could do something like this:

data "azurerm_key_vault" "my_key_vault" {
  name                            =  var.name
  resource_group_name =  var.resource_group
}

data "azurerm_key_vault_secret" "my_key_vault_secret" {
  name            = "my-cert"
  key_vault_id = data.azurerm_key_vault.my_key_vault.id
}
.....
hostname_configuration {
    management {
      host_name = var.host
      certificate   = data.azurerm_key_vault_secret.my_key_vault_secret.value
    }
  }

But please keep in mind that this workaround will pull out the certificate in terraform.

jabba2324 commented 5 years ago

Thanks for your response @NickMetz I can't have the cert being displayed in terraform, additionally I can't have the cert password being used in terraform either.

The above 3 steps are not a good solution. Because if we delete and recreate api_management, permissions for service principal are gone.

You say this, what kind of changes would make terraform destroy and re-create the api manager as opposed to just making changes?

jabba2324 commented 5 years ago

Just giving the key vault access policy a try to find that It's not possible to get the principal id. I'm met with

"object_id" is an invalid UUUID: uuid: UUID string too short

Similar issue reported here: https://github.com/terraform-providers/terraform-provider-azurerm/issues/2103

wasfree commented 5 years ago

HI @cdesewell,

You say this, what kind of changes would make terraform destroy and re-create the api manager as opposed to just making changes?

I would expect if we delete an api managment which use managed identity, relying service principal will also be deleted.

Just giving the key vault access policy a try to find that It#s not possible to get the principal id. I'm met with.

You should find the principal id searching for the same name as your api managment name.

jabba2324 commented 5 years ago

Thanks @NickMetz the Principal id attribute being empty is a bug

wasfree commented 5 years ago

Hi @cdesewell,

can you please provide a code sample of what you tried?

The only thing i can imagine you tried something like this:

data "azurerm_api_management" "test" {
  name                = "example-apim"
  resource_group_name = "example-resource-group"
}

data "azurerm_key_vault" "my_key_vault" {
  name                = "example-key-vault"
  resource_group_name = "example-resource-group"
}

data "azurerm_key_vault_secret" "my_key_vault_secret" {
  name            = "my-cert"
  key_vault_id    = data.azurerm_key_vault.my_key_vault.id
}

resource "azurerm_api_management" "test" {
  name                = "example-apim"
  location            = "westeurope"
  resource_group_name = "example-resource-group"
  publisher_name      = "My Company"
  publisher_email     = "company@terraform.io"

  hostname_configuration {
    management {
      host_name    = "foo.example.com"
      key_vault_id = data.azurerm_key_vault_secret.my_key_vault_secret.id
    }
  }

  identity {
      type = "SystemAssigned"
   }

  sku {
    name     = "Developer"
    capacity = 1
  }
}

resource "azurerm_key_vault_access_policy" "test" {
  vault_name          = "example-vault"
  resource_group_name = "example-resource-group"

  tenant_id = data.azurerm_api_management.test.tenant_id
  object_id = data.azurerm_api_management.test.principal_id

  key_permissions = [
    "get",
  ]

  secret_permissions = [
    "get",
  ]
}

But this won't work because api_management creation will fail due to missing permissions to key_vault (required to fetch certificate in hostname_configuration). If creation of api_management fail managed service principal will not be created. That for it would also not be possible to get object_id from service principal any you can't attach right permissions.

Like it wrote in #3058 chicken vs. egg causality dilemma. It would be better if we had the possibility to add a predefined service principal to api_managment with access to keyvault. But this is something that needs to be fixed from Azure side.

example (is not possible at the moment)

  service_principal {
    client_id     = "00000000-0000-0000-0000-000000000000"
    client_secret = "00000000000000000000000000000000"
  }
jabba2324 commented 5 years ago

@NickMetz your example is spot on, it's a chicken and egg dilemma. Could one solution be to have host configuration as a separate terraform resource definition. Similiar to how an App Service Custom Hostname: https://www.terraform.io/docs/providers/azurerm/r/app_service_custom_hostname_binding.html

mikhailshilkov commented 4 years ago

Bumped into this "chicken and egg" problem today. It seems that the proper solution should be a separate resource for host_configuration, as @cdesewell suggested.

w0rldart commented 4 years ago

Yup, having the same issue. And as Mikhail said, @cdesewell's suggestion seems to make sense most.

jabba2324 commented 4 years ago

Can we get an update on this? Is this something that will get addressed?

jeanpaulsmit commented 4 years ago

I managed to fix this, with the assistance of Microsoft. Things are a bit different than I thought (read the tip section in https://docs.microsoft.com/en-us/azure/api-management/configure-custom-domain). My steps are:

This solves the chicken-egg problem.

jokarl commented 4 years ago

I just wanted to chime in with how I solved it using the suggestion from @jeanpaulsmit. Hope this helps anyone until there is a proper REST endpoint for doing this.

I added this resource in the same terraform script that creates the API service:

data "azurerm_key_vault" "storemanager-common" {
  name                = "key-vault-name"
  resource_group_name = data.azurerm_resource_group.resource_group.name
}

resource "azurerm_key_vault_access_policy" "key_vault_access_policy" {
  key_vault_id  = data.azurerm_key_vault.storemanager-common.id
  tenant_id     = data.azurerm_client_config.current.tenant_id
  object_id     = azurerm_api_management.api_management.identity[0].principal_id
  secret_permissions = [
    "get"
  ]

  provisioner "local-exec" {
    command = format("%s/add-domain.ps1 %s %s %s %s",
      path.module,
     "hostname.com",
      "certificateId",
      azurerm_api_management.api_management.resource_group_name,
      azurerm_api_management.api_management.name
    )
  }
}

And add-domain.ps1. This requires PowerShell to be installed as a snap. If you have installed it some other way, change the hashbang to point to your PowerShell bin.

#!/snap/bin/pwsh

$hostname = $args[0]
$certificateId = $args[1]
$resourceGroupName = $args[2]
$apiServiceName = $args[3]

if (!(Get-Module -ListAvailable -Name Az)) {
    Write-Host "Installing Azure module"
    Install-Module -Name Az -AllowClobber
}

Write-Host "Assigning custom hostname $hostname"
$apim = Get-AzApiManagement -ResourceGroupName $resourceGroupName -Name $apiServiceName
$apim.ProxyCustomHostnameConfiguration = @(New-AzApiManagementCustomHostnameConfiguration -Hostname $hostname -HostnameType Proxy -KeyVaultId $certificateId)
Set-AzApiManagement -InputObject $apim
Write-Host "Assigned hostname $hostname"
bytejunkie commented 4 years ago

I've got this very issue too. I can see two workaround above, but I'd love someone from hashicorp to chip in on if they can see why this circular dependancy just doesnt work. @tombuildsstuff maybe? it defintely feels like a bug to me.

I get that I can use a workaround and we actually already have one, in that we deploy an APIM instance without the hostname config, then in the very same template we repeat the APIM resource with another name and add the hostname config in. its a double deployment of APIM, its a bit hacky, but it ususally works. until today when its erroring for some unknown reason. this is some pretty nasty code that at some point im going to have to hand over to the client an explain that the tool i told them would revolutionise their deployments has some pretty hacky workarounds in it.

AmudaPalani commented 3 years ago

resolved this issue using azurerm_api_management_custom_domain

manicminer commented 3 years ago

I believe this issue is resolved using the newly added azurerm_api_management_custom_domain resource, so I'm going to close this issue accordingly. Thanks!

ghost commented 3 years ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 hashibot-feedback@hashicorp.com. Thanks!