Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions modules/aws/route53-dns-alias-record/backplane/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# AWS Route53 DNS Alias Record Backplane

This will deploy an IAM user (or role only in case of using `workload_identity_federation`) with Route53 access for managing DNS alias records.

## Usage

```hcl
provider "aws" {
region = "eu-central-1" # or any other region
}

module "aws_route53_dns_alias_record_backplane" {
source = "git::https://github.com/meshcloud/meshstack-hub.git//modules/aws/route53-dns-alias-record/backplane"

# List of Route53 hosted zone IDs that the building block can manage
hosted_zone_ids = [
"<hosted_zone_id_1>",
"<hosted_zone_id_2>"
]

workload_identity_federation = {
issuer = "https://your-oidc-issuer"
audience = "your-audience"
subjects = [
"system:serviceaccount:your-namespace:your-service-account-name", # Exact match
"system:serviceaccount:your-namespace:*", # Wildcard match
]
} # Optional, if not provided, IAM access keys will be created instead
}

output "aws_route53_dns_alias_record_backplane" {
value = module.aws_route53_dns_alias_record_backplane
}
```

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 6.32 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_iam_access_key.buildingblock_route53_alias_record_access_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_openid_connect_provider.buildingblock_oidc_provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_openid_connect_provider) | resource |
| [aws_iam_policy.buildingblock_route53_alias_record_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.assume_federated_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.buildingblock_route53_alias_record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_user.buildingblock_route53_alias_record_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [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 |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.route53_alias_record_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.workload_identity_federation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <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 |
| <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 |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_credentials"></a> [credentials](#output\_credentials) | n/a |
| <a name="output_workload_identity_federation_role"></a> [workload\_identity\_federation\_role](#output\_workload\_identity\_federation\_role) | n/a |
<!-- END_TF_DOCS -->
103 changes: 103 additions & 0 deletions modules/aws/route53-dns-alias-record/backplane/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
data "aws_caller_identity" "current" {}

resource "aws_iam_user" "buildingblock_route53_alias_record_user" {
count = var.workload_identity_federation == null ? 1 : 0

name = "buildingblock-route53-alias-record-user"
}

data "aws_iam_policy_document" "route53_alias_record_access" {
# Global Route53 actions that don't support resource-level permissions
statement {
effect = "Allow"
actions = [
"route53:GetChange",
"route53:ListHostedZones"
]
resources = ["*"]
}

# Zone-specific actions scoped to specific hosted zones
statement {
effect = "Allow"
actions = [
"route53:ListTagsForResource",
"route53:GetHostedZone",
"route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets"
]
resources = [
for zone_id in var.hosted_zone_ids : "arn:aws:route53:::hostedzone/${zone_id}"
]
}
}

resource "aws_iam_policy" "buildingblock_route53_alias_record_policy" {
name = var.workload_identity_federation == null ? "Route53AliasRecordBuildingBlockPolicy" : "Route53AliasRecordBuildingBlockFederatedPolicy"
description = "Policy for the Route53 DNS Alias Record Building Block"
policy = data.aws_iam_policy_document.route53_alias_record_access.json
}

resource "aws_iam_user_policy_attachment" "buildingblock_route53_alias_record_user_policy_attachment" {
count = var.workload_identity_federation == null ? 1 : 0

user = aws_iam_user.buildingblock_route53_alias_record_user[0].name
policy_arn = aws_iam_policy.buildingblock_route53_alias_record_policy.arn
}

resource "aws_iam_access_key" "buildingblock_route53_alias_record_access_key" {
count = var.workload_identity_federation == null ? 1 : 0

user = aws_iam_user.buildingblock_route53_alias_record_user[0].name
}

# Workload Identity Federation

resource "aws_iam_openid_connect_provider" "buildingblock_oidc_provider" {
count = var.workload_identity_federation != null ? 1 : 0

url = var.workload_identity_federation.issuer
client_id_list = [var.workload_identity_federation.audience]
}

data "aws_iam_policy_document" "workload_identity_federation" {
count = var.workload_identity_federation != null ? 1 : 0
version = "2012-10-17"

statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.buildingblock_oidc_provider[0].arn]
}
actions = ["sts:AssumeRoleWithWebIdentity"]

condition {
test = "StringEquals"
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:aud"

values = [var.workload_identity_federation.audience]
}

condition {
test = "StringLike"
variable = "${trimprefix(var.workload_identity_federation.issuer, "https://")}:sub"

values = var.workload_identity_federation.subjects
}
}
}

resource "aws_iam_role" "assume_federated_role" {
count = var.workload_identity_federation != null ? 1 : 0

name = "BuildingBlockRoute53AliasRecordIdentityFederation"
assume_role_policy = data.aws_iam_policy_document.workload_identity_federation[0].json
}

