terraform module depends_on Azure - azure

I am building a production infrastructure in Azure cloud with terraform. My requirements are below.
Azure key vault should be provision first, as I will utilize a secret from there. But as terraform module doesn't support depend_on. Any workaround will be highly appricaiable.
source = "./../modules/azurekeyvault/"
username = "${var.username}"
tags_environment = "${var.tags_environment}"
}
module "mysql" {
source = "./../modules/mysql/"
}
Azure key vault module.
name = "${var.lsrkeyvault}"
location = "${data.azurerm_resource_group.lsr.location}"
resource_group_name = "${data.azurerm_resource_group.lsr.name}"
enabled_for_disk_encryption = true
tenant_id = "${data.azurerm_client_config.current.tenant_id}"
sku_name = "standard
resource "azurerm_key_vault_secret" "userlist" {
count = length(var.username)
name = "${var.username[count.index]}"
value = "${bcrypt(random_string.password.result)}"
key_vault_id = "${azurerm_key_vault.kvlsr.id}"
tags = {
environment = "${var.tags_environment}"
}
}
Mysql Module code:
name = "kyv-lsr-dev"
resource_group_name = "rgroup"
}
data "azurerm_key_vault_secret" "userlist" {
name = "mylab"
key_vault_id = "${data.azurerm_key_vault.keyvault.id}"

I don't know if this would work, but here is what I would recommend trying to help TF build out the correct dependency graph. I would make the keyvault id a variable in your module. That way when you use the module you will be explicitly calling the keyvault which should trigger it to be created before the module is executed.
module "mysql" {
source = "./../modules/mysql/"
keyvault_id = "${module.keyvault.id}"
}
This would require your mysql module to take keyvault_id as a variable and use that instead of the data resoruce. It would also require your keyvault module to output the keyvault id. Again, this may not work, but I think it will.

Related

Terraform does not wait for update of azurerm_key_vault_secret.xyz.id

We are using terraform to build my azure resources with azurerm provider.
We are injecting a secret during the terraform run and this secret may change from time to time.
We use a azurerm_key_vault_secret to store the secret and a function app with managed identity (that has got reading access to the key vault) that receives the secret like this:
resource "azurerm_key_vault_secret" "my_secret" {
name = "my-secret"
value = var.my_secret
key_vault_id = azurerm_key_vault.default.id
}
resource "azurerm_function_app" "app" {
name = "..."
app_settings = {
MySecret = "#Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.my_secret.id})"
}
identity {
type = "SystemAssigned"
}
...
}
When i run terraform apply and the secret is changed, the function app points to the old version of the secret. It seems the azurerm_key_vault_secret.my_secret.id is being read before the secret was updated.
Does anybody have any idea, how I can make sure the function_app will wait for the update of the secret?
(And yes, the id changes and I also don't like it, but that is how the provider works.)
When you are updating a key vault secret then the change is handled by Key vault UI . So Terraform won't detect the changes on azurerm_key_vault_secret.example.id and thus the reference's also won't be modified .
As a Workaround , You can use a data source for the same key vault secret and provide it in the function-app as shown in the below code , so that all the changes done in key vault secret can be read from data source and the changes can be applied accordingly :
resource "azurerm_key_vault_secret" "example" {
name = "functionappsecret"
value = "changedpassword"
key_vault_id = azurerm_key_vault.example.id
}
data "azurerm_key_vault_secret" "secret" {
name="functionappsecret"
key_vault_id = azurerm_key_vault.example.id
depends_on = [
azurerm_key_vault_secret.example
]
}
resource "azurerm_function_app" "example" {
name = "ansuman-azure-functions"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
app_service_plan_id = azurerm_app_service_plan.example.id
storage_account_name = azurerm_storage_account.example.name
storage_account_access_key = azurerm_storage_account.example.primary_access_key
app_settings = {
MySecret = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.secret.id})"
}
identity {
type="SystemAssigned"
}
}
Ouptut:

How to save the azure keyvault certificate to a local folder with terraform?

I am trying to download a certificate in azure keyvault and save it as a local file by using terraform? Is this operation possible at all with terraform? I have checked it but couldn't find a terraform function doing that.
You may use the azurerm_key_vault_certificate_data data source.
Example from the docs
data "azurerm_key_vault" "example" {
name = "examplekv"
resource_group_name = "some-resource-group"
}
data "azurerm_key_vault_certificate_data" "example" {
name = "secret-sauce"
key_vault_id = data.azurerm_key_vault.example.id
}
output "example_pem" {
value = data.azurerm_key_vault_certificate_data.example.pem
}

Creating azure automation dsc configuration and dsc configuration node using terraform doesn't seems to be working

