From eae2e2e98c8c0201254c3cab37603ec2bf9f1104 Mon Sep 17 00:00:00 2001 From: Byungjin Park Date: Fri, 16 May 2025 23:46:58 +0900 Subject: [PATCH] feat(ses-identity): add `ses-identity` module to manage SES identities --- .github/labeler.yaml | 5 + .github/labels.yaml | 3 + .tflint.hcl | 24 +++- README.md | 7 +- modules/ses-identity/README.md | 59 ++++++++++ modules/ses-identity/main.tf | 60 ++++++++++ modules/ses-identity/outputs.tf | 65 +++++++++++ modules/ses-identity/resource-group.tf | 31 +++++ modules/ses-identity/route53.tf | 18 +++ modules/ses-identity/variables.tf | 151 +++++++++++++++++++++++++ modules/ses-identity/versions.tf | 10 ++ 11 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 modules/ses-identity/README.md create mode 100644 modules/ses-identity/main.tf create mode 100644 modules/ses-identity/outputs.tf create mode 100644 modules/ses-identity/resource-group.tf create mode 100644 modules/ses-identity/route53.tf create mode 100644 modules/ses-identity/variables.tf create mode 100644 modules/ses-identity/versions.tf diff --git a/.github/labeler.yaml b/.github/labeler.yaml index f7cd8df..85d5285 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -14,6 +14,11 @@ - any-glob-to-any-file: - modules/msk-cluster/**/* +":floppy_disk: ses-identity": +- changed-files: + - any-glob-to-any-file: + - modules/ses-identity/**/* + ":floppy_disk: sns-fifo-topic": - changed-files: - any-glob-to-any-file: diff --git a/.github/labels.yaml b/.github/labels.yaml index f3a5bc2..0dc119f 100644 --- a/.github/labels.yaml +++ b/.github/labels.yaml @@ -49,6 +49,9 @@ - color: "fbca04" description: "This issue or pull request is related to msk-cluster module." name: ":floppy_disk: msk-cluster" +- color: "fbca04" + description: "This issue or pull request is related to ses-identity module." + name: ":floppy_disk: ses-identity" - color: "fbca04" description: "This issue or pull request is related to sns-fifo-topic module." name: ":floppy_disk: sns-fifo-topic" diff --git a/.tflint.hcl b/.tflint.hcl index 1432c6f..355742b 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -70,8 +70,30 @@ rule "terraform_unused_required_providers" { plugin "aws" { source = "github.com/terraform-linters/tflint-ruleset-aws" - version = "0.38.0" + version = "0.39.0" enabled = true deep_check = false } + +# INFO: Wrong validation +rule "aws_route53_record_invalid_health_check_id" { + enabled = false +} +# INFO: Wrong validation +rule "aws_route53_record_invalid_name" { + enabled = false +} +# INFO: Wrong validation +rule "aws_route53_record_invalid_set_identifier" { + enabled = false +} +# INFO: Wrong validation +rule "aws_route53_record_invalid_type" { + enabled = false +} +# INFO: Wrong validation +rule "aws_route53_record_invalid_zone_id" { + enabled = false +} + diff --git a/README.md b/README.md index f7a5048..d4d8a4a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Terraform module which creates messaging related resources on AWS. - [eventbridge-event-bus](./modules/eventbridge-event-bus) - [eventbridge-rule](./modules/eventbridge-rule) - [msk-cluster](./modules/msk-cluster) +- [ses-identity](./modules/ses-identity) - [sns-fifo-topic](./modules/sns-fifo-topic) - [sns-standard-topic](./modules/sns-standard-topic) @@ -22,6 +23,10 @@ Terraform Modules from [this package](https://github.com/tedilabs/terraform-aws- - Rule - **AWS MSK (Managed Streaming for Apache Kafka)** - Cluster +- **AWS SES (Simple Email Service)** + - Identity + - Domain Identity + - Email Identity - **AWS SNS (Simple Notification Service)** - FIFO Topic - Standard Topic @@ -46,4 +51,4 @@ Like this project? Follow the repository on [GitHub](https://github.com/tedilabs Provided under the terms of the [Apache License](LICENSE). -Copyright © 2023, [Byungjin Park](https://www.posquit0.com). +Copyright © 2023-2025, [Byungjin Park](https://www.posquit0.com). diff --git a/modules/ses-identity/README.md b/modules/ses-identity/README.md new file mode 100644 index 0000000..11a8e6e --- /dev/null +++ b/modules/ses-identity/README.md @@ -0,0 +1,59 @@ +# ses-identity + +This module creates following resources. + +- `aws_sesv2_email_identity` +- `aws_route53_record` (optional) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.10 | +| [aws](#requirement\_aws) | >= 5.84 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.98.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [resource\_group](#module\_resource\_group) | tedilabs/misc/aws//modules/resource-group | ~> 0.10.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_route53_record.dkim](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_sesv2_email_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sesv2_email_identity) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [name](#input\_name) | (Required) The domain name of the SES domain identity. | `string` | n/a | yes | +| [configuration\_set](#input\_configuration\_set) | (Optional) The configuration set to use by default when sending from this identity. Note that any configuration set defined in the email sending request takes precedence. | `string` | `null` | no | +| [dkim](#input\_dkim) | (Optional) The configuration for the DKIM (DomainKeys Identified Mail). `dkim` as defined below.
(Optional) `type` - Whether to use either Easy DKIM (`EASY_DKIM`) or Bring Your Own DKIM (`BYODKIM`), and depending on your choice, you'll have to configure the signing key length of the private key. Valid values are `EASY_DKIM` and `BYODKIM`. Defaults to `EASY_DKIM`.
(Optional) `signing_key_type` - The key type of the future DKIM key pair to be generated. This can be changed at most once per day. The signing key length of the private key. Valid values are `RSA_1024` and `RSA_2048`. Defaults to `RSA_2048`. Only required if `type` is `EASY_DKIM`.
(Optional) `private_key` - A private key that's used to generate a DKIM signature. The private key must use 1024 or 2048-bit RSA encryption, and must be encoded using base64 encoding. Only required if `type` is `BYODKIM`.
(Optional) `selector_name` - A string that's used to identify a public key in the DNS configuration for a domain. Only required if `type` is `BYODKIM`.
(Optional) `verification` - A configuration for the DKIM verification. `verification` as defined below.
(Optional) `enabled` - Whether to process DKIM verification by creating the necessary domain records in the module. Defaults to `false`.
(Optional) `zone_id` - The ID of Hosted Zone to automatically manage the records for DKIM verification. |
object({
type = optional(string, "EASY_DKIM")
signing_key_type = optional(string, "RSA_2048")
private_key = optional(string)
selector_name = optional(string)
verification = optional(object({
enabled = optional(bool, false)
zone_id = optional(string)
}))
})
| `{}` | no | +| [module\_tags\_enabled](#input\_module\_tags\_enabled) | (Optional) Whether to create AWS Resource Tags for the module informations. | `bool` | `true` | no | +| [resource\_group\_description](#input\_resource\_group\_description) | (Optional) The description of Resource Group. | `string` | `"Managed by Terraform."` | no | +| [resource\_group\_enabled](#input\_resource\_group\_enabled) | (Optional) Whether to create Resource Group to find and group AWS resources which are created by this module. | `bool` | `true` | no | +| [resource\_group\_name](#input\_resource\_group\_name) | (Optional) The name of Resource Group. A Resource Group name can have a maximum of 127 characters, including letters, numbers, hyphens, dots, and underscores. The name cannot start with `AWS` or `aws`. | `string` | `""` | no | +| [tags](#input\_tags) | (Optional) A map of tags to add to all resources. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the SES identity. | +| [configuration\_set](#output\_configuration\_set) | The configuration set to use by default when sending from this identity. | +| [dkim](#output\_dkim) | The configuration for the DKIM. | +| [id](#output\_id) | The ID of the SES identity. | +| [name](#output\_name) | The domain name for the SES identity. | +| [status](#output\_status) | The status of the SES identity. | +| [type](#output\_type) | The type of the SES identity. | + diff --git a/modules/ses-identity/main.tf b/modules/ses-identity/main.tf new file mode 100644 index 0000000..f719925 --- /dev/null +++ b/modules/ses-identity/main.tf @@ -0,0 +1,60 @@ +locals { + metadata = { + package = "terraform-aws-messaging" + version = trimspace(file("${path.module}/../../VERSION")) + module = basename(path.module) + name = var.name + } + module_tags = var.module_tags_enabled ? { + "module.terraform.io/package" = local.metadata.package + "module.terraform.io/version" = local.metadata.version + "module.terraform.io/name" = local.metadata.module + "module.terraform.io/full-name" = "${local.metadata.package}/${local.metadata.module}" + "module.terraform.io/instance" = local.metadata.name + } : {} +} + +locals { + signing_key_type = { + "RSA_1024" = "RSA_1024_BIT" + "RSA_2048" = "RSA_2048_BIT" + } + signing_key_type_reverse = { + for k, v in local.signing_key_type : v => k + } +} + +################################################### +# SES Domain Identity +################################################### + +resource "aws_sesv2_email_identity" "this" { + email_identity = var.name + configuration_set_name = var.configuration_set + + dkim_signing_attributes { + # Easy DKIM + next_signing_key_length = (var.dkim.type == "EASY_DKIM" + ? local.signing_key_type[var.dkim.signing_key_type] + : null + ) + + # BYODKIM + domain_signing_private_key = (var.dkim.type == "BYODKIM" + ? var.dkim.private_key + : null + ) + domain_signing_selector = (var.dkim.type == "BYODKIM" + ? var.dkim.selector_name + : null + ) + } + + tags = merge( + { + "Name" = local.metadata.name + }, + local.module_tags, + var.tags, + ) +} \ No newline at end of file diff --git a/modules/ses-identity/outputs.tf b/modules/ses-identity/outputs.tf new file mode 100644 index 0000000..21f2b83 --- /dev/null +++ b/modules/ses-identity/outputs.tf @@ -0,0 +1,65 @@ +output "arn" { + description = "The ARN of the SES identity." + value = aws_sesv2_email_identity.this.arn +} + +output "id" { + description = "The ID of the SES identity." + value = aws_sesv2_email_identity.this.id +} + +output "status" { + description = "The status of the SES identity." + value = aws_sesv2_email_identity.this.verified_for_sending_status ? "VERIFIED" : "PENDING" +} + +output "name" { + description = "The domain name for the SES identity." + value = aws_sesv2_email_identity.this.email_identity +} + +output "type" { + description = "The type of the SES identity." + value = aws_sesv2_email_identity.this.identity_type +} + +output "configuration_set" { + description = "The configuration set to use by default when sending from this identity." + value = aws_sesv2_email_identity.this.configuration_set_name +} + +output "dkim" { + description = "The configuration for the DKIM." + value = { + type = var.dkim.type + status = one(aws_sesv2_email_identity.this.dkim_signing_attributes[*].status) + current_signing_key_type = local.signing_key_type_reverse[one(aws_sesv2_email_identity.this.dkim_signing_attributes[*].current_signing_key_length)] + signing_key_type = local.signing_key_type_reverse[one(aws_sesv2_email_identity.this.dkim_signing_attributes[*].next_signing_key_length)] + selector_name = one(aws_sesv2_email_identity.this.dkim_signing_attributes[*].domain_signing_selector) + origin = one(aws_sesv2_email_identity.this.dkim_signing_attributes[*].signing_attributes_origin) + last_key_generated_at = one(aws_sesv2_email_identity.this.dkim_signing_attributes[*].last_key_generation_timestamp) + + verification = { + enabled = var.dkim.verification.enabled + zone = { + id = var.dkim.verification.zone_id + } + records = [ + for record in aws_route53_record.dkim : { + name = record.name + value = record.records + type = record.type + ttl = record.ttl + } + ] + } + } +} + +# output "debug" { +# value = { +# for k, v in aws_sesv2_email_identity.this : +# k => v +# if !contains(["arn", "id", "email_identity", "identity_type", "tags", "tags_all", "dkim_signing_attributes", "verified_for_sending_status", "configuration_set_name"], k) +# } +# } \ No newline at end of file diff --git a/modules/ses-identity/resource-group.tf b/modules/ses-identity/resource-group.tf new file mode 100644 index 0000000..7487ba0 --- /dev/null +++ b/modules/ses-identity/resource-group.tf @@ -0,0 +1,31 @@ +locals { + resource_group_name = (var.resource_group_name != "" + ? var.resource_group_name + : join(".", [ + local.metadata.package, + local.metadata.module, + replace(local.metadata.name, "/[^a-zA-Z0-9_\\.-]/", "-"), + ]) + ) +} + + +module "resource_group" { + source = "tedilabs/misc/aws//modules/resource-group" + version = "~> 0.10.0" + + count = (var.resource_group_enabled && var.module_tags_enabled) ? 1 : 0 + + name = local.resource_group_name + description = var.resource_group_description + + query = { + resource_tags = local.module_tags + } + + module_tags_enabled = false + tags = merge( + local.module_tags, + var.tags, + ) +} diff --git a/modules/ses-identity/route53.tf b/modules/ses-identity/route53.tf new file mode 100644 index 0000000..08d48b1 --- /dev/null +++ b/modules/ses-identity/route53.tf @@ -0,0 +1,18 @@ +################################################### +# DKIM Verification +################################################### + +resource "aws_route53_record" "dkim" { + for_each = (var.dkim.type == "EASY_DKIM" && var.dkim.verification.enabled + ? toset(aws_sesv2_email_identity.this.dkim_signing_attributes[0].tokens) + : [] + ) + + zone_id = var.dkim.verification.zone_id + name = "${each.key}._domainkey.${aws_sesv2_email_identity.this.email_identity}" + type = "CNAME" + records = ["${each.key}.dkim.amazonses.com"] + + ttl = "600" + allow_overwrite = true +} \ No newline at end of file diff --git a/modules/ses-identity/variables.tf b/modules/ses-identity/variables.tf new file mode 100644 index 0000000..c58c759 --- /dev/null +++ b/modules/ses-identity/variables.tf @@ -0,0 +1,151 @@ +variable "name" { + description = "(Required) The domain name of the SES domain identity." + type = string + nullable = false +} + +variable "configuration_set" { + description = <