Error: 'threat_detection_policy' : attribute supports 1 item maximum, config has 2 declared - database

Expected Behaviour
I'm trying to enable 'threat detection policy' and send alerts to a list of email address
Actual Behaviour
throws error (see Error output)
Error Output
Error: threat_detection_policy: attribute supports 1 item maximum, config has 2 declared
on ..\mysql-module-test\example-location\main.tf line 20, in resource "azurerm_mysql_server" "instance":
20: resource "azurerm_mysql_server" "instance" {
Terraform (and AzureRM Provider) Version
Affected Resource(s)
azurerm_v2.41.0
terraform v0.13.0
Terraform Configuration Files
Main.tf
resource "azurerm_mysql_server" "instance" {
name = "${var.names.product_name}-${var.names.environment}-${var.server_id}"
location = var.location
resource_group_name = var.resource_group_name
tags = var.tags
administrator_login = local.administrator_login
administrator_login_password = local.administrator_password
sku_name = var.sku_name
storage_mb = var.storage_mb
version = var.mysql_version
auto_grow_enabled = (var.create_mode == "Replica" ? true : var.auto_grow_enabled)
backup_retention_days = var.backup_retention_days
geo_redundant_backup_enabled = var.geo_redundant_backup_enabled
infrastructure_encryption_enabled = var.infrastructure_encryption_enabled
public_network_access_enabled = (((length(var.service_endpoints) > 0) || (length(var.access_list) > 0)) ? true : false)
ssl_enforcement_enabled = var.ssl_enforcement_enabled
ssl_minimal_tls_version_enforced = var.ssl_enforcement_enabled ? "TLS1_2" : "TLSEnforcementDisabled"
create_mode = var.create_mode
creation_source_server_id = (var.create_mode == "Replica" ? var.creation_source_server_id : null)
dynamic "threat_detection_policy" { # Error: threat_detection_policy: attribute supports 1 item maximum, config has 2 declared
for_each = (var.threat_detection_policy != null ? var.threat_detection_policy : null)
content {
enabled = var.threat_detection_policy.enable_threat_detection_policy
email_addresses = var.threat_detection_policy.threat_detection_email_addresses
}
}
}
Variables.tf
# Advanced threat protection policy settings
variable "threat_detection_policy" {
description = "Threat detection policy configuration. If not input, threat detection will be disabled."
type = object({
enable_threat_detection_policy = bool
threat_detection_email_addresses = list(string)
})
default = null
}
Module call
# advanced threat protection policy
threat_detection_policy = {
enable_threat_detection_policy = true
threat_detection_email_addresses = ["first.last#contoso.com", "first.last#contoso.com"]
}
Error Output
Error: threat_detection_policy: attribute supports 1 item maximum, config has 2 declared
on ..\mysql-module-test\example-location\main.tf line 20, in resource "azurerm_mysql_server" "instance":
20: resource "azurerm_mysql_server" "instance" {

When you use for_each on a map (or object) Terraform is iterating over the keys. So it is attempting to create two threat_detection_policy blocks for the keys enable_threat_detection_policy and threat_detection_email_addresses.
Dynamic blocks don't really make sense for your scenario, since the azurerm_mysql_server resource can only have a single threat_detection_policy block. A configuration like this may work:
threat_detection_policy {
enabled = var.threat_detection_policy != null
email_addresses = var.threat_detection_policy != null ? var.threat_detection_policy.threat_detection_email_addresses : []
}

Related

Terraform for Digital Ocean Managed Database Firewall

I have been trying to dynamically create the Terraform code for a managed Digital Ocean database I have. I am trying to achieve that I have some lists of FW entries like:
locals {
####################################################################################
## DO object ids (the different ID's for the Postgres databases in Digital Ocean
####################################################################################
id_postgres_application_dev = "12345"
id_postgres_application_stg = "23456"
id_postgres_application_prd = "34567"
# Map to fw for Postgres
pg-application_id = {
"dev" = id_postgres_application_dev
"stg" = id_postgres_application_stg
"prd" = id_postgres_application_prd
}
####################################################################################
## Outside IP addresses
####################################################################################
fw_ip_peter = "4.100.123.140"
fw_ip_sunshine = "152.120.106.102"
####################################################################################
## Postgres Application
####################################################################################
# Map to fw for Postgres
pg-application_fw_rules_ip = {
"dev" = [
local.fw_ip_peter,
local.fw_ip_sunshine]
"stg" = [
local.fw_ip_peter]
"prd" = [
local.fw_ip_peter]
}
long_key = {
type = "string"
default = <<EOF
rule = {
type = "KEY"
value = "VALUE"
}
EOF
}
fw_rules = toset(lookup(local.pg-application_fw_rules_ip, var.environment))
}
Now what I want to achieve is to dynamically generate the FW rule entries (these are described in the Digital Ocean documentation here: https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/database_firewall
So the result would be something like for the dev environment:
id_postgres_application = lookup(local.pg-application_id, var.environment)
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = id_postgres_application
rule {
type = "ip_addr"
value = "4.100.123.140" // Peter
}
rule {
type = "ip_addr"
value = "152.120.106.102" // Sunshine (for dev only)
}
}
So the problem lies in the rule sections - to repeat these per entry in the fw_rules variable.
Does anyone have specific advice on how to do this? I have tried many different solutions, and I think my basic problem is to understand which method to apply?
Generally, you would use dynamic blocks for that. Thus, your code could look like the following:
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = id_postgres_application
dynamic "rule" {
for_each = local.application_fw_rules_ip[var.environment]
content {
type = "ip_addr"
value = rule.key
}
}
}
Treat the code as an example, as probably some further adjustments specific to your setup may be required.

Get oauth2_permissions from azuread_application using Terraform

I have an app registration which defines two oauth2_permissions blocks, e.g. (other details elided)
resource "azuread_application" "myapp" {
oauth2_permissions {
is_enabled = true
type = "User"
value = "Permission.One"
}
oauth2_permissions {
is_enabled = true
type = "User"
value = "Permission.Two"
}
}
Which, when applied,works just fine. I then want to refer to those permissions in another app registration, e.g.
resource "azuread_application" "myotherapp" {
required_resource_access {
resource_app_id = azuread_application.myapp.application_id
resource_access {
id = ??
type = "Scope"
}
}
}
For the id here, I have tried:
id = lookup(azuread_application.myapp.oauth2_permissions[0], "id")
which gives This value does not have any indices. As does
id = azuread_application.myapp.oauth2_permissions.0.id
I can define a data block and get the output of oauth2_permissions from myapp:
data "azuread_application" "myapp" {
application_id = azuread_application.myapp.application_id
}
output "myapp-perms" {
value = data.azuread_application.myapp.oauth2_permissions
}
And on apply, that will correctly show an array of the two permission blocks. If I try to refer to the data block instead of the application block, i.e.
id = lookup(data.azuread_application.myapp.oauth2_permissions[0], "id")
This gives me a different error: The given key does not identify an element in this collection value
If I apply those two permissions manually on the console, everything works fine. From reading around I was fairly sure that at least one of the above methods should work but I am clearly missing something.
For completeness, provider definition:
provider "azurerm" {
version = "~> 2.12"
}
provider "azuread" {
version = "~> 0.11.0"
}
Based on comments.
The solution is to use tolist. The reason is that the multiple oauth2_permissions blocks will be represented as sets of objects, which can't be accessed using indices.
id = tolist(azuread_application.myapp.oauth2_permissions)[0].id
However, the sets don't have guaranteed order. Thus a special attention should be payed to this.

How can I associate NSG's and Subnets being created by loops in Terraform?

Here is the code I am using to create subnets and nsgs I want to associate the NSG and subnet in the same script but I am unable to understand how can I get subnet IDs and NSG IDs which are being produced here and use them in the association resource. Thanks in advance for the help !
First part of code this is being used to create n no of Subnets and NSGs depends upon the parameter
provider "azurerm" {
version = "2.0.0"
features {}
}
resource "azurerm_resource_group" "new-rg" {
name = var.rg_name
location = "West Europe"
}
resource "azurerm_virtual_network" "new-vnet" {
name = var.vnet_name
address_space = ["${var.vnet_address_space}"]
location = azurerm_resource_group.new-rg.location
resource_group_name = azurerm_resource_group.new-rg.name
}
resource "azurerm_subnet" "test" {
count = "${length(var.subnet_prefix)}"
name = "${element(var.subnet_subnetname, count.index)}"
resource_group_name = azurerm_resource_group.new-rg.name
virtual_network_name = azurerm_virtual_network.new-vnet.name
address_prefix = "${element(var.subnet_prefix, count.index)}"
}
resource "azurerm_network_security_group" "new-nsg" {
count = "${length(var.subnet_prefix)}"
name = "${element(var.subnet_subnetname, count.index)}-nsg"
location = azurerm_resource_group.new-rg.location
resource_group_name = azurerm_resource_group.new-rg.name
}
Below is the resource where i have to pass the parameters to create the association for the above subnets and nsgs being created.
Second Part of code Need to make the below code usable for above solution for n no of associations.
resource "azurerm_subnet_network_security_group_association" "example" {
subnet_id = azurerm_subnet.example.id
network_security_group_id = azurerm_network_security_group.example.id
}
How can associate the n number of subnets and nsgs being created by using 2nd part of code, I cant find my way to that
This seems like a good case for for_each. Here is some code I'm using for AWS (the same logic applies as far as I can tell)-
(var.nr_azs is just an int, formatlist is used because for_each only likes strings)
locals {
az_set = toset(formatlist("%s", range(var.nr_azs))) # create a list of numbers and convert them to strings)
}
resource "aws_subnet" "private" {
for_each = local.az_set
availability_zone = random_shuffle.az.result[each.key]
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, each.key)
vpc_id = aws_vpc.main.id
map_public_ip_on_launch = false
}
resource "aws_eip" "nat_gw" {
vpc = true
}
resource "aws_nat_gateway" "gw" {
for_each = aws_subnet.private
allocation_id = aws_eip.nat_gw.id
subnet_id = each.value.id
}
resource "aws_route_table" "private_egress" {
for_each = aws_nat_gateway.gw
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = each.value.id
}
}
resource "aws_route_table_association" "private" {
for_each = local.az_set
subnet_id = aws_subnet.private[each.key].id
route_table_id = aws_route_table.private_egress[each.key].id
}
So i was able to solve the issue mentioned by me above the following code contains the solution for the mentioned scenario for the problem.
resource "azurerm_subnet_network_security_group_association" "snet-nsg-association" {
count = length(var.subnet_subnetname)
subnet_id = element(azurerm_subnet.multi-snet.*.id, count.index)
network_security_group_id = element(azurerm_network_security_group.new-nsg.*.id, count.index)
}