As a very first step of my release process I run the following terraform code
resource "azurerm_automation_account" "automation_account" {
for_each = data.terraform_remote_state.pod_bootstrap.outputs.ops_rg
name = "${local.automation_account_prefix}-${each.key}"
location = each.key
resource_group_name = each.value.name
sku_name = "Basic"
tags = {
environment = "development"
}
}
The automation accounts created as expected and I can see those in Azure portal.
I also have terraform code that creates a couple of windows VMs,each VM creation accompained by the following
resource "azurerm_virtual_machine_extension" "dsc" {
name = "DevOpsDSC"
virtual_machine_id = var.vm_id
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.83"
settings = <<SETTINGS_JSON
{
"configurationArguments": {
"RegistrationUrl": "${var.dsc_server_endpoint}",
"NodeConfigurationName": "${var.dsc_config}",
"ConfigurationMode": "${var.dsc_mode}",
"ConfigurationModeFrequencyMins": 15,
"RefreshFrequencyMins": 30,
"RebootNodeIfNeeded": false,
"ActionAfterReboot": "continueConfiguration",
"AllowModuleOverwrite": true
}
}
SETTINGS_JSON
protected_settings = <<PROTECTED_SETTINGS_JSON
{
"configurationArguments": {
"RegistrationKey": {
"UserName": "PLACEHOLDER_DONOTUSE",
"Password": "${var.dsc_primary_access_key}"
}
}
}
PROTECTED_SETTINGS_JSON
}
The result is the following
So VM extension is created for each VM and the status says that provisioning succeeded.
For the next step I run the following terraform code
resource "azurerm_automation_dsc_configuration" "iswebserver" {
for_each = data.terraform_remote_state.pod_bootstrap.outputs.ops_rg
name = "iswebserver"
resource_group_name = each.value.name
automation_account_name = data.terraform_remote_state.ops.outputs.automation_account[each.key].name
location = each.key
content_embedded = "configuration iswebserver {}"
}
resource "azurerm_automation_dsc_nodeconfiguration" "iswebserver" {
for_each = data.terraform_remote_state.pod_bootstrap.outputs.ops_rg
name = "iswebserver.localhost"
resource_group_name = each.value.name
automation_account_name = data.terraform_remote_state.ops.outputs.automation_account[each.key].name
depends_on = [azurerm_automation_dsc_configuration.iswebserver]
content_embedded = file("${path.cwd}/iswebserver.mof")
}
The mof file content is the following
/*
#TargetNode='IsWebServer'
#GeneratedBy=P120bd0
#GenerationDate=02/25/2021 17:33:16
#GenerationHost=D-MJ05UA54
*/
instance of MSFT_RoleResource as $MSFT_RoleResource1ref
{
ResourceID = "[WindowsFeature]IIS";
IncludeAllSubFeature = True;
Ensure = "Present";
SourceInfo = "D:\\DSC\\testconfig.ps1::5::9::WindowsFeature";
Name = "Web-Server";
ModuleName = "PsDesiredStateConfiguration";
ModuleVersion = "1.0";
ConfigurationName = "TestConfig";
};
instance of OMI_ConfigurationDocument
{
Version="2.0.0";
MinimumCompatibleVersion = "1.0.0";
CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"};
Author="P120bd0";
GenerationDate="02/25/2021 17:33:16";
GenerationHost="D-MJ05UA54";
Name="TestConfig";
};
After running the code I have got the following result
The configuration is created as expected, clicking on configuration entry in UI grid, leads to the following
Meaning that node configuration is created as well. My expectation was that for each VM I will see the Node configured to run configuration provided in mof file but Nodes UI shows empty Nodes
So I was trying to configure node manually to connect all peaces together
and that fails with the following
So I am totally confisued. On the one hand there's azurerm_virtual_machine_extension that allows to create extension and bind it to the automation account. In addition there are azurerm_automation_dsc_configuration and azurerm_automation_dsc_nodeconfiguration that allows to create configuration and node configuration. But the bottom line is that you cannot connect all those dots to be able to create node.
Just to confirm that configuration is valid, I create additional vm without using azurerm_virtual_machine_extension and I was able succesfully add this MV to created node configuration
The problem was in azurerm_virtual_machine_extension dsc_configuration parameter. The value needs to be the same as name property of the azurerm_automation_dsc_nodeconfiguration resource.

Migrate terraform modules to updated provider format

