Terraform Version
Terraform v1.3.2
provider registry.terraform.io/okta/okta v3.36.0
Affected Resource(s)
okta_app_group_assignments
okta_group_memberships
okta_user
I am trying to configure and implement Okta SSO / MFA via Terraform. Reasoning is because I want to manage my organisation accounts and app integrations via terraform as I am wanting to achieve an automated approach to creating a user > assigning user to a group dependent on department > assign apps to user all through a github repo where the user can change the auto .tfvars file, input their user information and request via a PR (approved by myself who is a super admin).
This will be very cool, as the config will ultimately automate group assignment and apps dependent on the department inputted.
I am struggling discovering a nice easy way to loop over an iterate user / group ids with a conditional on where, lets say;
okta_user.user.department == okta_group_description (this would be very cool)
I have tried conditional logic with an if statement similar to code below and had no luck.
The code below may not necessarily show a clear understanding, but almost an attempt to get this working.
If anyone could aid or show configured code for my use case. That would be brill :)
Actual Behavior
errors on multiple different reasons on my attempts to configure like this.
Steps to Reproduce
I have also tried adding users like this in the group_membership resource:
["${okta_user.user[each.key].id}"]
But get an error:
│ Error: Invalid index
│
│ on main.tf line 115, in resource "okta_group_memberships" "ba":
│ 115: users = ["${okta_user.user[each.key].id}"]
│ ├────────────────
│ │ each.key is "BA"
│ │ okta_user.user is object with 2 attributes
│
│ The given key does not identify an element in this collection value.
Terraform Configuration Files
resource "okta_group" "groups" {
for_each = var.groupList
name = each.value.name
description = each.value.description
}
resource "okta_user" "user"{
#for_each = {for i, v in var.userList: i => v}
for_each = var.userList
first_name = each.value.first_name
last_name = each.value.last_name
display_name = each.value.display_name
email = each.value.email
login = each.value.login
department = each.value.department
organization = each.value.organization
}
resource "okta_group_memberships" "devops" {
for_each = {for group, n in var.groupList: group => n if n.name == "DevOps"}
group_id = okta_group.groups[each.key].id
users = []
depends_on = [okta_group.groups]
}
resource "okta_app_group_assignments" "everyone" {
for_each = { for k in compact([for k, v in var.samlAppList: v.admin_note ? k : ""]): k => var.samlAppList[k] }
app_id = okta_app_saml.saml_apps[each.key].id
group {
id = okta_group.everyone.id
priority = 1
}
}
#variables.tf
variable "groupList"{
type = map(object({
name = string
description = string
}))
}
#auto.tfvars
groupList = {
devOps = {
name : "DevOps"
description : "DevOps"
},
DS = {
name : "DS"
description : "DS"
},
BA = {
name : "BA"
description : "BA"
},
FrontEnd = {
name : "FrontEnd"
description : "FrontEnd"
},
BackEnd = {
name : "BackEnd"
description : "BackEnd"
}
}
#User Information
variable "userList" {
type = map(object({
first_name = string
last_name = string
display_name = string
email = string
login = string
department = string
organization = string
}))
}
#User Details
userList = {
tom = {
first_name : ""
last_name : ""
display_name : ""
email : ""
login : ""
department : ""
organization : ""
}
}
Related
I need to add azure ad users with a specific department in a specific group.
My first block is to read all the users on my Azure Active Directory.
data "azuread_users" "users" {
return_all = true
}
Then, for each user who has the "codeur" department, they must go to my "cod-xxx01" group.
resource "azuread_group_member" "Terra-Aad-Member-Cod" {
for_each = { for allusers, cod in data.azuread_users.users : allusers => cod if cod.data.azuread_users.users.department == "codeur" }
group_object_id = azuread_group.Terra-Aad-Group["cod-xxx01"].object_id
member_object_id = each.value.object_id
}
My errors are :
│ Error: Unsupported attribute
│
│ on 3_ServiceGroupsMembers.tf line 32, in resource "azuread_group_member" "Terra-Aad-Member-Cod":
│ 32: for_each = { for allusers, cod in data.azuread_users.users : allusers => cod if cod.azuread_users.users.department == "codeur" }
│
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│
│ on 3_ServiceGroupsMembers.tf line 32, in resource "azuread_group_member" "Terra-Aad-Member-Cod":
│ 32: for_each = { for allusers, cod in data.azuread_users.users : allusers => cod if cod.azuread_users.users.department == "codeur" }
│
│ Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list?
Output of the data source:
{
account_enabled = true
display_name = "Name SURNAME"
mail = ""
mail_nickname = "xxxxx"
object_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
onpremises_immutable_id = ""
onpremises_sam_account_name = ""
onpremises_user_principal_name = ""
usage_location = ""
user_principal_name = "xxx#xx.xx"
}
It seems , user stored in cod is again checked with overall users ,( cod if cod.data.azuread_users.users.department == "codeur" ) which is incorrect.
Please check to iterate for one user each time .
And correct the following values with id and each
group_object_id = azuread_group. Terra-Aad-Group.id
member_object_id = data.azuread_user.user[each.key].id
I have tried to reproduce in my environment.
Firstly downloaded azure ad users into csv as below to local drive.
Called them to local environment and used.
CODE:
locals {
users = csvdecode(file("C:/exportUsers.csv"))
}
resource "azuread_user" "users" {
for_each = { for i , user in local.users : i => user }
user_principal_name = each.value.UserPrincipalName
// for_each = { for user in local.users : user.UPN => user }
// user_principal_names = each.value.UPN
}
resource "azuread_group" "Terra-Aad-Group" {
display_name = "cod-xxx01"
security_enabled = true
}
resource "azuread_group_member" "Terra-Aad-Member-Cod" {
// for_each = { for i, user in data.azuread_users.users : i => user if user.department == " HR xxxxxxx Programs" }
for_each = { for i, user in data.azuread_user.users : i => user if user.department == "xxxxx" }
group_object_id = azuread_group.Terra-Aad-Group.object_id
member_object_id =data.azuread_users.users[each.key].id
}
Note: Please make sure you have User.ReadWrite.All or
Directory.ReadWrite.All and Group.ReadWrite.All , GroupMember.ReadWrite.All delegated and
application permissions to do these operations.
azuread_users | Terraform Registry doesnot have department attribute but azuread_user | Terraform Registry has department attribute.
I am fairly new to terraform and trying to create a google_compute_backend_service using terraform and there are multiple backend blocks inside the resource as shown below:
resource "google_compute_backend_service" "app-backend" {
log_config {
enable = "true"
sample_rate = "1"
}
name = "app-backend"
port_name = "http-34070"
project = "my-project"
protocol = "HTTP"
session_affinity = "NONE"
timeout_sec = "30"
backend {
group = "insatnce-group1"
}
backend {
group = "instance-group2"
}
backend {
group = "instance-group3"
}
health_checks = [google_compute_http_health_check.app-http-l7.name]
}
As seen in the code block above the backend block repeats multiple times. I want to make it dynamic so I do not have to write multiple blocks manually.
I tried the following:
Created a variable in the variables.tf file that contains all the instance groups:
variable "groups" {
type = list(object({
name = string
}))
default = [{ name = "instance-group1"},
{ name = "instance-group2"},
{ name = "instance-group3"}]
}
And modified my resource block to this:
resource "google_compute_backend_service" "app-backend" {
log_config {
enable = "true"
sample_rate = "1"
}
name = "app-backend"
port_name = "http-34070"
project = "my-project"
protocol = "HTTP"
session_affinity = "NONE"
timeout_sec = "30"
dynamic "backend" {
for_each = var.groups
iterator = item
group = item.value.name
}
health_checks = [google_compute_http_health_check.app-http-l7.name]
}
However, when I execute terraform plan I get the following error:
Error: Unsupported argument
│
│ on backend_service.tf line 15, in resource "google_compute_backend_service" "app-backend":
│ 15: group = item.value.name
│
│ An argument named "group" is not expected here.
Where am I going wrong? Is there a better way to achieve this?
You can check the dynamic blocks documentation for the syntax. Otherwise, you had the right idea.
dynamic "backend" {
for_each = var.groups
content {
group = backend.value.name
}
}
You can also simplify the variable structure to make this even easier.
variable "groups" {
type = set(string)
default = ["instance-group1", "instance-group2", "instance-group3"]
}
dynamic "backend" {
for_each = var.groups
content {
group = backend.value
}
}
I would like to create an AWS account with SSO Account Assignments in the same first terraform run without hit the for_each limitation with dynamic values that cannot be predicted during plan.
I've tried to separate the aws_organizations_account resource from aws_ssoadmin_account_assignment in completely separate TF module and also I tried to use depends_on between those resources and modules.
What is the simplest and correct way to fix this issue?
Terraform v1.2.4
AWS SSO Account Assignments Module
Closed Pull Request that did not fix this issue
main.tf file (aws module)
resource "aws_organizations_account" "account" {
name = var.aws_account_name
email = "${var.aws_account_name}#gmail.com"
tags = {
Name = var.aws_account_name
}
parent_id = var.aws_org_folder_id
}
data "aws_identitystore_group" "this" {
for_each = local.group_list
identity_store_id = local.identity_store_id
filter {
attribute_path = "DisplayName"
attribute_value = each.key
}
}
data "aws_identitystore_user" "this" {
for_each = local.user_list
identity_store_id = local.identity_store_id
filter {
attribute_path = "UserName"
attribute_value = each.key
}
}
data "aws_ssoadmin_instances" "this" {}
locals {
assignment_map = {
for a in var.account_assignments :
format("%v-%v-%v-%v", aws_organizations_account.account.id, substr(a.principal_type, 0, 1), a.principal_name, a.permission_set_name) => a
}
identity_store_id = tolist(data.aws_ssoadmin_instances.this.identity_store_ids)[0]
sso_instance_arn = tolist(data.aws_ssoadmin_instances.this.arns)[0]
group_list = toset([for mapping in var.account_assignments : mapping.principal_name if mapping.principal_type == "GROUP"])
user_list = toset([for mapping in var.account_assignments : mapping.principal_name if mapping.principal_type == "USER"])
}
resource "aws_ssoadmin_account_assignment" "this" {
for_each = local.assignment_map
instance_arn = local.sso_instance_arn
permission_set_arn = each.value.permission_set_arn
principal_id = each.value.principal_type == "GROUP" ? data.aws_identitystore_group.this[each.value.principal_name].id : data.aws_identitystore_user.this[each.value.principal_name].id
principal_type = each.value.principal_type
target_id = aws_organizations_account.account.id
target_type = "AWS_ACCOUNT"
}
main.tf (root)
module "sso_account_assignments" {
source = "./modules/aws"
account_assignments = [
{
permission_set_arn = "arn:aws:sso:::permissionSet/ssoins-0000000000000000/ps-31d20e5987f0ce66",
permission_set_name = "ReadOnlyAccess",
principal_type = "GROUP",
principal_name = "Administrators"
},
{
permission_set_arn = "arn:aws:sso:::permissionSet/ssoins-0000000000000000/ps-955c264e8f20fea3",
permission_set_name = "ReadOnlyAccess",
principal_type = "GROUP",
principal_name = "Developers"
},
{
permission_set_arn = "arn:aws:sso:::permissionSet/ssoins-0000000000000000/ps-31d20e5987f0ce66",
permission_set_name = "ReadOnlyAccess",
principal_type = "GROUP",
principal_name = "Developers"
},
]
}
The important thing about a map for for_each is that all of the keys must be made only of values that Terraform can "see" during the planning step.
You defined local.assignment_map this way in your example:
assignment_map = {
for a in var.account_assignments :
format("%v-%v-%v-%v", aws_organizations_account.account.id, substr(a.principal_type, 0, 1), a.principal_name, a.permission_set_name) => a
}
I'm not personally familiar with the aws_organizations_account resource type, but I'm guessing that aws_organizations_account.account.id is an attribute whose value gets decided by the remote system during the apply step (once the object is created) and so this isn't a suitable value to use as part of a for_each map key.
If so, I think the best path forward here is to use a different attribute of the resource that is defined statically in your configuration. If var.aws_account_name has a static value defined in your configuration (that is, it isn't derived from an apply-time attribute of another resource) then it might work to use the name attribute instead of the id attribute:
assignment_map = {
for a in var.account_assignments :
format("%v-%v-%v-%v", aws_organizations_account.account.name, substr(a.principal_type, 0, 1), a.principal_name, a.permission_set_name) => a
}
Another option would be to remove the organization reference from the key altogether. From what you've shared it seems like there is only one account and so all of these keys would end up starting with exactly the same account name anyway, and so that string isn't contributing to the uniqueness of those keys. If that's true then you could drop that part of the key and just keep the other parts as the unique key:
assignment_map = {
for a in var.account_assignments :
format(
"%v-%v-%v",
substr(a.principal_type, 0, 1),
a.principal_name,
a.permission_set_name,
) => a
}
I have the following csv file :
first_name,last_name,department,job_title
Michael,Scott,TF_TEST1,Manager
Jim,Halpert,TF_TEST1,Engineer
Pam,Beesly,TF_TEST2,Engineer
I want to create all those users and set them member of the group corresponding to their departement, like :
User
Group
Michael Scott
TF_TEST1
Jim Halpert
TF_TEST1
Pam Beesly
TF_TEST2
Here is what I have so far :
# Configure the Azure Active Directory Provider
provider "azuread" {}
# Retrieve domain information
data "azuread_domains" "default" {
only_initial = true
}
locals {
domain_name = data.azuread_domains.default.domains.0.domain_name
users = csvdecode(file("${path.module}/users.csv"))
groups = toset(local.users[*].department)
}
resource "azuread_user" "users" {
for_each = { for user in local.users : user.first_name => user }
user_principal_name = format(
"%s.%s#%s",
lower(each.value.first_name),
lower(each.value.last_name),
local.domain_name
)
password = format(
"%s%s%s!",
lower(each.value.last_name),
substr(lower(each.value.first_name), 0, 1),
length(each.value.first_name)
)
force_password_change = true
display_name = "${each.value.first_name} ${each.value.last_name}"
department = each.value.department
job_title = each.value.job_title
}
resource "azuread_group" "groups" {
for_each = local.groups
display_name = each.key
security_enabled = true
assignable_to_role = true
}
Users and groups get created just fine.
However I can't figure a way of adding those users inside their corresponding groups.
I feel like I should itarate through my azuread_user.users and azuread_group.groups to make the binding using a group_member resources but no chance.
Or maybe that would be easier using the members = [] property from group resource ?
Any help will be appreciated :)
As per our discussion from the comments, you can achieve what you want by using a combination of values built-in function [1] and if instead of the ternary operator:
resource "azuread_group" "groups" {
for_each = local.groups
display_name = each.key
security_enabled = true
assignable_to_role = true
members = [ for u in values(azuread_user.users) : u.id if u.department == each.key ]
}
[1] https://www.terraform.io/language/functions/values
I'm tring to create a zabbix template with applications defined and trigger.
I can create the template, import my hosts and associate to it.
Now when I try to add the trigger to the template, I receive the error in the object.
this is my
data.tf
data "zabbix_hostgroup" "group" {
name = "Templates"
}
data "zabbix_template" "template" {
for_each = {
common_simple = { name = "Common Simple" }
common_snmp = { name = "Common SNMP" }
class_template = { name = var.class_names[var.class_id] }
}
name = each.value.name
}
data "zabbix_proxy" "proxy" {
for_each = {
for inst in var.instances :
"${inst.instance}.${inst.site}" => inst.site
}
#host = "zabpxy01.${each.value}.mysite.local"
host = "mon-proxy1.${each.value}.mtsite.local"
}
and this is my hosts.tf:
# create host group for specific to service
resource "zabbix_hostgroup" "hostgroup" {
name = var.class_names[var.class_id]
}
# create template
resource "zabbix_template" "template" {
host = var.class_id
name = var.class_names[var.class_id]
description = var.class_names[var.class_id]
groups = [
data.zabbix_hostgroup.group.id
]
}
# create application
resource "zabbix_application" "application" {
hostid = data.zabbix_template.template.id
name = var.class_names[var.class_id]
}
# create snmp disk_total item
resource "zabbix_item_snmp" "disk_total_item" {
hostid = data.zabbix_template.template.id
key = "snmp_disk_root_total"
name = "Disk / total"
valuetype = "unsigned"
delay = "1m"
snmp_oid="HOST-RESOURCES-MIB::hrStorageSize[\"index\", \"HOST-RESOURCES-MIB::hrStorageDescr\", \"/\"]"
depends_on = [
data.zabbix_template.template
]
}
# create snmp disk_used item
resource "zabbix_item_snmp" "disk_used_item" {
hostid = data.zabbix_template.template.id
key = "snmp_disk_root_used"
name = "Disk / used"
valuetype = "unsigned"
delay = "1m"
snmp_oid="HOST-RESOURCES-MIB::hrStorageUsed[\"index\", \"HOST-RESOURCES-MIB::hrStorageDescr\", \"/\"]"
depends_on = [
data.zabbix_template.template
]
}
# create trigger > 75%
resource "zabbix_trigger" "trigger" {
name = "Disk Usage 75%"
expression = "({${data.zabbix_template.template.host}:${zabbix_item_snmp.disk_used_item.key}.last()} / {${data.zabbix_template.template.host}:${zabbix_item_snmp.disk_total_item.key}.last()}) * 100 >= 75"
priority = "warn"
enabled = true
multiple = false
recovery_none = false
manual_close = false
}
# create hosts
resource "zabbix_host" "host" {
for_each = {
for inst in var.instances : "${var.class_id}${format("%02d", inst.instance)}.${inst.site}" => inst
}
host = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["hostname"]
name = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["hostname"]
enabled = false
proxyid = data.zabbix_proxy.proxy["${each.value.instance}.${each.value.site}"].id
groups = [
zabbix_hostgroup.hostgroup.id
]
templates = concat ([
data.zabbix_template.template["common_simple"].id,
data.zabbix_template.template["common_snmp"].id,
zabbix_template.template.id
])
# add SNMP interface
interface {
type = "snmp"
ip = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["mgmt0"]
main = true
port = 161
}
# Add Zabbix Agent interface
interface {
type = "agent"
ip = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["mgmt0"]
main = true
port = 10050
}
macro {
name = "{$INTERFACE_MONITOR}"
value = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["mgmt0"]
}
macro {
name = "{$SNMP_COMMUNITY}"
value = var.ip_addresses[var.class_id][each.value.site][each.value.instance]["snmp"]
}
depends_on = [
zabbix_hostgroup.hostgroup,
data.zabbix_template.template,
data.zabbix_proxy.proxy,
]
}
output "class_template_id" {
value = zabbix_template.template.id
description = "Template ID of created class template for items"
}
When I run "Terraform plan" I receive the error:
Error: Missing resource instance key │ │ on hosts/hosts.tf line 26,
in resource "zabbix_application" "application": │ 26: hostid =
data.zabbix_template.template.id │ │ Because
data.zabbix_template.template has "for_each" set, its attributes must
be accessed on specific instances. │ │ For example, to correlate with
indices of a referring resource, use: │
data.zabbix_template.template[each.key]
Where is my error?
Thanks for the support
UPDATE
I tried to use
output "data_zabbix_template" {
value = data.zabbix_template.template
}
but I don't see any output when I run terraform plan
I tried to modify in:
hostid = data.zabbix_template.template.class_template.id
but I continue to receive the same error:
Error: Missing resource instance key on hosts/hosts.tf line 27, in
resource "zabbix_application" "application": 27: hostid =
data.zabbix_template.template.class_template.id Because
data.zabbix_template.template has "for_each" set, its attributes must
be accessed on specific instances.
For example, to correlate with indices of a referring resource, use:
data.zabbix_template.template[each.key]
Error: Unsupported attribute on hosts/hosts.tf line 27, in resource
"zabbix_application" "application": 27: hostid =
data.zabbix_template.template.class_template.id This object has no
argument, nested block, or exported attribute named "class_template".
UPDATE:
My script for each host taht I'll add, set two existing template ("Common Simple" and "Common SNMP") and create a new template as below:
# module.mytemplate-servers_host.zabbix_template.template will be created
+ resource "zabbix_template" "template" {
+ description = "mytemplate-servers"
+ groups = [
+ "1",
]
+ host = "mytemplate-servers"
+ id = (known after apply)
+ name = "mytemplate-servers"
}
Now my scope is to add on this template an application and set two items and one trigger
When you use for_each in a data source or resource, the output of that data source or resource is a map, where the keys in the map are the same as the keys in the for_each and the values are the regular output of that data/resource for the given input value with that key.
Try using:
output "data_zabbix_template" {
value = data.zabbix_template.template
}
And you'll see what I mean. The output will look something like:
data_zabbix_template = {
common_simple = {...}
common_snmp = {...}
class_template = {...}
}
So in order to use this data source (on the line where the error is being thrown), you need to do:
hostid = data.zabbix_template.template.common_simple.id
And replace common_simple in that line with whichever key in the for_each you want to use. You'll need to do this everywhere that you use data.zabbix_template.template.