Terraform for_each loop aws_auth eks gets overwritten - amazon-web-services

I am creating my very own EKS cluster and had some issues adding in roles via my namespace declared in locals dynamically
I am using the terraform-aws-eks-auth Tag v1.0.0 to populate my IAM roles into the groups that i have created in terraform.
I expect that each.key variable would populate/append in the list of maps entries of map_roles but so far it doesn't do so. The end result in aws auth config map shows that it only contains AdminAccessRole, my_eks_admin_role, dev-kiwi-ingress-nginx_admin_role and dev-kiwi-ingress-nginx_readonly_role. The previous namespaces that was supposed to be dynamically created were overwritten.
Should I even use for_each in such case? How can I populate aws-auth dynamically by namespaces used, combined with 2 static entries.
I appreciate any advice or help!
locals {
namespaces = [
"${var.env}-devops",
"${var.env}-devops-ingress-nginx",
"${var.env}-shared",
"${var.env}-shared-ingress-nginx",
"${var.env}-pear",
"${var.env}-pear-ingress-nginx",
"${var.env}-apple",
"${var.env}-apple-ingress-nginx",
"${var.env}-banana",
"${var.env}-banana-ingress-nginx",
"${var.env}-kiwi",
"${var.env}-kiwi-ingress-nginx"
]
}
module "eks_auth" {
source = "./terraform-modules/terraform-aws-eks-auth-1.0.0"
eks = module.eks
# Additional IAM roles to add to the aws-auth configmap.
for_each = toset(local.namespaces)
map_roles = [
{
rolearn = "arn:aws:iam::1234567890:role/AdminAccessRole"
username = "AdminAccessRole"
groups = ["system:masters"]
},
{
rolearn = "arn:aws:iam::1234567890:role/my-eks-admin-role"
username = "my_eks_admin_role"
groups = ["system:masters"]
},
{
rolearn = aws_iam_role.eks_namespace_admin_roles[each.key].arn
username = "${each.key}_admin_role"
groups = ["${each.key}:${each.key}_group"]
},
{
rolearn = aws_iam_role.eks_namespace_readonly_roles[each.key].arn
username = "${each.key}_readonly_role"
groups = ["${each.key}:${each.key}_group"]
}
]
map_users = [
{
userarn = "arn:aws:iam::1234567890:user/terraform-service-account"
username = "terraform-service-account"
groups = ["system:masters"]
}
]
}
Terraform v1.0.11
aws provider version = "~> 4.9.0"

By adding the for_each in the level shown in the question, terraform will attempt to create multiple instances of the eks_auth module
In your case you need to apply the looping in the variable creation.
Also in order to not loop through the static definitions we use concat to merge the lists E.g.
map_roles = concat( [ for index,value in toset(local.namespaces) :
{
rolearn = aws_iam_role.eks_namespace_admin_roles[value].arn
username = "${value}_admin_role"
groups = ["${value}:${value}_group"]
},
{
rolearn = aws_iam_role.eks_namespace_readonly_roles[value].arn
username = "${value}_readonly_role"
groups = ["${value}:${value}_group"]
}
],
[
{
rolearn = "arn:aws:iam::1234567890:role/AdminAccessRole"
username = "AdminAccessRole"
groups = ["system:masters"]
},
{
rolearn = "arn:aws:iam::1234567890:role/my-eks-admin-role"
username = "my_eks_admin_role"
groups = ["system:masters"]
}
])

