Skip to content

Commit

Permalink
feat: add ses module and example (#6)
Browse files Browse the repository at this point in the history
* add ses module and example

* chore: Update terraform docs

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
Jlkan and github-actions[bot] authored Jun 10, 2024
1 parent 09daf26 commit a9b44bf
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 0 deletions.
21 changes: 21 additions & 0 deletions examples/ses/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions examples/ses/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!-- BEGIN_TF_DOCS -->
## Requirements

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

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 3.70.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_ses"></a> [ses](#module\_ses) | ../../modules/ses | n/a |

## Resources

| Name | Type |
|------|------|
| [aws_iam_access_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
| [aws_iam_user.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
| [aws_iam_user_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_dkim_verification_attrs"></a> [dkim\_verification\_attrs](#output\_dkim\_verification\_attrs) | n/a |
| <a name="output_domain_identity_verification_attrs"></a> [domain\_identity\_verification\_attrs](#output\_domain\_identity\_verification\_attrs) | n/a |
| <a name="output_user_key_id"></a> [user\_key\_id](#output\_user\_key\_id) | n/a |
| <a name="output_user_secret"></a> [user\_secret](#output\_user\_secret) | n/a |
<!-- END_TF_DOCS -->
30 changes: 30 additions & 0 deletions examples/ses/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
locals {
domain_name = "exmaple.com"
}

module "ses" {
source = "../../modules/ses"
domain_name = local.domain_name
name_prefix = "exmpale-com"

zone_id = data.aws_route53_zone.this.zone_id
verify_dkim = true
}

resource "aws_iam_user" "this" {
name = "example-com"
}

resource "aws_iam_access_key" "this" {
user = aws_iam_user.this.name
}

resource "aws_iam_user_policy_attachment" "this" {
user = aws_iam_user.this.name
policy_arn = module.ses.send_email_policy_arn
}

data "aws_route53_zone" "this" {
name = local.domain_name
private_zone = false
}
16 changes: 16 additions & 0 deletions examples/ses/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
output "user_key_id" {
value = aws_iam_access_key.this.id
}

output "user_secret" {
value = aws_iam_access_key.this.ses_smtp_password_v4
sensitive = true
}

output "dkim_verification_attrs" {
value = module.ses.dkim_verification_attrs
}

output "domain_identity_verification_attrs" {
value = module.ses.domain_identity_verification_attrs
}
15 changes: 15 additions & 0 deletions examples/ses/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
terraform {
required_version = ">= 0.14.5"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}

provider "aws" {
region = "eu-central-1"
}

21 changes: 21 additions & 0 deletions modules/ses/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions modules/ses/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!-- BEGIN_TF_DOCS -->


## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | 3.71.0 |

## Resources

| Name | Type |
|------|------|
| [aws_iam_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_route53_record.ses_dmarc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws_route53_record.ses_spf](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws_route53_record.this_verify_dkim](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws_ses_domain_dkim.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_domain_dkim) | resource |
| [aws_ses_domain_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_domain_identity) | resource |
| [aws_ses_email_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ses_email_identity) | resource |
| [aws_iam_policy_document.this](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_dmarc"></a> [dmarc](#input\_dmarc) | DMARC record for domain. Read full spec: https://mxtoolbox.com/dmarc/details/what-is-a-dmarc-record<br> v : required, protocol version (v=DMARC1)<br> p : required, policy (p=none\|quarantine\|reject)<br> pct : optional, percentage of messages subjected to filtering (0-100)<br> rua : optional, reporting URI for aggregate reports (mailto:[email protected],mailto:[email protected])<br> ruf : optional, reporting URI for forensic reports (mailto:[email protected],mailto:[email protected])<br> fo : optional, failure reporting options (fo=0\|1\|d\|s)<br> aspf : optional, The aspf tag represents alignment mode for SPF. An optional tag, aspf=r is a common example of its configuration.<br> adkim : optional, The adkim tag represents alignment mode for DKIM. An optional tag, adkim=r is a common example of its configuration.<br> rf : optional, The rf tag represents reporting format. An optional tag, rf=afrf is a common example of its configuration.<br> ri : optional, The ri tag represents reporting interval. An optional tag, ri=86400 is a common example of its configuration.<br> sp : optional, The sp tag represents subdomain policy. An optional tag, sp=reject is a common example of its configuration. | <pre>object({<br> v = string # required, protocol version (v=DMARC1)<br> p = string # required, policy (p=none|quarantine|reject)<br> pct = number # optional, percentage of messages subjected to filtering (0-100)<br> rua = string # optional, reporting URI for aggregate reports (mailto:[email protected],mailto:[email protected])<br> ruf = string # optional, reporting URI for forensic reports (mailto:[email protected],mailto:[email protected])<br> fo = string # optional, failure reporting options (fo=0|1|d|s)<br> aspf = string # optional, The aspf tag represents alignment mode for SPF. An optional tag, aspf=r is a common example of its configuration.<br> adkim = string # optional, The adkim tag represents alignment mode for DKIM. An optional tag, adkim=r is a common example of its configuration.<br> rf = string # optional, The rf tag represents reporting format. An optional tag, rf=afrf is a common example of its configuration.<br> ri = string # optional, The ri tag represents reporting interval. An optional tag, ri=86400 is a common example of its configuration.<br> sp = string # optional, The sp tag represents subdomain policy. An optional tag, sp=reject is a common example of its configuration.<br> })</pre> | <pre>{<br> "adkim": "s",<br> "aspf": "s",<br> "fo": null,<br> "p": "reject",<br> "pct": "100",<br> "rf": null,<br> "ri": null,<br> "rua": null,<br> "ruf": null,<br> "sp": null,<br> "v": "DMARC1"<br>}</pre> | no |
| <a name="input_dmarc_enabled"></a> [dmarc\_enabled](#input\_dmarc\_enabled) | Set DMARC record in Route53. | `bool` | `false` | no |
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | The domain name from which AWS SES will be able to send emails. | `string` | n/a | yes |
| <a name="input_email_addresses"></a> [email\_addresses](#input\_email\_addresses) | Emails from which AWS SES will be able to send emails. | `set(string)` | `[]` | no |
| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Prefix that will be prepended to resource names | `string` | n/a | yes |
| <a name="input_spf_enabled"></a> [spf\_enabled](#input\_spf\_enabled) | Set SPF record in Route53. | `bool` | `false` | no |
| <a name="input_verify_dkim"></a> [verify\_dkim](#input\_verify\_dkim) | Automatically verify DKIM records in Route53. | `bool` | `false` | no |
| <a name="input_zone_id"></a> [zone\_id](#input\_zone\_id) | The Route53 zone ID for the domain name. | `string` | `""` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_dkim_verification_attrs"></a> [dkim\_verification\_attrs](#output\_dkim\_verification\_attrs) | DKIM name, value, type attributes needed to verify domain |
| <a name="output_send_email_policy_arn"></a> [send\_email\_policy\_arn](#output\_send\_email\_policy\_arn) | IAM policy ARN for sending emails |
<!-- END_TF_DOCS -->
87 changes: 87 additions & 0 deletions modules/ses/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
locals {
dkim_verification_attrs = [for dkim in aws_ses_domain_dkim.this.dkim_tokens : {
name = "${dkim}._domainkey.${var.domain_name}"
ttl = 600
type = "CNAME"
value = "${dkim}.dkim.amazonses.com"
}]

# remove empty options
dmarc_record = join(";", compact([
"v=${var.dmarc.v}",
"p=${var.dmarc.p}",
var.dmarc.pct == null ? null : "pct=${var.dmarc.pct}",
var.dmarc.rua == null ? null : "rua=${var.dmarc.rua}",
var.dmarc.ruf == null ? null : "ruf=${var.dmarc.ruf}",
var.dmarc.fo == null ? null : "fo=${var.dmarc.fo}",
var.dmarc.aspf == null ? null : "aspf=${var.dmarc.aspf}",
var.dmarc.adkim == null ? null : "adkim=${var.dmarc.adkim}",
var.dmarc.rf == null ? null : "rf=${var.dmarc.rf}",
var.dmarc.ri == null ? null : "ri=${var.dmarc.ri}",
var.dmarc.sp == null ? null : "sp=${var.dmarc.sp}",
]))
}

resource "aws_ses_email_identity" "this" {
for_each = var.email_addresses
email = each.key
}

resource "aws_ses_domain_identity" "this" {
domain = var.domain_name
}

resource "aws_ses_domain_dkim" "this" {
domain = aws_ses_domain_identity.this.domain
}

data "aws_iam_policy_document" "this" {
statement {
actions = [
"ses:SendEmail",
"ses:SendRawEmail",
]

resources = flatten(
[[for email in aws_ses_email_identity.this : email.arn], aws_ses_domain_identity.this.arn],
)
}
}

resource "aws_iam_policy" "this" {
name = "${var.name_prefix}-ses-send-email"
policy = data.aws_iam_policy_document.this.json
}


resource "aws_route53_record" "this_verify_dkim" {
count = var.zone_id != "" && var.verify_dkim ? 3 : 0

zone_id = var.zone_id
name = local.dkim_verification_attrs[count.index].name
type = local.dkim_verification_attrs[count.index].type
ttl = local.dkim_verification_attrs[count.index].ttl
records = [local.dkim_verification_attrs[count.index].value]
}

resource "aws_route53_record" "ses_dmarc" {
count = var.zone_id != "" && var.dmarc_enabled ? 1 : 0

zone_id = var.zone_id
name = "_dmarc.${var.domain_name}"
type = "TXT"
ttl = 600
records = [local.dmarc_record]
}

resource "aws_route53_record" "ses_spf" {
count = var.zone_id != "" && var.spf_enabled ? 1 : 0

zone_id = var.zone_id
name = var.domain_name
type = "TXT"
ttl = 600
records = [
"v=spf1 include:amazonses.com -all"
]
}
9 changes: 9 additions & 0 deletions modules/ses/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "dkim_verification_attrs" {
value = local.dkim_verification_attrs
description = "DKIM name, value, type attributes needed to verify domain"
}

output "send_email_policy_arn" {
value = aws_iam_policy.this.arn
description = "IAM policy ARN for sending emails"
}
84 changes: 84 additions & 0 deletions modules/ses/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
variable "name_prefix" {
type = string
description = "Prefix that will be prepended to resource names"
}

variable "domain_name" {
type = string
description = "The domain name from which AWS SES will be able to send emails."
}

# optional

variable "email_addresses" {
type = set(string)
default = []
description = "Emails from which AWS SES will be able to send emails."
}

variable "zone_id" {
type = string
description = "The Route53 zone ID for the domain name."
default = ""
}

variable "verify_dkim" {
type = bool
description = "Automatically verify DKIM records in Route53."
default = false
}

variable "dmarc_enabled" {
type = bool
description = "Set DMARC record in Route53."
default = false
}

variable "spf_enabled" {
type = bool
description = "Set SPF record in Route53."
default = false
}

variable "dmarc" {
description = <<EOT
DMARC record for domain. Read full spec: https://mxtoolbox.com/dmarc/details/what-is-a-dmarc-record
v : required, protocol version (v=DMARC1)
p : required, policy (p=none|quarantine|reject)
pct : optional, percentage of messages subjected to filtering (0-100)
rua : optional, reporting URI for aggregate reports (mailto:[email protected],mailto:[email protected])
ruf : optional, reporting URI for forensic reports (mailto:[email protected],mailto:[email protected])
fo : optional, failure reporting options (fo=0|1|d|s)
aspf : optional, The aspf tag represents alignment mode for SPF. An optional tag, aspf=r is a common example of its configuration.
adkim : optional, The adkim tag represents alignment mode for DKIM. An optional tag, adkim=r is a common example of its configuration.
rf : optional, The rf tag represents reporting format. An optional tag, rf=afrf is a common example of its configuration.
ri : optional, The ri tag represents reporting interval. An optional tag, ri=86400 is a common example of its configuration.
sp : optional, The sp tag represents subdomain policy. An optional tag, sp=reject is a common example of its configuration.
EOT
type = object({
v = string # required, protocol version (v=DMARC1)
p = string # required, policy (p=none|quarantine|reject)
pct = number # optional, percentage of messages subjected to filtering (0-100)
rua = string # optional, reporting URI for aggregate reports (mailto:[email protected],mailto:[email protected])
ruf = string # optional, reporting URI for forensic reports (mailto:[email protected],mailto:[email protected])
fo = string # optional, failure reporting options (fo=0|1|d|s)
aspf = string # optional, The aspf tag represents alignment mode for SPF. An optional tag, aspf=r is a common example of its configuration.
adkim = string # optional, The adkim tag represents alignment mode for DKIM. An optional tag, adkim=r is a common example of its configuration.
rf = string # optional, The rf tag represents reporting format. An optional tag, rf=afrf is a common example of its configuration.
ri = string # optional, The ri tag represents reporting interval. An optional tag, ri=86400 is a common example of its configuration.
sp = string # optional, The sp tag represents subdomain policy. An optional tag, sp=reject is a common example of its configuration.
})
default = {
v = "DMARC1"
p = "reject"
pct = "100"
rua = null
ruf = null
fo = null
aspf = "s"
adkim = "s"
rf = null
ri = null
sp = null
}
}

0 comments on commit a9b44bf

Please sign in to comment.