share multiple DNS domain with multiple aws accounts by terraform in AWS Resource Access Manager - amazon-web-services

I'm forwarding DNS requests sent to a list of internal domains (on premise) by using AWS Route53 resolver. By terraform, I want to share the rules I created to other accounts of the company, so I have the following:
# I create as much share endpoint as domain I have, so If I have 30 domains, I'll make 30 endpoint RAM:
resource "aws_ram_resource_share" "endpoint_share" {
count = length(var.forward_domain)
name = "route53-${var.forward_domain[count.index]}-share"
allow_external_principals = false
}
# Here I share every single endpoint with all the AWS ACcount we have
resource "aws_ram_principal_association" "endpoint_ram_principal" {
count = length(var.resource_share_accounts)
principal = var.resource_share_accounts[count.index]
resource_share_arn = {
for item in aws_ram_resource_share.endpoint_share[*]:
item.arn
}
}
The last block, calls the arn output of the first one which is a list.
Now, this last block doesn't work, I don't know how to use multiple counts, when I run this, I get the following error:
Error: Invalid 'for' expression
line 37: Key expression is required when building an object.
Any idea how to make this work?
Terraform version: 0.12.23

Use square brackets in resource_share_arn, like this:
resource_share_arn = [
for item in aws_ram_resource_share.endpoint_share[*]:
item.arn
]

Related

Nesting for_each loops and configuring multiple regions in Python

We have a long(ish) list of ip addresses to trust, for services published by CloudFlare, and rather than asking each team that publishes a service from an account in aws to implement this in their security groups / acls etc. I thought a prefix list would be perfect. I would like to set this up in a central account, that is then shared to all the child accounts across the aws Organisation. Ideally, I would like to avoid declaring more resources than necessary, so I am using for_each loops and a dynamic entry. So far so good.
However, prefix lists are not a global object, meaning they need to be created per region, and Terraform requires that this is set on the Provider level.
Is there a way to have a single resource declaration work with a single local map to dynamically manage all these moving parts?
It seems the fewest steps I can do, is to have a resource declaration per region, and then a local map per provider, which is already making 6 "blocks" for only 3 regions, + 2 lists for ipv4 and ipv6...
Here's the code example:
locals {
# The two ip lists to add to appropriate prefix lists
cloudflare-ips = {
prefixlist_cloudflare_ipv4 = {
ips = [
"10.0.0.0/32",
"173.245.48.0/20",
...
],
type = "IPv4",
},
prefixlist_cloudflare_ipv6 = {
ips = [
"2400:cb00::/32",
"2606:4700::/32",
...
],
type = "IPv6",
}
}
}
# Generate multipel predix lists from the map of ipv4 and ipv6 addresses
resource "aws_ec2_managed_prefix_list" "cloudflare-ipv4" {
for_each = local.cloudflare-ips
name = "cloudflare_${each.value.type}"
# Here is where I have to add a provider for the region, and this cannot be done within any type of loop ?
provider = aws.ap-southeast-1
address_family = each.value.type
max_entries = 50
dynamic "entry" {
for_each = tolist( each.value.ips )
content {
cidr = entry.value
description = "CloudFlare ${entry.key}"
}
}
}
I've tried including a list of strings with region / provider names and calling these directly from inside the loop, but even though it can evaluate a string (e.g. provider = "aws.ap-southeast-1") it does not accept reading from the local map like so:
provider = each.value.region
with error:
│ The provider argument requires a provider type name, optionally followed by a period and then a configuration alias.
I guess the next step would be to make this into a module ? Any other suggestions... ?
this cannot be done within any type of loop
That's correct. You can't have dynamic provider, thus you can't use any loops and variables. Its value must be hardcoded.

Add new users as members to GCP Cloud Identity Group using Terraform

