Skip to content

Commit 5d9237b

Browse files
committed
feat: move route53 dns BBs to meshstack-hub
1 parent aba1d60 commit 5d9237b

26 files changed

Lines changed: 918 additions & 0 deletions
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# AWS Route53 DNS Alias Record Backplane
2+
3+
This will deploy an IAM user (or role only in case of using `workload_identity_federation`) with Route53 access for managing DNS alias records.
4+
5+
## Usage
6+
7+
```hcl
8+
provider "aws" {
9+
region = "eu-central-1" # or any other region
10+
}
11+
12+
module "aws_route53_dns_alias_record_backplane" {
13+
source = "git::https://github.com/meshcloud/meshstack-hub.git//modules/aws/route53-dns-alias-record/backplane"
14+
15+
# List of Route53 hosted zone IDs that the building block can manage
16+
hosted_zone_ids = [
17+
"<hosted_zone_id_1>",
18+
"<hosted_zone_id_2>"
19+
]
20+
21+
workload_identity_federation = {
22+
issuer = "https://your-oidc-issuer"
23+
audience = "your-audience"
24+
subjects = [
25+
"system:serviceaccount:your-namespace:your-service-account-name", # Exact match
26+
"system:serviceaccount:your-namespace:*", # Wildcard match
27+
]
28+
} # Optional, if not provided, IAM access keys will be created instead
29+
}
30+
31+
output "aws_route53_dns_alias_record_backplane" {
32+
value = module.aws_route53_dns_alias_record_backplane
33+
}
34+
```
35+
36+
<!-- BEGIN_TF_DOCS -->
37+
## Requirements
38+
39+
| Name | Version |
40+
|------|---------|
41+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3.0 |
42+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 5.0 |
43+
44+
## Modules
45+
46+
No modules.
47+
48+
## Resources
49+
50+
| Name | Type |
51+
|------|------|
52+
| [aws_iam_access_key.buildingblock_route53_alias_record_access_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
53+
| [aws_iam_openid_connect_provider.buildingblock_oidc_provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource |
54+
| [aws_iam_policy.buildingblock_route53_alias_record_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
55+
| [aws_iam_role.assume_federated_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
56+
| [aws_iam_role_policy_attachment.buildingblock_route53_alias_record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
57+
| [aws_iam_user.buildingblock_route53_alias_record_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
58+
| [aws_iam_user_policy_attachment.buildingblock_route53_alias_record_user_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
59+
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
60+
| [aws_iam_policy_document.route53_alias_record_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
61+
| [aws_iam_policy_document.workload_identity_federation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
62+
63+
## Inputs
64+
65+
| Name | Description | Type | Default | Required |
66+
|------|-------------|------|---------|:--------:|
67+
| <a name="input_hosted_zone_ids"></a> [hosted\_zone\_ids](#input\_hosted\_zone\_ids) | List of Route53 hosted zone IDs that the building block can manage. Example: ['<hosted\_zone\_id\_1>', '<hosted\_zone\_id\_2>'] | `list(string)` | n/a | yes |
68+
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. Supports multiple subjects and wildcard patterns (e.g., 'system:serviceaccount:namespace:*'). | <pre>object({<br> issuer = string,<br> audience = string,<br> subjects = list(string)<br> })</pre> | `null` | no |
69+
70+
## Outputs
71+
72+
| Name | Description |
73+
|------|-------------|
74+
| <a name="output_credentials"></a> [credentials](#output\_credentials) | n/a |
75+
| <a name="output_workload_identity_federation_role"></a> [workload\_identity\_federation\_role](#output\_workload\_identity\_federation\_role) | n/a |
76+
<!-- END_TF_DOCS -->
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
data "aws_caller_identity" "current" {}
2+
3+
resource "aws_iam_user" "buildingblock_route53_alias_record_user" {
4+
count = var.workload_identity_federation == null ? 1 : 0
5+
6+
name = "buildingblock-route53-alias-record-user"
7+
}
8+
9+
data "aws_iam_policy_document" "route53_alias_record_access" {
10+
# Global Route53 actions that don't support resource-level permissions
11+
statement {
12+
effect = "Allow"
13+
actions = [
14+
"route53:GetChange",
15+
"route53:ListHostedZones"
16+
]
17+
resources = ["*"]
18+
}
19+
20+
# Zone-specific actions scoped to specific hosted zones
21+
statement {
22+
effect = "Allow"
23+
actions = [
24+
"route53:ListTagsForResource",
25+
"route53:GetHostedZone",
26+
"route53:ChangeResourceRecordSets",
27+
"route53:ListResourceRecordSets"
28+
]
29+
resources = [
30+
for zone_id in var.hosted_zone_ids : "arn:aws:route53:::hostedzone/${zone_id}"
31+
]
32+
}
33+
}
34+
35+
resource "aws_iam_policy" "buildingblock_route53_alias_record_policy" {
36+
name = var.workload_identity_federation == null ? "Route53AliasRecordBuildingBlockPolicy" : "Route53AliasRecordBuildingBlockFederatedPolicy"
37+
description = "Policy for the Route53 DNS Alias Record Building Block"
38+
policy = data.aws_iam_policy_document.route53_alias_record_access.json
39+
}
40+
41+
resource "aws_iam_user_policy_attachment" "buildingblock_route53_alias_record_user_policy_attachment" {
42+
count = var.workload_identity_federation == null ? 1 : 0
43+
44+
user = aws_iam_user.buildingblock_route53_alias_record_user[0].name
45+
policy_arn = aws_iam_policy.buildingblock_route53_alias_record_policy.arn
46+
}
47+
48+
resource "aws_iam_access_key" "buildingblock_route53_alias_record_access_key" {
49+
count = var.workload_identity_federation == null ? 1 : 0
50+
51+
user = aws_iam_user.buildingblock_route53_alias_record_user[0].name
52+
}
53+
54+
# Workload Identity Federation
55+
56+
resource "aws_iam_openid_connect_provider" "buildingblock_oidc_provider" {
57+
count = var.workload_identity_federation != null ? 1 : 0
58+
59+
url = var.workload_identity_federation.issuer
60+
client_id_list = [var.workload_identity_federation.audience]
61+
}
62+
63+
data "aws_iam_policy_document" "workload_identity_federation" {
64+
count = var.workload_identity_federation != null ? 1 : 0
65+
version = "2012-10-17"
66+
67+
statement {
68+
effect = "Allow"
69+
principals {
70+
type = "Federated"
71+
identifiers = [aws_iam_openid_connect_provider.buildingblock_oidc_provider[0].arn]
72+
}
73+
actions = ["sts:AssumeRoleWithWebIdentity"]
74+
75+
condition {
76+
test = "StringEquals"
77+
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:aud"
78+
79+
values = [var.workload_identity_federation.audience]
80+
}
81+
82+
condition {
83+
test = "StringLike"
84+
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:sub"
85+
86+
values = var.workload_identity_federation.subjects
87+
}
88+
}
89+
}
90+
91+
resource "aws_iam_role" "assume_federated_role" {
92+
count = var.workload_identity_federation != null ? 1 : 0
93+
94+
name = "BuildingBlockRoute53AliasRecordIdentityFederation"
95+
assume_role_policy = data.aws_iam_policy_document.workload_identity_federation[0].json
96+
}
97+
98+
resource "aws_iam_role_policy_attachment" "buildingblock_route53_alias_record" {
99+
count = var.workload_identity_federation != null ? 1 : 0
100+
101+
role = aws_iam_role.assume_federated_role[0].name
102+
policy_arn = aws_iam_policy.buildingblock_route53_alias_record_policy.arn
103+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
output "credentials" {
2+
sensitive = true
3+
value = {
4+
AWS_ACCESS_KEY_ID = var.workload_identity_federation == null ? aws_iam_access_key.buildingblock_route53_alias_record_access_key[0].id : "N/A; workload identity federation in use"
5+
AWS_SECRET_ACCESS_KEY = var.workload_identity_federation == null ? aws_iam_access_key.buildingblock_route53_alias_record_access_key[0].secret : "N/A; workload identity federation in use"
6+
}
7+
}
8+
9+
output "workload_identity_federation_role" {
10+
value = var.workload_identity_federation == null ? null : aws_iam_role.assume_federated_role[0].arn
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
variable "hosted_zone_ids" {
2+
type = list(string)
3+
description = "List of Route53 hosted zone IDs that the building block can manage. Example: ['<hosted_zone_id_1>', '<hosted_zone_id_2>']"
4+
}
5+
6+
variable "workload_identity_federation" {
7+
type = object({
8+
issuer = string,
9+
audience = string,
10+
subjects = list(string)
11+
})
12+
default = null
13+
description = "Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. Supports multiple subjects and wildcard patterns (e.g., 'system:serviceaccount:namespace:*')."
14+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
terraform {
2+
required_version = ">= 1.3.0"
3+
required_providers {
4+
aws = {
5+
source = "hashicorp/aws"
6+
version = "~> 5.0"
7+
}
8+
}
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# AWS Route53 DNS Alias Record
2+
3+
## Description
4+
This building block creates Route53 alias records, which are AWS-specific DNS records that can only route traffic to AWS resources (load balancers, CloudFront distributions, S3 websites, etc.).
5+
6+
## When to Use
7+
- Point custom domains to AWS load balancers (ALB/NLB)
8+
- Route traffic to CloudFront distributions
9+
- Create apex/root domain records (e.g., example.com)
10+
11+
## Shared Responsibility
12+
13+
| Responsibility | Platform Team | Application Team |
14+
|----------------|---------------|------------------|
15+
| Managing Route53 hosted zones |||
16+
| Provisioning DNS alias records |||
17+
| Managing record names and target resources |||
18+
19+
## Key Recommendations
20+
- Use descriptive DNS names (e.g., `api.example.com`, `www.example.com`)
21+
- Enable health checks for automatic failover when appropriate
22+
- Coordinate with your platform team before modifying production DNS records
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
name: AWS Route53 DNS Alias Record
3+
supportedPlatforms:
4+
- aws
5+
description: Provides AWS Route53 DNS alias records
6+
---
7+
8+
# AWS Route53 DNS Alias Record
9+
10+
This Terraform module provisions AWS Route53 DNS alias records.
11+
12+
## Providers
13+
14+
```hcl
15+
terraform {
16+
required_providers {
17+
aws = {
18+
source = "hashicorp/aws"
19+
version = "~> 5.77.0"
20+
}
21+
}
22+
}
23+
24+
provider "aws" {
25+
region = var.region
26+
allowed_account_ids = var.allowed_account_ids # Optional
27+
}
28+
```
29+
30+
31+
## Backend configuration
32+
Here you can find an example of how to create a backend.tf file on this [Wiki Page](https://github.com/meshcloud/building-blocks/wiki/%5BUser-Guide%5D-Setting-up-the-Backend-for-terraform-state#how-to-configure-backendtf-file-for-these-providers)
33+
34+
<!-- BEGIN_TF_DOCS -->
35+
## Requirements
36+
37+
| Name | Version |
38+
|------|---------|
39+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
40+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 5.77.0 |
41+
42+
## Modules
43+
44+
No modules.
45+
46+
## Resources
47+
48+
| Name | Type |
49+
|------|------|
50+
| [aws_route53_record.record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
51+
| [aws_route53_zone.zone](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |
52+
53+
## Inputs
54+
55+
| Name | Description | Type | Default | Required |
56+
|------|-------------|------|---------|:--------:|
57+
| <a name="input_alias_evaluate_target_health"></a> [alias\_evaluate\_target\_health](#input\_alias\_evaluate\_target\_health) | When set to true, an alias resource record set inherits the health of the referenced AWS resource, such as an ELB load balancer or another resource record set in the hosted zone. | `bool` | `false` | no |
58+
| <a name="input_alias_name"></a> [alias\_name](#input\_alias\_name) | Alias target DNS name. | `string` | n/a | yes |
59+
| <a name="input_alias_zone_id"></a> [alias\_zone\_id](#input\_alias\_zone\_id) | AWS Route53 hosted zone id for the alias target. Note: These can be magic constants, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html | `string` | n/a | yes |
60+
| <a name="input_allowed_account_ids"></a> [allowed\_account\_ids](#input\_allowed\_account\_ids) | List of allowed AWS account IDs to prevent operations on the wrong account | `list(string)` | `null` | no |
61+
| <a name="input_private_zone"></a> [private\_zone](#input\_private\_zone) | Set to true if the AWS Route 53 zone is a Private Hosted Zone. | `bool` | `false` | no |
62+
| <a name="input_region"></a> [region](#input\_region) | The AWS region | `string` | `"eu-central-1"` | no |
63+
| <a name="input_sub"></a> [sub](#input\_sub) | DNS record name, excluding the `zone_name`. Use the value '@' to create an apex record. | `string` | n/a | yes |
64+
| <a name="input_type"></a> [type](#input\_type) | n/a | `string` | n/a | yes |
65+
| <a name="input_zone_name"></a> [zone\_name](#input\_zone\_name) | AWS Route53 zone name in which the record should be created. | `string` | n/a | yes |
66+
67+
## Outputs
68+
69+
| Name | Description |
70+
|------|-------------|
71+
| <a name="output_alias_target"></a> [alias\_target](#output\_alias\_target) | The alias target |
72+
| <a name="output_record_name"></a> [record\_name](#output\_record\_name) | The FQDN of the DNS record |
73+
| <a name="output_record_type"></a> [record\_type](#output\_record\_type) | The type of the DNS record |
74+
| <a name="output_summary"></a> [summary](#output\_summary) | Summary of the created DNS alias record |
75+
<!-- END_TF_DOCS -->
22 KB
Loading
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
data "aws_route53_zone" "zone" {
2+
name = var.zone_name
3+
private_zone = var.private_zone
4+
}
5+
6+
locals {
7+
# meshStack doesn't support empty strings as inputs right now, so we treat @ (which is common to denote apex records
8+
# in zonefiles) as a special value to indicate "empty"
9+
record_name = var.sub == "@" ? "" : var.sub
10+
}
11+
12+
resource "aws_route53_record" "record" {
13+
zone_id = data.aws_route53_zone.zone.zone_id
14+
name = join(".", compact([local.record_name, data.aws_route53_zone.zone.name]))
15+
type = var.type
16+
17+
alias {
18+
name = var.alias_name
19+
evaluate_target_health = var.alias_evaluate_target_health
20+
zone_id = var.alias_zone_id
21+
}
22+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
output "record_name" {
2+
description = "The FQDN of the DNS record"
3+
value = aws_route53_record.record.name
4+
}
5+
6+
output "record_type" {
7+
description = "The type of the DNS record"
8+
value = aws_route53_record.record.type
9+
}
10+
11+
output "alias_target" {
12+
description = "The alias target"
13+
value = var.alias_name
14+
}
15+
16+
output "summary" {
17+
description = "Summary of the created DNS alias record"
18+
value = <<-EOT
19+
# Route53 DNS Alias Record Created
20+
21+
✅ **Your DNS alias record is ready!**
22+
23+
## Record Details
24+
25+
| Property | Value |
26+
|----------|-------|
27+
| **DNS Name** | `${aws_route53_record.record.name}` |
28+
| **Type** | `${var.type}` |
29+
| **Alias Target** | `${var.alias_name}` |
30+
| **Health Check** | ${var.alias_evaluate_target_health ? "✅ Enabled" : "⚠️ Disabled"} |
31+
| **Zone** | `${var.zone_name}` |
32+
33+
---
34+
35+
## Resolution
36+
37+
```
38+
${aws_route53_record.record.name}${var.alias_name}
39+
```
40+
41+
${var.private_zone ? "⚠️ **Note:** This is a private hosted zone record, only resolvable within your VPC." : "🌐 **Note:** This is a public DNS record, resolvable globally."}
42+
EOT
43+
}

0 commit comments

Comments
 (0)