With the help from Tolis, this is the answer to the issue.
It was still throwing out errors when tried to FOR loop a couple of map entries, so i split them up and concat multiple list instead.
module "eks_auth" {
source = "./terraform-modules/terraform-aws-eks-auth-1.0.0"
eks = module.eks
# Additional IAM roles to add to the aws-auth configmap.
for_each = toset(local.namespaces)
map_roles = concat(
[
for key in toset(local.namespaces) :
{
rolearn = aws_iam_role.eks_namespace_admin_roles[key].arn
username = "${key}_admin_role"
groups = ["${key}:${key}_group"]
}
],
[
for key in toset(local.namespaces) :
{
rolearn = aws_iam_role.eks_namespace_readonly_roles[key].arn
username = "${key}_readonly_role"
groups = ["${key}:${key}_group"]
}
],
[
{
rolearn = "arn:aws:iam::1234567890:role/AdminAccessRole"
username = "AdminAccessRole"
groups = ["system:masters"]
},
{
rolearn = "arn:aws:iam::1234567890:role/my-eks-admin-role"
username = "my_eks_admin_role"
groups = ["system:masters"]
}
])
# Additional IAM users to add to the aws-auth configmap.
map_users = [
]
}

Related

Terraform split all object in a list while using count.index

I have a terraform code like below.
locals {
org_sub_accounts = [
"111111111111",
"222222222222,
"333333333333",
]
role_arns = [
"arn:aws:iam::111111111111:role/DataConnector1",
"arn:aws:iam::222222222222:role/DataConnector2",
"arn:aws:iam::333333333333:role/DataConnector3",
]
}
resource "aws_cloudformation_stack_set_instance" "stack" {
count = length(local.org_sub_accounts)
account_id = local.org_sub_accounts[count.index]
region = "ap-east-1"
parameter_overrides = {
RoleName = local.role_arns[count.index]
}
stack_set_name = aws_cloudformation_stack_set.stackset.name
}
My problem is my RoleName should be DataConnector potion (after /) but not the entire ARN in the aws_cloudformation_stack_set_instance. How can I pass the RoleName DataConnector* within each index?
Note, here I defined the variables in the locals to show my use case. But actually those comes from other resource outputs.
This can be achieved by using the split built-in function:
locals {
role_names = [for arn in local.role_arns : split("/", arn)[1]]
}
With this part split("/", arn)[1] you are splitting the IAM role ARN into two parts (before and after the /) and with the index [1] you are effectively getting the second part of that list. Then, you would have to change the code to reflect that with:
resource "aws_cloudformation_stack_set_instance" "stack" {
count = length(local.org_sub_accounts)
account_id = local.org_sub_accounts[count.index]
region = "ap-east-1"
parameter_overrides = {
RoleName = local.role_names[count.index]
}
stack_set_name = aws_cloudformation_stack_set.stackset.name
}
[1] https://developer.hashicorp.com/terraform/language/functions/split

Terraform: How to use security group id in tfvars

I am creating an RDS option group with several options and one of them has the parameter "vpc_security_group_memberships" which takes the ID of a security group.
db_options.tfvars:
db_options = [
{
option_name = "OEM_AGENT"
option_settings = [
{
name = "AGENT_REGISTRATION_PASSWORD"
value = "****"
},
{
name = "OMS_HOST"
value = "gridcontrol.example.com"
},
{
name = "OMS_PORT"
value = "4000"
}
]
port = "3872"
version = "13.5.0.0.v1"
vpc_security_group_memberships = [ ]
}
]
And I create a security group in sg.tf:
resource "aws_security_group" "db-sg" {
description = "Allow access to RDS instance"
name = "${var.env}.${var.db_name}.sg"
tags = {
Name = "${var.env}.${var.db_name}.sg"
}
vpc_id = var.vpc_id
}
Is it possible to put the aws_security_group.db-sg.id for use in vpc_security_group_memberships?
Is it possible to put the aws_security_group.db-sg.id for use in vpc_security_group_memberships?
Sadly its not possible. TF variables can't be dynamic. They must be fully defined at plan time. But you can instead use locals. Local values can by dynamic.

Terraform, create map where list elements are values