I have the gcp-organization-admins Cloud Identity User Group to which I want to add a new user user-01#example.com as a Member using Terraform.
Getting error - Error creating GroupMembership: googleapi: got HTTP response code 404.
The requested URL /v1beta1/gcp-organization-admins#example.com/memberships?alt=json was not found on this server.
Can anyone suggest how to resolve this please.
fyi...Just as a test, I was able to create new Cloud Identity user groups and added some test users into it without any problems using Terraform module https://github.com/terraform-google-modules/terraform-google-group
#=====================
# terraform.tfvars
#=====================
org_admin_user = ["user-01#example.com"]
org_admin_group = "gcp-organization-admins#example.com"
#=========================================================
# add-member.tf (adds user to google group as a member)
#=========================================================
resource "google_cloud_identity_group_membership" "user-01" {
for_each = toset(var.org_admin_user)
provider = google-beta
group = var.org_admin_group
preferred_member_key {
id = each.key
}
roles {
name = "MEMBER"
}
}
I ran into this same problem. Turns out that Terraform wants the GCP Group "name" and not the email address.
So the group attribute of the google_cloud_identity_group_membership resource block should look something like "groups/23097432uwhwiyo" and not "gcp-organization-admins#example.com"
You can look up the group "name" with the following gcloud command:
gcloud identity groups describe "gcp-organization-admins#example.com"
This might to be the same issue as documented here: https://github.com/hashicorp/terraform-provider-google/issues/7616
A comment in that bug mentions the following:
I can work around the above issue by switching the order of the two roles in the resource, i.e.
From
roles { name = "MANAGER" }
roles { name = "MEMBER" }
to
roles { name = "MEMBER" }
roles { name = "MANAGER" }
https://github.com/hashicorp/terraform-provider-google/issues/7616#issuecomment-742779169

How to create multi-value SRV DNS record with terraform in AWS Route53 service?

I am trying to create multi-value SRV DNS entry in AWS route53 service via terraform. Values should be taken from instances tags. Due to the fact, that this is only one record, approach with count is not applicable.
The trick is, that I have 10 instances but they need to be filtered first by finding specific tags. Based on resultlist, SRV record should be created by using the Name tag assigned to each instance.
Any idea how to approach this issue?
Thanks in advance for any tip.
I did it like this:
resource "aws_instance" "myservers" {
count = 3
#.... other configuration....
}
resource "aws_route53_record" "srv" {
zone_id = aws_route53_zone.myzone.zone_id
name = "_service"
type = "SRV"
records = [for server in data.aws_instance.myservers : "0 10 5000 ${server.private_ip}."]
}
Terraform's for expression is being the key for the solution.
Regarding the SRV record in AWS Route 53, it should have a line per server and each line in the following form: priority weight port target (space is the delimiter). For the example above: 0 is the priority, 10 is the weight, 5000 is the port and the last one is the server IP (or name)

Terraform and GCS : how to create a multi zone LB

I want to create a load balancer adressing two or more instances on several zones of the same region in GCP
I start like that :
- create a backend service adressing two instance groups :
resource "google_compute_backend_service" "www-service" {
name = "${var.environment}-www-service"
protocol = "HTTP"
port_name = "http"
backend {
group = "${google_compute_instance_group.instance-group-0.self_link}"
}
backend {
group = "${google_compute_instance_group.instance-group-1.self_link}"
}
health_checks = ["${google_compute_health_check.health-check1.self_link}"]
}
Then I have two instance groups, each one with a one instance with that syntax :
resource "google_compute_instance_group" "instance-group-0" {
count = "${var.web_count}"
name = "${var.environment}-instance-group-0"
instances = ["${google_compute_instance.www.self_link}"]
named_port {
name = "http"
port = "80"
}
network = "${google_compute_network.platform-network.self_link}"
}
I get an error :
google_compute_backend_service.www-service: Resource 'google_compute_instance_group.instance-group-0' not found for variable 'google_compute_instance_group.instance-group-0.self_link'
I see that switching the backend/group declarations in the backend_service moves the error to group-1, so I can guess that this is not the proper syntax, although you can create a backend_service with multiple instance groups in the Google GUI.
I have two questions :
Q1. How can I create backend_service with multiple instance groups ?
what is the right syntax ?
Q2. Is it possible to reference a compute_instance in a compute_instance_group via a syntax like :
instances = ["${google_compute_instance.www.[count.index].self_link}"]
(the above syntax does not work)
Thanks for your answer
Finally I found the syntax across multiple Github tickets :
resource "google_compute_instance_group" "instance-group-0" {
name = "${var.environment}-instance-group-0"
zone = "${data.google_compute_zones.available.names[0]}"
instances = ["${slice(google_compute_instance.www.*.self_link, 0, floor(var.web_count/2)-1)}"]
named_port {
name = "http"
port = "80"
}
network = "${google_compute_network.platform-network.self_link}"
}
same for instance group 1, but different slice
Then :
resource "google_compute_instance" "www" {
count = "${var.web_count}"
zone = "${data.google_compute_zones.available.names[floor((2*count.index)/var.web_count)]"}
I must say that different design decisions can be taken :
- use a region managed instance group is simpler, except the template is static
- use Kubernetes
A1 - each backend service can only use a single instance group. You need to create a new backend for each group. You can then assign multiple backends to your load balancer.
On another note, the error message is with regards to the resource type you are using. Here is the resource page for unmanaged Instance groups, Managed instance groups and for backend services. You can find a list of the different compute APIs here.
A2- you should use $(ref.name.selfLink) instead, I believe this syntax should work and you can still use your variable. Also, make sure to update the API call syntax for adding instances to the group