I inherited a codebase with all providers stored inside modules and am having a lot of trouble moving the providers out so that I can remove the resources created from modules.
The current design violates the rules outlined here: https://www.terraform.io/docs/configuration/providers.html and makes removing modules impossible.
My understanding of the migration steps is:
Create a provider for use at the top-level
Update module resources to use providers stored outside of the module
Remove module (with top-level provider persisting)
Example module
An example /route53-alias-record/main.ts is:
variable "evaluate_target_health" {
default = true
}
data "terraform_remote_state" "env" {
backend = "s3"
config = {
bucket = "<bucket>"
key = "infra-${var.environment}-${var.target}.tfstate"
region = "<region>"
}
}
provider "aws" {
region = data.terraform_remote_state.env.outputs.region
allowed_account_ids = data.terraform_remote_state.env.outputs.allowed_accounts
assume_role {
role_arn = data.terraform_remote_state.env.outputs.aws_account_role
}
}
resource "aws_route53_record" "alias" {
zone_id = data.terraform_remote_state.env.outputs.public_zone_id
name = var.fqdn
type = "A"
alias {
name = var.alias_name
zone_id = var.zone_id
evaluate_target_health = var.evaluate_target_health
}
}
Starting usage
module "api-dns-alias" {
source = "../environment/infra/modules/route53-alias-record"
environment = "${var.environment}"
zone_id = "${module.service.lb_zone_id}"
alias_name = "${module.service.lb_dns_name}"
fqdn = "${var.environment}.example.com"
}
Provider overriding
## Same as inside module
provider "aws" {
region = data.terraform_remote_state.env.outputs.region
allowed_account_ids = data.terraform_remote_state.env.outputs.allowed_accounts
assume_role {
role_arn = data.terraform_remote_state.env.outputs.aws_account_role
}
}
module "api-dns-alias" {
source = "../environment/infra/modules/route53-alias-record"
environment = "${var.environment}"
zone_id = "${module.service.lb_zone_id}"
alias_name = "${module.service.lb_dns_name}"
fqdn = "${var.environment}.example.com"
providers = {
aws = aws ## <-- pass in explicitly
}
}
I was able to safely deploy with the providers set, but I do not believe that they are being used inside the module, which means the handshake still fails when I remove the module and the resources cannot be deleted.
I am looking for the steps needed to migrate to an outside provider so that I can safely remove resources.
I am currently working with terraform 0.12.24

Azure Function AppSettings using Terraform and mutliple sources for map

so in summary I am specifically looking to maintain the app settings for my azure functions using two different sources,
the first source is a map of custom settings that will be maintained manually or through code which might have little change
The second source of app settings map are key secret uri's as per the code before, this enables the azure function to use secret references as configuration value.
I am trying to automate the process of retrieving a subset of secrets dynamically from keyvault and merging it into the custom map app settings that I define in code.
Question:
My ideal world would be that i update the list secretKeys and the map appSettingsSecretsMap get's dynamically created and then consumed by resource creation resource "azurerm_function_app" "functionApp_workerFunctions" in its appsettings. Does anyone have a idea of how I might achieve this a bit more dynamically?
My full code is as per below:
variable "secretKeys" {
type = list(string)
default = [
"TestDbPassword",
"TestDbUserId"]
}
data "azurerm_key_vault" "keyvault" {
name = "source-keyvault"
resource_group_name = "source-keyvault-rg"
}
data "azurerm_key_vault_secret" "kvSecrets" {
for_each = toset(var.secretKeys)
name = each.key
key_vault_id = data.azurerm_key_vault.keyvault.id
}
# Testing Access to secret
output "TestDbPassword" {
value = data.azurerm_key_vault_secret.kvSecrets["TestDbPassword"].id
}
#https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
variabe "appSettingsSecretsMap" {
type = map
default = {
DBPassword = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets["TestDbPassword"].id})"
DBUserId = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets["TestDbUserId"].id})"
}
}
# Reference for appSettings https://learn.microsoft.com/en-us/azure/azure-functions/functions-app-settings
variable "appSettingsCustomMap" {
type = map
default = {
WEBSITE_RUN_FROM_PACKAGE = ""
FUNCTIONS_WORKER_RUNTIME = ""
APPINSIGHTS_INSTRUMENTATIONKEY = ""
#FUNCTIONS_EXTENSION_VERSION = "~1"
}
}
resource "azurerm_function_app" "functionApp_workerFunctions" {
name = "worker-function-${var.ENVIRONMENT}"
location = "XYZ-Example"
resource_group_name = "XYZ-Example"
app_service_plan_id = "XYZ-Example"
storage_account_name = "XYZ-Example"
storage_account_access_key = "XYZ-Example"
app_settings = merge(var.appSettingsMap, var.appSettingsSecretsMap)
}
For the custom settings, I think it's better to set it in variable manually. It has a lot of things with a little change. If you make it automated, I think it will be a little redundancy. Just do it as you show in the question.
For key fault, I recommend you use the locals block:
locals {
appSettingsSecretsMap = {
DBPassword = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets[0].id})"
DBUserId = "#Microsoft.KeyVault(SecretUri=${data.azurerm_key_vault_secret.kvSecrets[1].id})"
}
}
The data for the key vault secrets will return a list with the element like this:
So you cannot quote it with the secret name. Just do it as what I show you.

Resources