I am trying to parse this variable in terraform. The main goal is to create map of "group" = "member" type. Did i set type in variable correct? Required output provided below.
variable "iam" "this"{
type = map(map(list(string)))
default = {
"admins" = {
"user" = [
"oleh_mykolaishyn#gmail.com",
"yevhen_plaksa#gmail.com"
]
"service_principal" = [
"dpaf2-dev-sa"
]
}
"dev" = {
"user" = [
"oleh_mykolaishyn#gmail.com",
"yevhen_plaksa#gmail.com"
]
"service_principal" = []
}
"ops" = {
"user" = [
"oleksandr_gorkun#gmail.com",
]
"service_principal" = [
"dpaf2-dev-sa",
"dpaf2-iacda-app-id",
]
}
}
}
Required output:
{
"admins" = "oleh_mykolaishyn#gmail.com"
"admins" = "yevhen_plaksa#gmail.com"
"admins" = "dpaf2-dev-sa"
"dev" = "oleh_mykolaishyn#gmail.com"
"dev" = "yevhen_plaksa#gmail.com"
"ops" = "oleksandr_gorkun#gmail.com"
"ops" = "dpaf2-dev-sa"
"ops" = "dpaf2-iacda-app-id"
}
Start with this and manipulate it to suit your exact needs. There are many ways to structure resources using maps, arrays and nesting.
locals {
service_principal = {
admin = ["dpaf2-dev-sa"]
dev = []
ops = [
"dpaf2-dev-sa",
"dpaf2-iacda-app-id",
]
}
users = {
admin = [
"oleh_mykolaishyn#gmail.com",
"yevhen_plaksa#gmail.com"
]
dev = [
"yevhen_plaksa#gmail.com"
]
ops = [
"oleksandr_gorkun#gmail.com"
]
}
result = flatten([for role in keys(local.users) : [for user in local.users[role] :
{ role = role, user = user, principals = local.service_principal[role] }]])
}
resource "null_resource" "users" {
count = length(local.result)
provisioner "local-exec" {
command = "echo user: $USER has role: $ROLE and principals: $SERVICE_PRINCIPAL >> ouput.txt"
environment = {
USER = local.result[count.index].user
ROLE = local.result[count.index].role
SERVICE_PRINCIPAL = join(", ", local.result[count.index].principals)
}
}
}
If you run:
terraform init
terraform plan
terraform apply --auto-approve
Then check your directory for a file named output.txt which will contain
user:oleh_mykolaishyn#gmail.com has role:admin and principals: dpaf2-dev-sa
user:yevhen_plaksa#gmail.com has role:dev and principals:
user:oleksandr_gorkun#gmail.com has role:ops and principals: dpaf2-dev-sa, dpaf2-iacda-app-id
user:yevhen_plaksa#gmail.com has role:admin and principals: dpaf2-dev-sa
I know the text file isn't your desired output but the loop is just a demonstration of how you can iterate over the local variable.
Again, many ways to sort and slice it. I avoided your desired output because maps cannot have duplicate keys so it'll never work.
If you want to see the result in isolation you can do:
terraform console
local.result
And it will appear in the terminal. To exit the console type exit.

How. to iterate list(map(list)) in terraform