concatenate filepath prefix and file name in terraform code

I'm trying to create policies in aws with terraform.
variable "path" {
type = "string"
}
variable "policies" {
type = list(object ({
name = string
plcyfilename = string
asmplcyfilename = string
desc = string
ownner = string}))
default = []
}
resource "aws_iam_policy" "policy" {
count = length(var.policies)
name = lookup(var.policies[count.index], "name")
policy = file(lookup(var.policies[count.index], concat("var.path","plcyfilename")))
description = "Policy for ${lookup(var.policies[count.index], "desc")}"
}
and this is how my tfvars looks like:
path = "./../t2/scripts/"
policies = [
{name = "cwpolicy", plcyfilename = "cw.json" , asmplcyfilename ="csasm.json", desc ="vpcflowlogs", ownner ="vpc"},
]
The error that is thrown while I do this is like this:
Error: Invalid function argument
on main.tf line 13, in resource "aws_iam_policy" "policy":
13: policy = file(lookup(var.policies[count.index], "${concat("${var.path}","plcyfilename")}"))
Invalid value for "seqs" parameter: all arguments must be lists or tuples; got
string.
I'm using terraform 0.12.
It works as expected if I change the variable to have complete file path:plcyfilename=./../t2/scripts/cw.json.
However I want to isolate the file path from the file names.
Can someone point me where I am going wrong.
The concat function is for concatenating lists, not for concatenating strings.
To concatenate strings in Terraform, we use template interpolation syntax:
policy = file("${var.path}/${var.policies[count.index].policy_filename}")
Since your collection of policies is not a sequence where the ordering is significant, I'd recommend also changing this to use resource for_each, which will ensure that Terraform tracks the policies using the policy name strings rather than using the positions in the list:
variable "policies" {
type = map(object({
policy_filename = string
assume_policy_filename = string
description = string
owner = string
}))
default = {}
}
resource "aws_iam_policy" "policy" {
for_each = var.policies
name = each.key
policy = file("${var.path}/${each.value.policy_filename}")
description = "Policy for ${each.value.description}"
}
In this case the policies variable is redefined as being a map, so you'd now present the name of each policy as the key within the map rather than as one of the attributes:
policies = {
cw = {
policy_filename = "cw.json"
assume_policy_filename = "csasm.json"
description = "vpcflowlogs"
owner = "vpc"
}
# ...
}
Because the for_each value is the policies map, each.key inside the resource block is a policy name and each.value is the object representing that policy, making the resulting expressions easier to read and understand.
By using for_each, we will cause Terraform to create resource instance addresses like aws_iam_policy.policy["cw"] rather than like aws_iam_policy.policy[1], and so adding and removing elements from the map will cause Terraform to add and remove corresponding instances from the resource, rather than try to update instances in-place to respect the list ordering as it would've done with your example.

How to verify that Slick is using parameters from application.conf?

In application.conf, parameters are set:
url = "jdbc:mysql://.../table_name"
user = ...
password = ...
driver = "com.mysql.jdbc.Driver"
connectionPool = HikariCP
queueSize = 25000
I am still receiving an error whenever the queue reaches 1000 items, meaning that the queueSize property is still the default value.
Task scala.slick.backend.DatabaseComponent$DatabaseDef$...
rejected from java.util.concurrent.ThreadPoolExecutor...
[Running, pool size = 20,
active threads = 20,
queued tasks = 1000,
completed tasks = 7507]
Not sure why it's not picking up your value, but you might want to try a different way of configuring.. you didn't say what version of Slick you are using. But please refer to the Slick 3.0.0 documentation. Try it using TypeSafe Config:
In your application.conf:
database {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource" // replace with mysql driver
properties = {
databaseName = "mydb"
user = "myuser"
password = "secret"
}
queueSize = 25000 // I've never changed that property, so not tested.
}
Then in scala:
val db = Database.forConfig("database")
Hope this works for you.

Resources