Setting "count" based on the length of an attribute on another resource

I have a fairly simple Terraform configuration, which creates a Route53 zone and then creates NS records in Cloudflare to delegate the subdomain to that zone. At present, it assumes there's always exactly four authoritative DNS servers for every Route53 zone, and creates four separate cloudflare_record resources, but I'd like to generalise that, partially because who knows if AWS will start putting a fifth authoritative server out there in the future, but also as a "test case" for more complicated stuff in the future (like AWS AZs, which I know vary in count between regions).
What I've come up with so far is:
resource "cloudflare_record" "public-zone-ns" {
domain = "example.com"
name = "${terraform.env}"
type = "NS"
ttl = "120"
count = "${length(aws_route53_zone.public-zone.name_servers)}"
value = "${lookup(aws_route53_zone.public-zone.name_servers, count.index)}"
}
resource "aws_route53_zone" "public-zone" {
name = "${terraform.env}.example.com"
}
When I run terraform plan over this, though, I get this error:
Error running plan: 1 error(s) occurred:
* cloudflare_record.public-zone-ns: cloudflare_record.public-zone-ns: value of 'count' cannot be computed
I think what that means is that because the aws_route53_zone hasn't actually be created, terraform doesn't know what length(aws_route53_zone.public-zone.name_servers) is, and therefore the interpolation into cloudflare_record.public-zone-ns.count fails and I'm screwed.
However, it seems surprising to me that Terraform would be so inflexible; surely being able to create a variable number of resources like this would be meat-and-potatoes stuff. Hard-coding the length, or creating separate resources, just seems so... limiting.
So, what am I missing? How can I create a number of resources when I don't know in advance how many I need?
Currently count not being able to be calculated is an open issue in terraform https://github.com/hashicorp/terraform/issues/12570
You could move the name servers to a variable array and then get the length of that, all in one terraform script.
I got your point, this surprised me as well. Even I added depends_on to resource cloudflare_record, it is helpless.
What you can do to pass this issue is to split it into two stacks and make sure the route 53 record is created before cloudflare record.
Stack #1
resource "aws_route53_zone" "public-zone" {
name = "${terraform.env}.example.com"
}
output "name_servers" {
value = "${aws_route53_zone.public-zone.name_servers}"
}
Stack #2
data "terraform_remote_state" "route53" {
backend = "s3"
config {
bucket = "terraform-state-prod"
key = "network/terraform.tfstate"
region = "us-east-1"
}
}
resource "cloudflare_record" "public-zone-ns" {
domain = "example.com"
name = "${terraform.env}"
type = "NS"
ttl = "120"
count = "${length(data.terraform_remote_state.route53.name_servers)}"
value = "${element(data.terraform_remote_state.route53.name_servers, count.index)}"
}