I want to create files(key) in s3. By below code, I can successfully create the files in s3 -
locals {
rules = [
{
user = "user-1",
roles = "test"
}
}
resource "aws_s3_bucket_object" "this" {
for_each = zipmap(local.rules.*.user, local.rules.*)
bucket = "test-09129"
acl = "private"
source = "/dev/null"
key = "${each.value.roles}/${each.key}"
}
file in s3 - test/user-1 created
But roles can be multiple so I create list for roles.
locals {
rules = [
{
user = "user-1",
roles = ["test"]
},
{
user = "user-2",
roles = ["test", "dev", "prd"]
}]
}
Now I want to create files based on above list. How to iterate list(roles) to create files based on user and role?
desire output -
file in s3 -
test/user-1
test/user-2
dev/user-2
prd/user-2
You can flatten your rules into helper_rules (assuming I understand correctly your desired outcome):
locals {
rules = [
{
user = "user-1",
roles = ["test"]
},
{
user = "user-2",
roles = ["test", "dev", "prd"]
}]
helper_rules = merge([
for user, rules in zipmap(local.rules.*.user, local.rules.*.roles):
{ for rule in rules:
"${user}-${rule}" => {user = user, rule = rule}
}
]...)
}
This will result in helper_rules:
{
"user-1-test" = {
"rule" = "test"
"user" = "user-1"
}
"user-2-dev" = {
"rule" = "dev"
"user" = "user-2"
}
"user-2-prd" = {
"rule" = "prd"
"user" = "user-2"
}
"user-2-test" = {
"rule" = "test"
"user" = "user-2"
}
}
With that, your objects could be:
resource "aws_s3_bucket_object" "this" {
for_each = local.helper_rules
bucket = "test-09129"
acl = "private"
source = "/dev/null"
key = "${each.value.role}/${each.value.user}"
}

Terraform use a list of configuration blocks as an argument

The Terraform resource, aws_db_proxy, has a list of auth block(s) as an argument. Below is an example from the terraform documentation.
Each auth block represents a user, and each user needs a secret in Secrets Manager. Our platform has four different environments (dev,qa,cert,prod), and we do not use secrets in our lower environments to save on costs. Ideally, I would create two lists of auth blocks, one for lower environments and one for upper environments. Then, in the resource I could pick the appropriate one based on environment.
Is there a way to pass a list of auth blocks to the aws_db_proxy resource?
The other solution I was thinking of was to use two separate aws_db_proxy configurations and use the appropriate one for each environment using the count meta-argument. However, I think this could get a little messy.
resource "aws_db_proxy" "example" {
name = "example"
debug_logging = false
engine_family = "MYSQL"
idle_client_timeout = 1800
require_tls = true
role_arn = aws_iam_role.example.arn
vpc_security_group_ids = [aws_security_group.example.id]
vpc_subnet_ids = [aws_subnet.example.id]
auth {
auth_scheme = "SECRETS"
description = "user1"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.example1.arn
}
auth {
auth_scheme = "SECRETS"
description = "example2"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.example2.arn
}
auth {
auth_scheme = "SECRETS"
description = "example3"
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.example3.arn
}
tags = {
Name = "example"
Key = "value"
}
}
You could use dynamic blocks to create auth blocks dynamically.
An example usage would depend on exactly how are you defing your aws_secretsmanager_secret for each user, but you could also make it dynamic.
Below is sample code. I haven't run it as its aim is to demonstrate the concept of the use of dynamic blocks and how you could make your aws_secretsmanager_secret:
# list of users
variable "proxy_users" {
default = ["user1", "example2", "example3"]
}
# secret for each user
resource "aws_secretsmanager_secret" "mysecret" {
for_each = toset(var.proxy_users)
name = "example${each.key}"
# rest of attributes
}
resource "aws_db_proxy" "example" {
name = "example"
debug_logging = false
engine_family = "MYSQL"
idle_client_timeout = 1800
require_tls = true
role_arn = aws_iam_role.example.arn
vpc_security_group_ids = [aws_security_group.example.id]
vpc_subnet_ids = [aws_subnet.example.id]
# create auth for each user
dynamic "auth" {
for_each = var.proxy_users
content {
auth_scheme = "SECRETS"
description = auth.key
iam_auth = "DISABLED"
secret_arn = aws_secretsmanager_secret.mysecret[auth.key].arn
}
}
tags = {
Name = "example"
Key = "value"
}
}
Thank you #Marcin
I had the same issue but I needed to insert existing secrets arn. You really helped
I did the following if anybody needs it
locals {
secrets_list = [
"db-credentials/${var.env-name}/user1",
"db-credentials/${var.env-name}/user2",
"db-credentials/${var.env-name}/user3"
]
}
data "aws_secretsmanager_secret" "rds_secrets" {
for_each = toset(local.secrets_list)
name = each.key
}
resource "aws_db_proxy" "rds_db_proxy" {
name = "${var.env-name}-rds-proxy"
engine_family = "MYSQL"
idle_client_timeout = 900
require_tls = true
.
.
.
.
dynamic "auth" {
for_each = local.secrets_list
content {
secret_arn = data.aws_secretsmanager_secret.rds_secrets[auth.value].arn
auth_scheme = "SECRETS"
iam_auth = "REQUIRED"
}
}
}