Terraform - Adding specific users to a specific group - azure-active-directory

I have a 2 csv files named "_member.csv" and "_groups.csv".
Content from _member.csv :
first_name,last_name,department,job_title
Albator,Albator,Exploitation,Integrateur d'exploitation
Bingo,Bingo,Exploitation,Ingenieur de production
Cresus,Cresus,Etudes et developpements, Analyste developpeur
Content from _groups.csv :
members_groups
adm-xxx
exp-xxx
cod-xxx
tec-xxx
met-xxx
svc-xxx
My resource to create users :
resource "azuread_user" "Terra-Aad-User-Member" {
for_each = { for user in local.azure_members : user.first_name => user }
user_principal_name = format(
"%s%s#%s",
substr(lower(each.value.first_name), 0, 1),
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
}
My resource to create groups :
resource "azuread_group" "Terra-Aad-Group" {
for_each = { for group in local.azure_groups : group.members_groups => group }
display_name = format("%s", lower(each.value.members_groups))
security_enabled = true
}
My resource to add users to groups :
resource "azuread_group_member" "Terra-Aad-Member-In-Group" {
for_each = { for u in azuread_user.Terra-Aad-User-Member : u.department => u... if u.department == "Exploitation" }
group_object_id = values(azuread_group.Terra-Aad-Group).0.object_id
member_object_id = each.value.id
}
The provider :
terraform {
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "=2.28.1"
}
}
}
provider "azuread" {
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
data "azuread_domains" "Terra-Aad" {
only_initial = true
}
My variables.tf
locals {
domain_name = data.azuread_domains.Terra-Aad.domains.0.domain_name
azure_members = csvdecode(file("${path.module}/_member.csv"))
azure_groups = csvdecode(file("${path.module}/_groups.csv"))
}
When I launch 'terraform plan', I have this error :
│ Error: Unsupported attribute
│
│ on groupmember.tf line 5, in resource "azuread_group_member" "Terra-Aad-Member-In-Group":
│ 5: member_object_id = each.value.id
│ ├────────────────
│ │ each.value is tuple with 2 elements
│
│ This value does not have any attributes.
Is it possible to automatically add all users who have the department 'Exploitation' to the group 'adm-xxx'? The problem is in the line 'member_object_id = each.value.id' I may need a conditional expression but I don't know exactly what that line is.
Does anyone have an idea?
Ok. So I added a null_resource to get all the ids of my users who are on the "Exploitation" department like this :
resource "null_resource" "Id-Department" {
provisioner "local-exec" {
command = "az ad user list --filter \"department eq 'Exploitation'\" --query \"[].id\" -o tsv"
interpreter = ["PowerShell", "-Command"]
}
depends_on = [azuread_user.Terra-Aad-User-Member]
}
I modified my resource azuread_group_member like this :
resource "azuread_group_member" "Terra-Aad-Member-In-Group" {
for_each = { for u in azuread_user.Terra-Aad-User-Member : u.department => u... if u.department == "Exploitation" }
group_object_id = values(azuread_group.Terra-Aad-Group).0.object_id
member_object_id = null_resource.Id-Department
depends_on = [null_resource.Id-Department]
}
Now I get this error :
│ Error: Incorrect attribute value type
│
│ on groupmember.tf line 13, in resource "azuread_group_member" "Terra-Aad-Member-In-Group":
│ 13: member_object_id = null_resource.Id-Department
│ ├────────────────
│ │ null_resource.Id-Department is object with 2 attributes
│
│ Inappropriate value for attribute "member_object_id": string required.
What I need is to take all the results of the command in null_resource to this: member_object_id = null_resource.Id-Department
Is there a way to do this ?

For starters, it seems most simple just to extract the groups to a list. Then for azuread_group, you can just use toset. Once that is done, you can reference this group by the group name in the object. So within azuread_group_member you use the for expression to get only members that are have "Exploitation" - you can reference their member_object_id by each.value.object_id. And you can get the group_object_id from the set-built azuread_group objects.
locals {
azure_groups = [for v in csvdecode(file("${path.module}/_groups.csv")) : v.members_groups]
azure_members = csvdecode(file("${path.module}/_member.csv"))
}
resource "azuread_user" "Terra-Aad-User-Member" {
for_each = { for user in local.azure_members : "${user.first_name}-${user.last_name}" => user }
user_principal_name = format(
"%s%s#%s",
substr(lower(each.value.first_name), 0, 1),
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" "Terra-Aad-Group" {
for_each = toset(local.azure_groups)
display_name = lower(each.value)
security_enabled = true
}
resource "azuread_group_member" "Terra-Aad-Member-In-Group" {
for_each = {
for k, v in azuread_user.Terra-Aad-User-Member :
k => v if v.department == "Exploitation"
}
group_object_id = azuread_group.Terra-Aad-Group["adm-xxx"].object_id
member_object_id = each.value.object_id
}
As an aside, I have no idea how a null_resource is relevant here. You should have all the information you need without it.

Related

Better handling of multiple loops within 1 resource with Terraform

I'm trying to create multiple CNAME resources based on multiple A record resources and am struggling to find a way to achieve this.
I currently have the following in my variable.tfvars file:
hostnames = [
"webserver1",
"webserver2"
]
domain = domain.com
subdomain_addresses = [
"www-1",
"www-2",
"www-3"
]
I have the following output from my ec2 instance module which is based off the length of var.hostnames: (in this instance there will be 2 private IPs)
output "server_ip_addresses" {
value = aws_instance.this.*.private_ip
}
I currently have the following in my resource.tf file:
resource "aws_route53_record" "instance_record" {
for_each = toset(var.hostnames)
zone_id = data.aws_route53_zone.route53_zone.zone_id
name = "${each.value}.${var.domain}"
type = "A"
ttl = "300"
records = module.ec2_instance.server_ip_addresses
}
resource "aws_route53_record" "webservice_records" {
for_each = toset(var.subdomain_addresses)
zone_id = data.aws_route53_zone.route53_zone.zone_id
name = "${each.value}.${var.domain}"
type = "CNAME"
ttl = "300"
records = [values(aws_route53_record.instance_record)[*].name]
}
What I need is a way to have the webservice_records resource create a subdomain for each entry in var.subdomain_addresses but associate each subdomain with each aws_route53_record.instance_record.name output.
So for example instance_record creates the following A records:
webserver1.domain.com = 10.1.1.18
webserver2.domain.com = 10.1.1.19
and webservice_records creates the following CNAMEs:
www-1.domain.com = webserver1.domain.com, webserver2.domain.com
www-2.domain.com = webserver1.domain.com, webserver2.domain.com
www-3.domain.com = webserver1.domain.com, webserver2.domain.com
I am currently getting the following error:
╷
│ Error: Incorrect attribute value type
│
│ on resource.tf line 62, in resource "aws_route53_record" "webservice_records":
│ 62: records = [values(aws_route53_record.instance_record)[*].name]
│ ├────────────────
│ │ aws_route53_record.instance_record is object with 2 attributes
│
│ Inappropriate value for attribute "records": element 0: string required.
╵
Does someone have a better idea of how this can be achieved?
you can use the setproduct function to achieve that output like below:
so it would be like something like this to create a map:
locals {
hostnames = [
"webserver1",
"webserver2"
]
domain = "domain.com"
subdomain_addresses = [
"www-1",
"www-2",
"www-3"
]
webserver_list = [ for web_list in setproduct(local.hostnames, [local.domain]) : join(".",web_list) ]
cname_map = {for sub_domain in local.subdomain_addresses: sub_domain => local.webserver_list}
}
resource "aws_route53_record" "cnames" {
for_each = local.cname_map
zone_id = data.aws_route53_zone.route53_zone.zone_id
name = each.key
type = "CNAME"
ttl = "300"
records = each.value
}
cname_map looks like this:
cname_map= {
www-1 = [
"webserver1.domain.com",
"webserver2.domain.com",
]
www-2 = [
"webserver1.domain.com",
"webserver2.domain.com",
]
www-3 = [
"webserver1.domain.com",
"webserver2.domain.com",
]
}

Terraform for_each, Count Index

I am trying to access all the values from a for_each statement in google_compute_instance resource
I want to get all the values [dev-1, dev-2] in thename attribute and parse it to vm_name in my metadata_startup_script
resource "google_compute_instance" "compute_instance" {
project = var.project_id
for_each = toset(["1", "2"])
name = "dev-${each.key}"
machine_type = "e2-micro"
zone = "${var.region}-b"
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-1804-lts"
}
}
network_interface {
network = "default"
access_config {
}
}
lifecycle {
ignore_changes = [attached_disk]
}
metadata_startup_script = templatefile("copy.tftpl", {
vm_name = "${google_compute_instance.compute_instance.0.name}"
nfs_ip = "${google_filestore_instance.instance.networks[0].ip_addresses[0]}"
file_share_name = "${google_filestore_instance.instance.file_shares[0].name}"
zone = "${var.region}-b"
})
}
I am unable to get all compute instance from the name argument
I get this error message
╷
│ Error: Cycle: google_compute_instance.compute_instance["2"], google_compute_instance.compute_instance["1"]
│
│
How do I resolve this issue so I can get all the virtual machine name and parse it to vm_name variable?
I would change the hardcoded elements in your for_each to a variable,
and pass that to your vm_name, something like this:
locals {
compute_names = ["dev-1", "dev-2"]
}
resource "google_compute_instance" "compute_instance" {
project = var.project_id
for_each = toset(local.compute_names)
name = each.key
machine_type = "e2-micro"
zone = "${var.region}-b"
...
metadata_startup_script = templatefile("copy.tftpl", {
vm_name = local.compute_names
...
zone = "${var.region}-b"
})
}

