Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ses module and example #6

Merged
merged 2 commits into from
Jun 10, 2024
Merged
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
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
}
}