@everydaydevopsio/hail 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +198 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +171 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +56 -0
- package/dist/email.d.ts +36 -0
- package/dist/email.js +119 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +141 -0
- package/dist/playwright.d.ts +13 -0
- package/dist/playwright.js +23 -0
- package/dist/setup.d.ts +17 -0
- package/dist/setup.js +95 -0
- package/dist/store.d.ts +29 -0
- package/dist/store.js +119 -0
- package/package.json +89 -4
- package/terraform/examples/cloudflare/.terraform.lock.hcl +64 -0
- package/terraform/examples/cloudflare/main.tf +95 -0
- package/terraform/examples/manual/.terraform.lock.hcl +47 -0
- package/terraform/examples/manual/main.tf +71 -0
- package/terraform/examples/route53/.terraform.lock.hcl +47 -0
- package/terraform/examples/route53/main.tf +94 -0
- package/terraform/modules/receiver/.terraform.lock.hcl +47 -0
- package/terraform/modules/receiver/lambda/handler.py +83 -0
- package/terraform/modules/receiver/main.tf +226 -0
- package/terraform/modules/receiver/outputs.tf +23 -0
- package/terraform/modules/receiver/variables.tf +67 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.7, < 2.0"
|
|
3
|
+
required_providers {
|
|
4
|
+
aws = { source = "hashicorp/aws", version = ">= 5.50, < 7.0" }
|
|
5
|
+
cloudflare = { source = "cloudflare/cloudflare", version = "~> 5.0" }
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
provider "aws" { region = var.region }
|
|
9
|
+
# Reads CLOUDFLARE_API_TOKEN from the environment. Do not put it in tfvars.
|
|
10
|
+
provider "cloudflare" {}
|
|
11
|
+
variable "name" { type = string }
|
|
12
|
+
variable "domain" { type = string }
|
|
13
|
+
variable "zone_name" { type = string }
|
|
14
|
+
variable "zone_id" { type = string }
|
|
15
|
+
variable "region" { type = string }
|
|
16
|
+
variable "existing_rule_set_name" {
|
|
17
|
+
type = string
|
|
18
|
+
default = null
|
|
19
|
+
}
|
|
20
|
+
variable "manage_rule_set_activation" {
|
|
21
|
+
type = bool
|
|
22
|
+
default = false
|
|
23
|
+
}
|
|
24
|
+
variable "retention_days" {
|
|
25
|
+
type = number
|
|
26
|
+
default = 3
|
|
27
|
+
}
|
|
28
|
+
variable "reader_principal_arns" {
|
|
29
|
+
type = list(string)
|
|
30
|
+
default = []
|
|
31
|
+
}
|
|
32
|
+
variable "external_indexer_role_arn" {
|
|
33
|
+
type = string
|
|
34
|
+
default = null
|
|
35
|
+
}
|
|
36
|
+
variable "external_reader_role_arn" {
|
|
37
|
+
type = string
|
|
38
|
+
default = null
|
|
39
|
+
}
|
|
40
|
+
variable "permissions_boundary_arn" {
|
|
41
|
+
type = string
|
|
42
|
+
default = null
|
|
43
|
+
}
|
|
44
|
+
variable "tags" {
|
|
45
|
+
type = map(string)
|
|
46
|
+
default = {}
|
|
47
|
+
}
|
|
48
|
+
data "cloudflare_zone" "selected" { zone_id = var.zone_id }
|
|
49
|
+
resource "terraform_data" "guard" {
|
|
50
|
+
input = var.domain
|
|
51
|
+
lifecycle {
|
|
52
|
+
precondition {
|
|
53
|
+
condition = data.cloudflare_zone.selected.name == var.zone_name && var.domain != var.zone_name && endswith(var.domain, ".${var.zone_name}")
|
|
54
|
+
error_message = "Use a dedicated subdomain of the selected existing zone; never the zone apex."
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
module "receiver" {
|
|
59
|
+
source = "../../modules/receiver"
|
|
60
|
+
name = var.name
|
|
61
|
+
domain = var.domain
|
|
62
|
+
region = var.region
|
|
63
|
+
existing_rule_set_name = var.existing_rule_set_name
|
|
64
|
+
manage_rule_set_activation = var.manage_rule_set_activation
|
|
65
|
+
retention_days = var.retention_days
|
|
66
|
+
reader_principal_arns = var.reader_principal_arns
|
|
67
|
+
external_indexer_role_arn = var.external_indexer_role_arn
|
|
68
|
+
external_reader_role_arn = var.external_reader_role_arn
|
|
69
|
+
permissions_boundary_arn = var.permissions_boundary_arn
|
|
70
|
+
tags = var.tags
|
|
71
|
+
depends_on = [terraform_data.guard]
|
|
72
|
+
}
|
|
73
|
+
resource "cloudflare_dns_record" "mx" {
|
|
74
|
+
zone_id = var.zone_id
|
|
75
|
+
name = var.domain
|
|
76
|
+
type = "MX"
|
|
77
|
+
content = module.receiver.mx_target
|
|
78
|
+
priority = 10
|
|
79
|
+
ttl = 300
|
|
80
|
+
}
|
|
81
|
+
resource "cloudflare_dns_record" "verification" {
|
|
82
|
+
zone_id = var.zone_id
|
|
83
|
+
name = "_amazonses.${var.domain}"
|
|
84
|
+
type = "TXT"
|
|
85
|
+
content = module.receiver.verification_token
|
|
86
|
+
ttl = 300
|
|
87
|
+
}
|
|
88
|
+
resource "aws_ses_domain_identity_verification" "receiver" {
|
|
89
|
+
domain = var.domain
|
|
90
|
+
depends_on = [cloudflare_dns_record.mx, cloudflare_dns_record.verification]
|
|
91
|
+
}
|
|
92
|
+
output "hail_config" { value = module.receiver.hail_config }
|
|
93
|
+
output "dns_records" { value = module.receiver.dns_records }
|
|
94
|
+
output "reader_policy_arn" { value = module.receiver.reader_policy_arn }
|
|
95
|
+
output "dlq_url" { value = module.receiver.dlq_url }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# This file is maintained automatically by "terraform init".
|
|
2
|
+
# Manual edits may be lost in future updates.
|
|
3
|
+
|
|
4
|
+
provider "registry.terraform.io/hashicorp/archive" {
|
|
5
|
+
version = "2.8.1"
|
|
6
|
+
constraints = ">= 2.4.0, < 3.0.0"
|
|
7
|
+
hashes = [
|
|
8
|
+
"h1:KfIRyazppfpvRKIW5URe4sIVRPPdcbtt4ddkYd1Bw3Y=",
|
|
9
|
+
"zh:03de290604114a89fcd45c2e5bc7787d5a1ebfc5f964fb5989306bea7a4c79ec",
|
|
10
|
+
"zh:0a7d69dc9fbbc48960bc2f04588c8fb1bd92c78a8f306566b7fb17fc4a4f2058",
|
|
11
|
+
"zh:4df1f3981379c35f1757da957470f7f7724496b57219da3485177cf1647bdf59",
|
|
12
|
+
"zh:50f0e72ba53bfe6e11b03b7fc899e1f72536354381d302a743a274ae2a3f45f4",
|
|
13
|
+
"zh:5c4e15a04c98e2a8cafb1cd9632b48ad318051e8462d105b1153006277985c35",
|
|
14
|
+
"zh:66069e604bcf5c4af0278e15997d9e6bd755c54fb3801d78885838b889729c5f",
|
|
15
|
+
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
|
16
|
+
"zh:979765db3f42601870ab377104ba70befb029547b278337ec4ada980e3582de6",
|
|
17
|
+
"zh:b165254da774f49945a73fbccc3ef1b63d70ea00a98fa9e14716665ba80ecaad",
|
|
18
|
+
"zh:c0bb2697b525da9fec4511f569ed2bd2b42f25d5c1f9fa00f8f3645b50cfc2e9",
|
|
19
|
+
"zh:c48f6695d12df0d0fa5231f7c1b8d518a4feae91a733fdd35a5804af3a303831",
|
|
20
|
+
"zh:d589954c93f075180c9f4e2ce91780d5edcb56dfd0d3cda8ba08e13c10b52249",
|
|
21
|
+
"zh:f5792ed06da65d0daf7ca3711f5399ff78c7cb4400afe53fbce9a926fbde4477",
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
provider "registry.terraform.io/hashicorp/aws" {
|
|
26
|
+
version = "6.66.0"
|
|
27
|
+
constraints = ">= 5.50.0, < 7.0.0"
|
|
28
|
+
hashes = [
|
|
29
|
+
"h1:hBEaeBm9nm7A/u1nnD0nfolTPP55/BoKRFWk8zG8/fk=",
|
|
30
|
+
"zh:156fe7164a3d26ef6b35734c43e99fb198df90575ed897d1182b8e930b8cd523",
|
|
31
|
+
"zh:1af52b22b35be00f8d16e3ebebff9fa699ec4db2ef69e6032ba5c536f80c03d9",
|
|
32
|
+
"zh:2545a8478bd551fdc9694f6cc1a1ad24617f6736f8bde0ad6cae90987c65380f",
|
|
33
|
+
"zh:4070db1ee369ccb41cb610bfd887386bc0a9b9ecad60aeb4dbce58443d2519dd",
|
|
34
|
+
"zh:53da7d3c1840ef875c7d34e967732502a64fe677af0e78824773d4c15a8fe740",
|
|
35
|
+
"zh:576a93a28bf611a4de2a2e6ced697a41d5126b8fd31d30782b16797e410a9706",
|
|
36
|
+
"zh:58fed5fa9a033355b9d4f3092c817b70d934100e0d8678d6e4c93f3c9493d4e4",
|
|
37
|
+
"zh:6a9ca2f24e2ee9156dd785d159a850b35d190e9cf7eca21cb9582970c2db80cd",
|
|
38
|
+
"zh:729edd30f99cc16009deba5c013265b0c81eda261a3d0821cbd011d3287fd230",
|
|
39
|
+
"zh:7ae460049b75bd4aefee465ef7c53a01ac2df46d4d3e3ac00824afa8b5cb83fb",
|
|
40
|
+
"zh:9051fa85c8034ade8a57a5c6f232fd33da28f3800bb5aa40bc8625dbc5e27632",
|
|
41
|
+
"zh:906547e4319805e7acf7fbdf2bac28a4b1a7370790a2a430c7adb1b29bb934eb",
|
|
42
|
+
"zh:998f27410a66158a35ee5ed142c27e5b21fe8601941da55da2157f8042d6dcca",
|
|
43
|
+
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
|
44
|
+
"zh:9c1804eff1dda0446dc2d215231015bb65a2fc6c3b7ba24584fe45f1ddd3fa9f",
|
|
45
|
+
"zh:b03ff5efdee310502aaaeb460144dc059bce72a0d8217e6b989099ef8aef9283",
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.7, < 2.0"
|
|
3
|
+
required_providers { aws = { source = "hashicorp/aws", version = ">= 5.50, < 7.0" } }
|
|
4
|
+
}
|
|
5
|
+
provider "aws" { region = var.region }
|
|
6
|
+
variable "name" { type = string }
|
|
7
|
+
variable "domain" { type = string }
|
|
8
|
+
variable "zone_name" { type = string }
|
|
9
|
+
variable "region" { type = string }
|
|
10
|
+
variable "existing_rule_set_name" {
|
|
11
|
+
type = string
|
|
12
|
+
default = null
|
|
13
|
+
}
|
|
14
|
+
variable "manage_rule_set_activation" {
|
|
15
|
+
type = bool
|
|
16
|
+
default = false
|
|
17
|
+
}
|
|
18
|
+
variable "retention_days" {
|
|
19
|
+
type = number
|
|
20
|
+
default = 3
|
|
21
|
+
}
|
|
22
|
+
variable "reader_principal_arns" {
|
|
23
|
+
type = list(string)
|
|
24
|
+
default = []
|
|
25
|
+
}
|
|
26
|
+
variable "external_indexer_role_arn" {
|
|
27
|
+
type = string
|
|
28
|
+
default = null
|
|
29
|
+
}
|
|
30
|
+
variable "external_reader_role_arn" {
|
|
31
|
+
type = string
|
|
32
|
+
default = null
|
|
33
|
+
}
|
|
34
|
+
variable "permissions_boundary_arn" {
|
|
35
|
+
type = string
|
|
36
|
+
default = null
|
|
37
|
+
}
|
|
38
|
+
variable "tags" {
|
|
39
|
+
type = map(string)
|
|
40
|
+
default = {}
|
|
41
|
+
}
|
|
42
|
+
resource "terraform_data" "guard" {
|
|
43
|
+
input = var.domain
|
|
44
|
+
lifecycle {
|
|
45
|
+
precondition {
|
|
46
|
+
condition = var.domain != var.zone_name && endswith(var.domain, ".${var.zone_name}")
|
|
47
|
+
error_message = "Use a dedicated test subdomain; never change the company email domain."
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
module "receiver" {
|
|
52
|
+
source = "../../modules/receiver"
|
|
53
|
+
name = var.name
|
|
54
|
+
domain = var.domain
|
|
55
|
+
region = var.region
|
|
56
|
+
existing_rule_set_name = var.existing_rule_set_name
|
|
57
|
+
manage_rule_set_activation = var.manage_rule_set_activation
|
|
58
|
+
retention_days = var.retention_days
|
|
59
|
+
reader_principal_arns = var.reader_principal_arns
|
|
60
|
+
external_indexer_role_arn = var.external_indexer_role_arn
|
|
61
|
+
external_reader_role_arn = var.external_reader_role_arn
|
|
62
|
+
permissions_boundary_arn = var.permissions_boundary_arn
|
|
63
|
+
tags = var.tags
|
|
64
|
+
depends_on = [terraform_data.guard]
|
|
65
|
+
}
|
|
66
|
+
# Manual mode deliberately does not wait for DNS during apply.
|
|
67
|
+
# Publish dns_records, then run `hail doctor` to verify readiness.
|
|
68
|
+
output "hail_config" { value = module.receiver.hail_config }
|
|
69
|
+
output "dns_records" { value = module.receiver.dns_records }
|
|
70
|
+
output "reader_policy_arn" { value = module.receiver.reader_policy_arn }
|
|
71
|
+
output "dlq_url" { value = module.receiver.dlq_url }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# This file is maintained automatically by "terraform init".
|
|
2
|
+
# Manual edits may be lost in future updates.
|
|
3
|
+
|
|
4
|
+
provider "registry.terraform.io/hashicorp/archive" {
|
|
5
|
+
version = "2.8.1"
|
|
6
|
+
constraints = ">= 2.4.0, < 3.0.0"
|
|
7
|
+
hashes = [
|
|
8
|
+
"h1:KfIRyazppfpvRKIW5URe4sIVRPPdcbtt4ddkYd1Bw3Y=",
|
|
9
|
+
"zh:03de290604114a89fcd45c2e5bc7787d5a1ebfc5f964fb5989306bea7a4c79ec",
|
|
10
|
+
"zh:0a7d69dc9fbbc48960bc2f04588c8fb1bd92c78a8f306566b7fb17fc4a4f2058",
|
|
11
|
+
"zh:4df1f3981379c35f1757da957470f7f7724496b57219da3485177cf1647bdf59",
|
|
12
|
+
"zh:50f0e72ba53bfe6e11b03b7fc899e1f72536354381d302a743a274ae2a3f45f4",
|
|
13
|
+
"zh:5c4e15a04c98e2a8cafb1cd9632b48ad318051e8462d105b1153006277985c35",
|
|
14
|
+
"zh:66069e604bcf5c4af0278e15997d9e6bd755c54fb3801d78885838b889729c5f",
|
|
15
|
+
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
|
16
|
+
"zh:979765db3f42601870ab377104ba70befb029547b278337ec4ada980e3582de6",
|
|
17
|
+
"zh:b165254da774f49945a73fbccc3ef1b63d70ea00a98fa9e14716665ba80ecaad",
|
|
18
|
+
"zh:c0bb2697b525da9fec4511f569ed2bd2b42f25d5c1f9fa00f8f3645b50cfc2e9",
|
|
19
|
+
"zh:c48f6695d12df0d0fa5231f7c1b8d518a4feae91a733fdd35a5804af3a303831",
|
|
20
|
+
"zh:d589954c93f075180c9f4e2ce91780d5edcb56dfd0d3cda8ba08e13c10b52249",
|
|
21
|
+
"zh:f5792ed06da65d0daf7ca3711f5399ff78c7cb4400afe53fbce9a926fbde4477",
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
provider "registry.terraform.io/hashicorp/aws" {
|
|
26
|
+
version = "6.66.0"
|
|
27
|
+
constraints = ">= 5.50.0, < 7.0.0"
|
|
28
|
+
hashes = [
|
|
29
|
+
"h1:hBEaeBm9nm7A/u1nnD0nfolTPP55/BoKRFWk8zG8/fk=",
|
|
30
|
+
"zh:156fe7164a3d26ef6b35734c43e99fb198df90575ed897d1182b8e930b8cd523",
|
|
31
|
+
"zh:1af52b22b35be00f8d16e3ebebff9fa699ec4db2ef69e6032ba5c536f80c03d9",
|
|
32
|
+
"zh:2545a8478bd551fdc9694f6cc1a1ad24617f6736f8bde0ad6cae90987c65380f",
|
|
33
|
+
"zh:4070db1ee369ccb41cb610bfd887386bc0a9b9ecad60aeb4dbce58443d2519dd",
|
|
34
|
+
"zh:53da7d3c1840ef875c7d34e967732502a64fe677af0e78824773d4c15a8fe740",
|
|
35
|
+
"zh:576a93a28bf611a4de2a2e6ced697a41d5126b8fd31d30782b16797e410a9706",
|
|
36
|
+
"zh:58fed5fa9a033355b9d4f3092c817b70d934100e0d8678d6e4c93f3c9493d4e4",
|
|
37
|
+
"zh:6a9ca2f24e2ee9156dd785d159a850b35d190e9cf7eca21cb9582970c2db80cd",
|
|
38
|
+
"zh:729edd30f99cc16009deba5c013265b0c81eda261a3d0821cbd011d3287fd230",
|
|
39
|
+
"zh:7ae460049b75bd4aefee465ef7c53a01ac2df46d4d3e3ac00824afa8b5cb83fb",
|
|
40
|
+
"zh:9051fa85c8034ade8a57a5c6f232fd33da28f3800bb5aa40bc8625dbc5e27632",
|
|
41
|
+
"zh:906547e4319805e7acf7fbdf2bac28a4b1a7370790a2a430c7adb1b29bb934eb",
|
|
42
|
+
"zh:998f27410a66158a35ee5ed142c27e5b21fe8601941da55da2157f8042d6dcca",
|
|
43
|
+
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
|
44
|
+
"zh:9c1804eff1dda0446dc2d215231015bb65a2fc6c3b7ba24584fe45f1ddd3fa9f",
|
|
45
|
+
"zh:b03ff5efdee310502aaaeb460144dc059bce72a0d8217e6b989099ef8aef9283",
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
terraform {
|
|
2
|
+
required_version = ">= 1.7, < 2.0"
|
|
3
|
+
required_providers { aws = { source = "hashicorp/aws", version = ">= 5.50, < 7.0" } }
|
|
4
|
+
}
|
|
5
|
+
provider "aws" { region = var.region }
|
|
6
|
+
variable "name" { type = string }
|
|
7
|
+
variable "domain" { type = string }
|
|
8
|
+
variable "zone_name" { type = string }
|
|
9
|
+
variable "zone_id" { type = string }
|
|
10
|
+
variable "region" { type = string }
|
|
11
|
+
variable "existing_rule_set_name" {
|
|
12
|
+
type = string
|
|
13
|
+
default = null
|
|
14
|
+
}
|
|
15
|
+
variable "manage_rule_set_activation" {
|
|
16
|
+
type = bool
|
|
17
|
+
default = false
|
|
18
|
+
}
|
|
19
|
+
variable "retention_days" {
|
|
20
|
+
type = number
|
|
21
|
+
default = 3
|
|
22
|
+
}
|
|
23
|
+
variable "reader_principal_arns" {
|
|
24
|
+
type = list(string)
|
|
25
|
+
default = []
|
|
26
|
+
}
|
|
27
|
+
variable "external_indexer_role_arn" {
|
|
28
|
+
type = string
|
|
29
|
+
default = null
|
|
30
|
+
}
|
|
31
|
+
variable "external_reader_role_arn" {
|
|
32
|
+
type = string
|
|
33
|
+
default = null
|
|
34
|
+
}
|
|
35
|
+
variable "permissions_boundary_arn" {
|
|
36
|
+
type = string
|
|
37
|
+
default = null
|
|
38
|
+
}
|
|
39
|
+
variable "tags" {
|
|
40
|
+
type = map(string)
|
|
41
|
+
default = {}
|
|
42
|
+
}
|
|
43
|
+
data "aws_route53_zone" "selected" {
|
|
44
|
+
zone_id = var.zone_id
|
|
45
|
+
private_zone = false
|
|
46
|
+
}
|
|
47
|
+
resource "terraform_data" "guard" {
|
|
48
|
+
input = var.domain
|
|
49
|
+
lifecycle {
|
|
50
|
+
precondition {
|
|
51
|
+
condition = !data.aws_route53_zone.selected.private_zone && trimsuffix(data.aws_route53_zone.selected.name, ".") == var.zone_name && var.domain != var.zone_name && endswith(var.domain, ".${var.zone_name}")
|
|
52
|
+
error_message = "Use a dedicated subdomain of the selected PUBLIC hosted zone; never the zone apex."
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
module "receiver" {
|
|
57
|
+
source = "../../modules/receiver"
|
|
58
|
+
name = var.name
|
|
59
|
+
domain = var.domain
|
|
60
|
+
region = var.region
|
|
61
|
+
existing_rule_set_name = var.existing_rule_set_name
|
|
62
|
+
manage_rule_set_activation = var.manage_rule_set_activation
|
|
63
|
+
retention_days = var.retention_days
|
|
64
|
+
reader_principal_arns = var.reader_principal_arns
|
|
65
|
+
external_indexer_role_arn = var.external_indexer_role_arn
|
|
66
|
+
external_reader_role_arn = var.external_reader_role_arn
|
|
67
|
+
permissions_boundary_arn = var.permissions_boundary_arn
|
|
68
|
+
tags = var.tags
|
|
69
|
+
depends_on = [terraform_data.guard]
|
|
70
|
+
}
|
|
71
|
+
resource "aws_route53_record" "mx" {
|
|
72
|
+
zone_id = var.zone_id
|
|
73
|
+
name = var.domain
|
|
74
|
+
type = "MX"
|
|
75
|
+
ttl = 300
|
|
76
|
+
records = ["10 ${module.receiver.mx_target}"]
|
|
77
|
+
allow_overwrite = false
|
|
78
|
+
}
|
|
79
|
+
resource "aws_route53_record" "verification" {
|
|
80
|
+
zone_id = var.zone_id
|
|
81
|
+
name = "_amazonses.${var.domain}"
|
|
82
|
+
type = "TXT"
|
|
83
|
+
ttl = 300
|
|
84
|
+
records = [module.receiver.verification_token]
|
|
85
|
+
allow_overwrite = false
|
|
86
|
+
}
|
|
87
|
+
resource "aws_ses_domain_identity_verification" "receiver" {
|
|
88
|
+
domain = var.domain
|
|
89
|
+
depends_on = [aws_route53_record.mx, aws_route53_record.verification]
|
|
90
|
+
}
|
|
91
|
+
output "hail_config" { value = module.receiver.hail_config }
|
|
92
|
+
output "dns_records" { value = module.receiver.dns_records }
|
|
93
|
+
output "reader_policy_arn" { value = module.receiver.reader_policy_arn }
|
|
94
|
+
output "dlq_url" { value = module.receiver.dlq_url }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# This file is maintained automatically by "terraform init".
|
|
2
|
+
# Manual edits may be lost in future updates.
|
|
3
|
+
|
|
4
|
+
provider "registry.terraform.io/hashicorp/archive" {
|
|
5
|
+
version = "2.8.1"
|
|
6
|
+
constraints = ">= 2.4.0, < 3.0.0"
|
|
7
|
+
hashes = [
|
|
8
|
+
"h1:KfIRyazppfpvRKIW5URe4sIVRPPdcbtt4ddkYd1Bw3Y=",
|
|
9
|
+
"zh:03de290604114a89fcd45c2e5bc7787d5a1ebfc5f964fb5989306bea7a4c79ec",
|
|
10
|
+
"zh:0a7d69dc9fbbc48960bc2f04588c8fb1bd92c78a8f306566b7fb17fc4a4f2058",
|
|
11
|
+
"zh:4df1f3981379c35f1757da957470f7f7724496b57219da3485177cf1647bdf59",
|
|
12
|
+
"zh:50f0e72ba53bfe6e11b03b7fc899e1f72536354381d302a743a274ae2a3f45f4",
|
|
13
|
+
"zh:5c4e15a04c98e2a8cafb1cd9632b48ad318051e8462d105b1153006277985c35",
|
|
14
|
+
"zh:66069e604bcf5c4af0278e15997d9e6bd755c54fb3801d78885838b889729c5f",
|
|
15
|
+
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
|
16
|
+
"zh:979765db3f42601870ab377104ba70befb029547b278337ec4ada980e3582de6",
|
|
17
|
+
"zh:b165254da774f49945a73fbccc3ef1b63d70ea00a98fa9e14716665ba80ecaad",
|
|
18
|
+
"zh:c0bb2697b525da9fec4511f569ed2bd2b42f25d5c1f9fa00f8f3645b50cfc2e9",
|
|
19
|
+
"zh:c48f6695d12df0d0fa5231f7c1b8d518a4feae91a733fdd35a5804af3a303831",
|
|
20
|
+
"zh:d589954c93f075180c9f4e2ce91780d5edcb56dfd0d3cda8ba08e13c10b52249",
|
|
21
|
+
"zh:f5792ed06da65d0daf7ca3711f5399ff78c7cb4400afe53fbce9a926fbde4477",
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
provider "registry.terraform.io/hashicorp/aws" {
|
|
26
|
+
version = "6.66.0"
|
|
27
|
+
constraints = ">= 5.50.0, < 7.0.0"
|
|
28
|
+
hashes = [
|
|
29
|
+
"h1:hBEaeBm9nm7A/u1nnD0nfolTPP55/BoKRFWk8zG8/fk=",
|
|
30
|
+
"zh:156fe7164a3d26ef6b35734c43e99fb198df90575ed897d1182b8e930b8cd523",
|
|
31
|
+
"zh:1af52b22b35be00f8d16e3ebebff9fa699ec4db2ef69e6032ba5c536f80c03d9",
|
|
32
|
+
"zh:2545a8478bd551fdc9694f6cc1a1ad24617f6736f8bde0ad6cae90987c65380f",
|
|
33
|
+
"zh:4070db1ee369ccb41cb610bfd887386bc0a9b9ecad60aeb4dbce58443d2519dd",
|
|
34
|
+
"zh:53da7d3c1840ef875c7d34e967732502a64fe677af0e78824773d4c15a8fe740",
|
|
35
|
+
"zh:576a93a28bf611a4de2a2e6ced697a41d5126b8fd31d30782b16797e410a9706",
|
|
36
|
+
"zh:58fed5fa9a033355b9d4f3092c817b70d934100e0d8678d6e4c93f3c9493d4e4",
|
|
37
|
+
"zh:6a9ca2f24e2ee9156dd785d159a850b35d190e9cf7eca21cb9582970c2db80cd",
|
|
38
|
+
"zh:729edd30f99cc16009deba5c013265b0c81eda261a3d0821cbd011d3287fd230",
|
|
39
|
+
"zh:7ae460049b75bd4aefee465ef7c53a01ac2df46d4d3e3ac00824afa8b5cb83fb",
|
|
40
|
+
"zh:9051fa85c8034ade8a57a5c6f232fd33da28f3800bb5aa40bc8625dbc5e27632",
|
|
41
|
+
"zh:906547e4319805e7acf7fbdf2bac28a4b1a7370790a2a430c7adb1b29bb934eb",
|
|
42
|
+
"zh:998f27410a66158a35ee5ed142c27e5b21fe8601941da55da2157f8042d6dcca",
|
|
43
|
+
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
|
44
|
+
"zh:9c1804eff1dda0446dc2d215231015bb65a2fc6c3b7ba24584fe45f1ddd3fa9f",
|
|
45
|
+
"zh:b03ff5efdee310502aaaeb460144dc059bce72a0d8217e6b989099ef8aef9283",
|
|
46
|
+
]
|
|
47
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Retryable SES S3-action -> SNS (raw) -> SQS mailbox indexer.
|
|
2
|
+
|
|
3
|
+
Derived from markcallen/ses-receiving-terraform. Keep raw MIME immutable;
|
|
4
|
+
write deterministic per-envelope-recipient metadata only after S3 confirms it.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
import boto3
|
|
13
|
+
|
|
14
|
+
s3 = boto3.client("s3")
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
logger.setLevel(logging.INFO)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def index_notification(payload):
|
|
20
|
+
bucket = os.environ["BUCKET"]
|
|
21
|
+
domain = os.environ["DOMAIN"].lower()
|
|
22
|
+
if payload.get("Type") == "Notification":
|
|
23
|
+
payload = json.loads(payload["Message"])
|
|
24
|
+
if payload.get("notificationType") != "Received":
|
|
25
|
+
raise ValueError("Expected an SES receiving notification")
|
|
26
|
+
mail, receipt = payload["mail"], payload["receipt"]
|
|
27
|
+
message_id = mail["messageId"]
|
|
28
|
+
if not isinstance(message_id, str) or not re.fullmatch(r"[a-zA-Z0-9_-]{1,200}", message_id):
|
|
29
|
+
raise ValueError("Invalid SES message ID")
|
|
30
|
+
raw_key = f"incoming/{message_id}"
|
|
31
|
+
action = receipt["action"]
|
|
32
|
+
if action.get("type") != "S3" or action.get("bucketName") != bucket or action.get("objectKey") not in (message_id, raw_key):
|
|
33
|
+
raise ValueError("Unexpected SES storage action")
|
|
34
|
+
received_at = mail["timestamp"]
|
|
35
|
+
timestamp = datetime.fromisoformat(received_at.replace("Z", "+00:00"))
|
|
36
|
+
if timestamp.tzinfo is None:
|
|
37
|
+
raise ValueError("Receipt timestamp must include a timezone")
|
|
38
|
+
# A failed read raises, leaving the SQS record available for retry.
|
|
39
|
+
head = s3.head_object(Bucket=bucket, Key=raw_key)
|
|
40
|
+
if head["ContentLength"] > 16 * 1024 * 1024:
|
|
41
|
+
raise ValueError("Message exceeds Hail's 16 MiB limit")
|
|
42
|
+
delivery = {
|
|
43
|
+
name: receipt[name]["status"]
|
|
44
|
+
for name in ("spfVerdict", "dkimVerdict", "dmarcVerdict", "spamVerdict", "virusVerdict")
|
|
45
|
+
if isinstance(receipt.get(name), dict) and isinstance(receipt[name].get("status"), str)
|
|
46
|
+
}
|
|
47
|
+
recipients = receipt["recipients"]
|
|
48
|
+
if not isinstance(recipients, list):
|
|
49
|
+
raise ValueError("Expected envelope recipients")
|
|
50
|
+
for recipient in sorted(set(value.lower() for value in recipients)):
|
|
51
|
+
local, separator, recipient_domain = recipient.partition("@")
|
|
52
|
+
if separator != "@" or recipient_domain != domain:
|
|
53
|
+
continue
|
|
54
|
+
if not re.fullmatch(r"[a-z0-9][a-z0-9._+-]{0,63}", local):
|
|
55
|
+
continue
|
|
56
|
+
metadata = {
|
|
57
|
+
"schemaVersion": 1,
|
|
58
|
+
"messageId": message_id,
|
|
59
|
+
"recipient": recipient,
|
|
60
|
+
"receivedAt": received_at,
|
|
61
|
+
"rawKey": raw_key,
|
|
62
|
+
"delivery": delivery,
|
|
63
|
+
}
|
|
64
|
+
# Same key and body on retries. Never delete raw mail or emit S3 events.
|
|
65
|
+
s3.put_object(
|
|
66
|
+
Bucket=bucket,
|
|
67
|
+
Key=f"index/{recipient}/{message_id}.json",
|
|
68
|
+
Body=json.dumps(metadata, sort_keys=True).encode("utf-8"),
|
|
69
|
+
ContentType="application/json",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def handler(event, context):
|
|
74
|
+
failures = []
|
|
75
|
+
for record in event["Records"]:
|
|
76
|
+
identifier = record["messageId"]
|
|
77
|
+
try:
|
|
78
|
+
index_notification(json.loads(record["body"]))
|
|
79
|
+
except Exception as error:
|
|
80
|
+
# Do not log message bodies, recipients, subjects, or token-bearing URLs.
|
|
81
|
+
logger.error("Mail indexing failed (%s)", type(error).__name__)
|
|
82
|
+
failures.append({"itemIdentifier": identifier})
|
|
83
|
+
return {"batchItemFailures": failures}
|