Typolink to index Pages on solr

I setup a TYPO3 v.9 website with solr.
And for some reason, the Pages indexer not working. So I used the custom TS configuration bellow, to work around and to force Pages indexing. With this custom TS, pages was indexed, BUT the links to a Page from the "Search results" is not working.
This is my custom TS :
#TS added to force page indexing
plugin.tx_solr.index.queue {
snar_pages = 1
snar_pages {
table = pages
fields {
title = title
content = SOLR_CONTENT
content {
cObject = COA
cObject {
10 = TEXT
10 {
field = bodytext
noTrimWrap = || |
}
}
}
url = CASE
url {
key.field = type
# Internal
1 = TEXT
1 {
if.isTrue.field = internalurl
typolink.parameter.field = internalurl
typolink.useCacheHash = 1
typolink.returnLast = url
}
# External
2 = TEXT
2 {
if.isTrue.field = externalurl
field = externalurl
}
default = TEXT
default {
typolink.parameter = {link}
typolink.additionalParams >
typolink.useCacheHash = 1
typolink.returnLast = url
}
}
}
}
is there an error in my TS that prevents the links to the pages from working ?
hmm i think you missed something to setup too for indexing pages. The DataFrontendHelper
see the docs
edited:
plugin.tx_solr {
index {
queue {
pages {
indexer {
frontendDataHelper {
host = {$plugin.tx_solr.indexer.frontendDataHandler.host}
scheme = {$plugin.tx_solr.indexer.frontendDataHandler.scheme}
}
}
}
}
}
}
Changing my TS as follows, and this fixed my problem :
url = CASE
url {
key.field = doktype
default = TEXT
default {
field = title
typolink.parameter.field = uid
typolink.returnLast = url
}
PS : pages need to be re-indexed for good results.

Create database schema with terraform

I created RDS instance using aws_db_instance (main.tf):
resource "aws_db_instance" "default" {
identifier = "${module.config.database["db_inst_name"]}"
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "${module.config.database["db_name_prefix"]}${terraform.workspace}"
username = "${module.config.database["db_username"]}"
password = "${module.config.database["db_password"]}"
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
}
Can I also create database schemas from file schema.sql with terraform apply?
$ tree -L 1
.
├── main.tf
└── schema.sql
You can use a provisioner (https://www.terraform.io/docs/provisioners/index.html) for that:
resource "aws_db_instance" "default" {
identifier = module.config.database["db_inst_name"]
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "${module.config.database["db_name_prefix"]}${terraform.workspace}"
username = module.config.database["db_username"]
password = module.config.database["db_password"]
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
provisioner "local-exec" {
command = "mysql --host=${self.address} --port=${self.port} --user=${self.username} --password=${self.password} < ./schema.sql"
}
}
#Apply scheme by using bastion host
resource "aws_db_instance" "default_bastion" {
identifier = module.config.database["db_inst_name"]
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
name = "${module.config.database["db_name_prefix"]}${terraform.workspace}"
username = module.config.database["db_username"]
password = module.config.database["db_password"]
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
provisioner "file" {
connection {
user = "ec2-user"
host = "bastion.example.com"
private_key = file("~/.ssh/ec2_cert.pem")
}
source = "./schema.sql"
destination = "~"
}
provisioner "remote-exec" {
connection {
user = "ec2-user"
host = "bastion.example.com"
private_key = file("~/.ssh/ec2_cert.pem")
}
command = "mysql --host=${self.address} --port=${self.port} --user=${self.username} --password=${self.password} < ~/schema.sql"
}
}
mysql client needs to be installed on your device.
If you don't have direct access to your DB, there is also a remote-exec provisioner, where you can use a bastion host (transfer file to remote place with file provisioner first).
If your schema is not to complex, you could also use the MySQL provider of terraform:
https://www.terraform.io/docs/providers/mysql/index.html

Wagtail Snippets permissions per group

I have a Wagtail site where every group can work on a different page tree, with different images and documents permissions.
That is a multisite setup where I am trying to keep sites really separate.
Is that possible to limit the snippets permissions on a per-group basis?
I would like my groups to see just a subset of the snippets.
I was facing something similar when I wanted to use Site settings.
The only solution I found was to create a custom model and using ModelAdmin.
Some ‘snippets’ to get you on the run:
class SiteSettings(models.Model):
base_form_class = SiteSettingsForm
COMPANY_FORM_CHOICES = (
('BED', 'Bedrijf'),
('ORG', 'Organisatie'),
('STI', 'Stichting'),
('VER', 'Vereniging'),
)
site = models.OneToOneField(
Site,
unique = True,
db_index = True,
on_delete = models.CASCADE,
verbose_name = _('site'),
related_name = 'site_settings',
help_text = _('The sites these setting belong to.')
)
company_name = models.CharField(
_('company name'),
blank = True,
max_length = 50,
help_text = _('De naam van het bedrijf of de organisatie.')
)
company_form = models.CharField(
_('company form'),
max_length = 3,
blank = True,
default = 'COM',
choices = COMPANY_FORM_CHOICES
)
...
class MyPermissionHelper(PermissionHelper):
def user_can_edit_obj(self, user, obj):
result = super().user_can_edit_obj(user, obj)
if not user.is_superuser:
user_site = get_user_site(user)
result = user_site and user_site == obj.site
return result
class SiteSettingsAdmin(ThumbnailMixin, ModelAdmin):
model = SiteSettings
menu_label = _('Site settings')
menu_icon = 'folder-open-inverse'
add_to_settings_menu = True
list_display = ['admin_thumb', 'company_name', 'get_categories']
list_select_related = True
list_display_add_buttons = 'site'
thumb_image_field_name = 'logo'
thumb_col_header_text = _('logo')
permission_helper_class = MyPermissionHelper
create_view_class = CreateSiteSettingsView
...
class CreateSiteSettingsView(SiteSettingsViewMixin, CreateView):
#cached_property
def sites_without_settings(self):
sites = get_sites_without_settings()
if not sites:
messages.info(
self.request,
_('No sites without settings found.')
)
return sites
def dispatch(self, request, *args, **kwargs):
if request.user.is_superuser and not self.sites_without_settings:
return redirect(self.url_helper.get_action_url('index'))
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial().copy()
current_site = self.request.site
initial.update({
'company_name': current_site.site_name}
)
if self.request.user.is_superuser:
initial.update({
'site': current_site}
)
return initial
def get_form(self):
form = super().get_form()
flds = form.fields
if self.request.user.is_superuser:
fld = form.fields['site']
fld.queryset = self.sites_without_settings.order_by(
Lower('site_name')
)
return form
def form_valid(self, form):
instance = form.save(commit=False)
if not self.request.user.is_superuser:
instance.site = self.request.site
instance.save()
messages.success(
self.request, self.get_success_message(instance),
buttons=self.get_success_message_buttons(instance)
)
return redirect(self.get_success_url())

Resources