From 50d21fea4badbadc0aeb68c1622f1917f105efed Mon Sep 17 00:00:00 2001 From: Mohammad Alhussan Date: Fri, 13 Feb 2026 16:45:32 +0100 Subject: [PATCH] feat: move route53 dns BBs to meshstack-hub --- .../backplane/README.md | 76 +++++++++++++ .../backplane/main.tf | 103 ++++++++++++++++++ .../backplane/outputs.tf | 11 ++ .../backplane/variables.tf | 14 +++ .../backplane/versions.tf | 9 ++ .../buildingblock/APP_TEAM_README.md | 22 ++++ .../buildingblock/README.md | 75 +++++++++++++ .../buildingblock/logo.png | Bin 0 -> 22509 bytes .../buildingblock/main.tf | 22 ++++ .../buildingblock/outputs.tf | 43 ++++++++ .../buildingblock/provider.tf | 4 + .../buildingblock/variables.tf | 64 +++++++++++ .../buildingblock/versions.tf | 9 ++ .../route53-dns-record/backplane/README.md | 76 +++++++++++++ .../aws/route53-dns-record/backplane/main.tf | 103 ++++++++++++++++++ .../route53-dns-record/backplane/outputs.tf | 11 ++ .../route53-dns-record/backplane/variables.tf | 14 +++ .../route53-dns-record/backplane/versions.tf | 9 ++ .../buildingblock/APP_TEAM_README.md | 23 ++++ .../buildingblock/README.md | 70 ++++++++++++ .../route53-dns-record/buildingblock/logo.png | Bin 0 -> 22509 bytes .../route53-dns-record/buildingblock/main.tf | 12 ++ .../buildingblock/outputs.tf | 45 ++++++++ .../buildingblock/provider.tf | 4 + .../buildingblock/variables.tf | 90 +++++++++++++++ .../buildingblock/versions.tf | 9 ++ 26 files changed, 918 insertions(+) create mode 100644 modules/aws/route53-dns-alias-record/backplane/README.md create mode 100644 modules/aws/route53-dns-alias-record/backplane/main.tf create mode 100644 modules/aws/route53-dns-alias-record/backplane/outputs.tf create mode 100644 modules/aws/route53-dns-alias-record/backplane/variables.tf create mode 100644 modules/aws/route53-dns-alias-record/backplane/versions.tf create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/APP_TEAM_README.md create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/README.md create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/logo.png create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/main.tf create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/outputs.tf create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/provider.tf create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/variables.tf create mode 100644 modules/aws/route53-dns-alias-record/buildingblock/versions.tf create mode 100644 modules/aws/route53-dns-record/backplane/README.md create mode 100644 modules/aws/route53-dns-record/backplane/main.tf create mode 100644 modules/aws/route53-dns-record/backplane/outputs.tf create mode 100644 modules/aws/route53-dns-record/backplane/variables.tf create mode 100644 modules/aws/route53-dns-record/backplane/versions.tf create mode 100644 modules/aws/route53-dns-record/buildingblock/APP_TEAM_README.md create mode 100644 modules/aws/route53-dns-record/buildingblock/README.md create mode 100644 modules/aws/route53-dns-record/buildingblock/logo.png create mode 100644 modules/aws/route53-dns-record/buildingblock/main.tf create mode 100644 modules/aws/route53-dns-record/buildingblock/outputs.tf create mode 100644 modules/aws/route53-dns-record/buildingblock/provider.tf create mode 100644 modules/aws/route53-dns-record/buildingblock/variables.tf create mode 100644 modules/aws/route53-dns-record/buildingblock/versions.tf diff --git a/modules/aws/route53-dns-alias-record/backplane/README.md b/modules/aws/route53-dns-alias-record/backplane/README.md new file mode 100644 index 0000000..88490b8 --- /dev/null +++ b/modules/aws/route53-dns-alias-record/backplane/README.md @@ -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 = [ + "", + "" + ] + + 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 +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [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 | +|------|-------------|------|---------|:--------:| +| [hosted\_zone\_ids](#input\_hosted\_zone\_ids) | List of Route53 hosted zone IDs that the building block can manage. Example: ['', ''] | `list(string)` | n/a | yes | +| [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:*'). |
object({
issuer = string,
audience = string,
subjects = list(string)
})
| `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [credentials](#output\_credentials) | n/a | +| [workload\_identity\_federation\_role](#output\_workload\_identity\_federation\_role) | n/a | + diff --git a/modules/aws/route53-dns-alias-record/backplane/main.tf b/modules/aws/route53-dns-alias-record/backplane/main.tf new file mode 100644 index 0000000..d4e90c1 --- /dev/null +++ b/modules/aws/route53-dns-alias-record/backplane/main.tf @@ -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 +} diff --git a/modules/aws/route53-dns-alias-record/backplane/outputs.tf b/modules/aws/route53-dns-alias-record/backplane/outputs.tf new file mode 100644 index 0000000..1f8b801 --- /dev/null +++ b/modules/aws/route53-dns-alias-record/backplane/outputs.tf @@ -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 +} diff --git a/modules/aws/route53-dns-alias-record/backplane/variables.tf b/modules/aws/route53-dns-alias-record/backplane/variables.tf new file mode 100644 index 0000000..f8c65b7 --- /dev/null +++ b/modules/aws/route53-dns-alias-record/backplane/variables.tf @@ -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: ['', '']" +} + +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:*')." +} diff --git a/modules/aws/route53-dns-alias-record/backplane/versions.tf b/modules/aws/route53-dns-alias-record/backplane/versions.tf new file mode 100644 index 0000000..d5d7768 --- /dev/null +++ b/modules/aws/route53-dns-alias-record/backplane/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.3.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.32" + } + } +} diff --git a/modules/aws/route53-dns-alias-record/buildingblock/APP_TEAM_README.md b/modules/aws/route53-dns-alias-record/buildingblock/APP_TEAM_README.md new file mode 100644 index 0000000..702df7b --- /dev/null +++ b/modules/aws/route53-dns-alias-record/buildingblock/APP_TEAM_README.md @@ -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 diff --git a/modules/aws/route53-dns-alias-record/buildingblock/README.md b/modules/aws/route53-dns-alias-record/buildingblock/README.md new file mode 100644 index 0000000..cb03e4f --- /dev/null +++ b/modules/aws/route53-dns-alias-record/buildingblock/README.md @@ -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) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [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 | +|------|-------------|------|---------|:--------:| +| [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 | +| [alias\_name](#input\_alias\_name) | Alias target DNS name. | `string` | n/a | yes | +| [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 | +| [allowed\_account\_ids](#input\_allowed\_account\_ids) | List of allowed AWS account IDs to prevent operations on the wrong account | `list(string)` | `null` | no | +| [private\_zone](#input\_private\_zone) | Set to true if the AWS Route 53 zone is a Private Hosted Zone. | `bool` | `false` | no | +| [region](#input\_region) | The AWS region | `string` | `"eu-central-1"` | no | +| [sub](#input\_sub) | DNS record name, excluding the `zone_name`. Use the value '@' to create an apex record. | `string` | n/a | yes | +| [type](#input\_type) | n/a | `string` | n/a | yes | +| [zone\_name](#input\_zone\_name) | AWS Route53 zone name in which the record should be created. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [alias\_target](#output\_alias\_target) | The alias target | +| [record\_name](#output\_record\_name) | The FQDN of the DNS record | +| [record\_type](#output\_record\_type) | The type of the DNS record | +| [summary](#output\_summary) | Summary of the created DNS alias record | + \ No newline at end of file diff --git a/modules/aws/route53-dns-alias-record/buildingblock/logo.png b/modules/aws/route53-dns-alias-record/buildingblock/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ea8929346c11421e4a35cbdb1e6b15102e2db6 GIT binary patch literal 22509 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 g@1Xv_BkJ<0KkK+(R@=G5Kh&;", + "" + ] + + 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_record_backplane" { + value = module.aws_route53_dns_record_backplane +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | ~> 6.32 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_access_key.buildingblock_route53_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_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_record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_user.buildingblock_route53_record_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource | +| [aws_iam_user_policy_attachment.buildingblock_route53_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_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 | +|------|-------------|------|---------|:--------:| +| [hosted\_zone\_ids](#input\_hosted\_zone\_ids) | List of Route53 hosted zone IDs that the building block can manage. Example: '', ''] | `list(string)` | n/a | yes | +| [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:*'). |
object({
issuer = string,
audience = string,
subjects = list(string)
})
| `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [credentials](#output\_credentials) | n/a | +| [workload\_identity\_federation\_role](#output\_workload\_identity\_federation\_role) | n/a | + diff --git a/modules/aws/route53-dns-record/backplane/main.tf b/modules/aws/route53-dns-record/backplane/main.tf new file mode 100644 index 0000000..1a9bf3e --- /dev/null +++ b/modules/aws/route53-dns-record/backplane/main.tf @@ -0,0 +1,103 @@ +data "aws_caller_identity" "current" {} + +resource "aws_iam_user" "buildingblock_route53_record_user" { + count = var.workload_identity_federation == null ? 1 : 0 + + name = "buildingblock-route53-record-user" +} + +data "aws_iam_policy_document" "route53_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_record_policy" { + name = var.workload_identity_federation == null ? "Route53RecordBuildingBlockPolicy" : "Route53RecordBuildingBlockFederatedPolicy" + description = "Policy for the Route53 DNS Record Building Block" + policy = data.aws_iam_policy_document.route53_record_access.json +} + +resource "aws_iam_user_policy_attachment" "buildingblock_route53_record_user_policy_attachment" { + count = var.workload_identity_federation == null ? 1 : 0 + + user = aws_iam_user.buildingblock_route53_record_user[0].name + policy_arn = aws_iam_policy.buildingblock_route53_record_policy.arn +} + +resource "aws_iam_access_key" "buildingblock_route53_record_access_key" { + count = var.workload_identity_federation == null ? 1 : 0 + + user = aws_iam_user.buildingblock_route53_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 = "BuildingBlockRoute53RecordIdentityFederation" + assume_role_policy = data.aws_iam_policy_document.workload_identity_federation[0].json +} + +resource "aws_iam_role_policy_attachment" "buildingblock_route53_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_record_policy.arn +} diff --git a/modules/aws/route53-dns-record/backplane/outputs.tf b/modules/aws/route53-dns-record/backplane/outputs.tf new file mode 100644 index 0000000..25bbc5e --- /dev/null +++ b/modules/aws/route53-dns-record/backplane/outputs.tf @@ -0,0 +1,11 @@ +output "credentials" { + sensitive = true + value = { + AWS_ACCESS_KEY_ID = var.workload_identity_federation == null ? aws_iam_access_key.buildingblock_route53_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_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 +} diff --git a/modules/aws/route53-dns-record/backplane/variables.tf b/modules/aws/route53-dns-record/backplane/variables.tf new file mode 100644 index 0000000..066ca7d --- /dev/null +++ b/modules/aws/route53-dns-record/backplane/variables.tf @@ -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: '', '']" +} + +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:*')." +} diff --git a/modules/aws/route53-dns-record/backplane/versions.tf b/modules/aws/route53-dns-record/backplane/versions.tf new file mode 100644 index 0000000..d4521bd --- /dev/null +++ b/modules/aws/route53-dns-record/backplane/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.32" + } + } +} diff --git a/modules/aws/route53-dns-record/buildingblock/APP_TEAM_README.md b/modules/aws/route53-dns-record/buildingblock/APP_TEAM_README.md new file mode 100644 index 0000000..7f49b74 --- /dev/null +++ b/modules/aws/route53-dns-record/buildingblock/APP_TEAM_README.md @@ -0,0 +1,23 @@ +# AWS Route53 DNS Record + +## Description +This building block creates standard DNS records for mapping domain names to IP addresses or other values. + +## When to Use +- Create DNS records (A, AAAA, CNAME, TXT, MX, SRV, etc.) +- Point subdomain names to IP addresses or other domains +- Configure email routing, domain verification, or service discovery + +## Shared Responsibility + +| Responsibility | Platform Team | Application Team | +|----------------|---------------|------------------| +| Managing Route53 hosted zones | ✅ | ❌ | +| Provisioning DNS records | ❌ | ✅ | +| Managing record values and TTL | ❌ | ✅ | + +## Key Recommendations +- Choose appropriate TTL: Lower (e.g., 300s) for frequent changes, higher (e.g., 3600s) for stable records +- Use descriptive DNS names that clearly indicate the service +- Test DNS changes in development environments first +- Remember: DNS changes take time to propagate (up to TTL duration) diff --git a/modules/aws/route53-dns-record/buildingblock/README.md b/modules/aws/route53-dns-record/buildingblock/README.md new file mode 100644 index 0000000..a78c71a --- /dev/null +++ b/modules/aws/route53-dns-record/buildingblock/README.md @@ -0,0 +1,70 @@ +--- +name: AWS Route53 DNS Record +supportedPlatforms: + - aws +description: Provides AWS Route53 DNS records for mapping domain names to IP addresses or other values. +--- + +# AWS Route53 DNS Record + +This Terraform module provisions AWS Route53 DNS 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 +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [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 | +|------|-------------|------|---------|:--------:| +| [allowed\_account\_ids](#input\_allowed\_account\_ids) | List of allowed AWS account IDs to prevent operations on the wrong account | `list(string)` | `null` | no | +| [private\_zone](#input\_private\_zone) | Set to true if the AWS Route 53 zone is a Private Hosted Zone. | `bool` | `false` | no | +| [record](#input\_record) | DNS record value | `string` | n/a | yes | +| [region](#input\_region) | The AWS region | `string` | `"eu-central-1"` | no | +| [sub](#input\_sub) | DNS record name, excluding the `zone_name`. Leave empty to create apex records. | `string` | n/a | yes | +| [ttl](#input\_ttl) | TTL of the record in seconds. | `string` | `"300"` | no | +| [type](#input\_type) | DNS Record type | `string` | n/a | yes | +| [zone\_name](#input\_zone\_name) | AWS Route53 zone name in which the record should be created. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [record\_name](#output\_record\_name) | The FQDN of the DNS record | +| [record\_type](#output\_record\_type) | The type of the DNS record | +| [record\_value](#output\_record\_value) | The value of the DNS record | +| [summary](#output\_summary) | Summary of the created DNS record | + diff --git a/modules/aws/route53-dns-record/buildingblock/logo.png b/modules/aws/route53-dns-record/buildingblock/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ea8929346c11421e4a35cbdb1e6b15102e2db6 GIT binary patch literal 22509 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 g@1Xv_BkJ<0KkK+(R@=G5Kh&; 0 + error_message = "The ttl value must be larger than 0 but was ${var.ttl}." + } +} diff --git a/modules/aws/route53-dns-record/buildingblock/versions.tf b/modules/aws/route53-dns-record/buildingblock/versions.tf new file mode 100644 index 0000000..d4521bd --- /dev/null +++ b/modules/aws/route53-dns-record/buildingblock/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.32" + } + } +}