resource "aws_iam_role_policy_attachment" "buildingblock_route53_alias_record" {
count = var.workload_identity_federation != null ? 1 : 0

role = aws_iam_role.assume_federated_role[0].name
policy_arn = aws_iam_policy.buildingblock_route53_alias_record_policy.arn
}
11 changes: 11 additions & 0 deletions modules/aws/route53-dns-alias-record/backplane/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
output "credentials" {
sensitive = true
value = {
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"
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"
}
}

output "workload_identity_federation_role" {
value = var.workload_identity_federation == null ? null : aws_iam_role.assume_federated_role[0].arn
}
14 changes: 14 additions & 0 deletions modules/aws/route53-dns-alias-record/backplane/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
variable "hosted_zone_ids" {
type = list(string)
description = "List of Route53 hosted zone IDs that the building block can manage. Example: ['<hosted_zone_id_1>', '<hosted_zone_id_2>']"
}

variable "workload_identity_federation" {
type = object({
issuer = string,
audience = string,
subjects = list(string)
})
default = null
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:*')."
}
9 changes: 9 additions & 0 deletions modules/aws/route53-dns-alias-record/backplane/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.32"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# AWS Route53 DNS Alias Record

## Description
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.).

## When to Use
- Point custom domains to AWS load balancers (ALB/NLB)
- Route traffic to CloudFront distributions
- Create apex/root domain records (e.g., example.com)

## Shared Responsibility

| Responsibility | Platform Team | Application Team |
|----------------|---------------|------------------|
| Managing Route53 hosted zones | ✅ | ❌ |
| Provisioning DNS alias records | ❌ | ✅ |
| Managing record names and target resources | ❌ | ✅ |

## Key Recommendations
- Use descriptive DNS names (e.g., `api.example.com`, `www.example.com`)
- Enable health checks for automatic failover when appropriate
- Coordinate with your platform team before modifying production DNS records
75 changes: 75 additions & 0 deletions modules/aws/route53-dns-alias-record/buildingblock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
name: AWS Route53 DNS Alias Record
supportedPlatforms:
- aws
description: Provides AWS Route53 DNS alias records
---

# AWS Route53 DNS Alias Record

This Terraform module provisions AWS Route53 DNS alias records.

## Providers

```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.77.0"
}
}
}

provider "aws" {
region = var.region
allowed_account_ids = var.allowed_account_ids # Optional
}
```


## Backend configuration
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)

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 6.32 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_route53_record.record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws_route53_zone.zone](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <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 |
| <a name="input_alias_name"></a> [alias\_name](#input\_alias\_name) | Alias target DNS name. | `string` | n/a | yes |
| <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 |
| <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 |
| <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 |
| <a name="input_region"></a> [region](#input\_region) | The AWS region | `string` | `"eu-central-1"` | no |
| <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 |
| <a name="input_type"></a> [type](#input\_type) | n/a | `string` | n/a | yes |
| <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 |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_alias_target"></a> [alias\_target](#output\_alias\_target) | The alias target |
| <a name="output_record_name"></a> [record\_name](#output\_record\_name) | The FQDN of the DNS record |
| <a name="output_record_type"></a> [record\_type](#output\_record\_type) | The type of the DNS record |
| <a name="output_summary"></a> [summary](#output\_summary) | Summary of the created DNS alias record |
<!-- END_TF_DOCS -->
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions modules/aws/route53-dns-alias-record/buildingblock/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
data "aws_route53_zone" "zone" {
name = var.zone_name
private_zone = var.private_zone
}

locals {
# meshStack doesn't support empty strings as inputs right now, so we treat @ (which is common to denote apex records
# in zonefiles) as a special value to indicate "empty"
record_name = var.sub == "@" ? "" : var.sub
}

resource "aws_route53_record" "record" {
zone_id = data.aws_route53_zone.zone.zone_id
name = join(".", compact([local.record_name, data.aws_route53_zone.zone.name]))
type = var.type

alias {
name = var.alias_name
evaluate_target_health = var.alias_evaluate_target_health
zone_id = var.alias_zone_id
}
}
43 changes: 43 additions & 0 deletions modules/aws/route53-dns-alias-record/buildingblock/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
output "record_name" {
description = "The FQDN of the DNS record"
value = aws_route53_record.record.name
}

output "record_type" {
description = "The type of the DNS record"
value = aws_route53_record.record.type
}

output "alias_target" {
description = "The alias target"
value = var.alias_name
}

output "summary" {
description = "Summary of the created DNS alias record"
value = <<-EOT
# Route53 DNS Alias Record Created

✅ **Your DNS alias record is ready!**

## Record Details

| Property | Value |
|----------|-------|
| **DNS Name** | `${aws_route53_record.record.name}` |
| **Type** | `${var.type}` |
| **Alias Target** | `${var.alias_name}` |
| **Health Check** | ${var.alias_evaluate_target_health ? "✅ Enabled" : "⚠️ Disabled"} |
| **Zone** | `${var.zone_name}` |

---

## Resolution

```
${aws_route53_record.record.name} → ${var.alias_name}
```

${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."}
EOT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
provider "aws" {
region = var.region
allowed_account_ids = var.allowed_account_ids
}
Loading
Loading