浏览代码

Initial mail relay module, includes SES setup in us-gov-west-1

Fred Damstra 4 年之前
父节点
当前提交
32d6bf37ae

+ 1 - 0
base/mailrelay/amis.tf

@@ -0,0 +1 @@
+../amis.tf

+ 74 - 0
base/mailrelay/cloud-init/cloud_init.tpl

@@ -0,0 +1,74 @@
+#cloud-config
+preserve_hostname: false
+hostname: ${hostname}
+salt-master: ${salt_master}
+fqdn: ${fqdn}
+
+# Write files happens early
+write_files:
+- content: |
+    proxy=http://${proxy}:80
+  path: /etc/yum.conf
+  append: true
+- content: |
+    [global]
+    proxy=${proxy}
+  path: /etc/pip.conf
+- content: |
+    export HTTPS_PROXY=http://${proxy}:80
+    export HTTP_PROXY=http://${proxy}:80
+    export NO_PROXY=localhost,127.0.0.1,169.254.169.254,pvt.xdrtest.accenturefederalcyber.com,pvt.xdr.accenturefederalcyber.com,reposerver.msoc.defpoint.local,jenkins.msoc.defpoint.local,pod1search-splunk-sh.msoc.defpoint.local,s3.amazonaws.com,ssm.${ aws_region }.amazonaws.com,ec2messages.${ aws_region }.amazonaws.com,ec2.${ aws_region }.amazonaws.com,ssmmessages.${ aws_region }.amazonaws.com,iratemoses.mdr.defpoint.com,jira.mdr.defpoint.com,reposerver.pvt.xdr.accenturefederalcyber.com,jenkins.pvt.xdr.accenturefederalcyber.com,pod1search-splunk-sh.pvt.xdr.accenturefederalcyber.com,reposerver.pvt.xdrtest.accenturefederalcyber.com,jenkins.pvt.xdrtest.accenturefederalcyber.com,pod1search-splunk-sh.pvt.xdrtest.accenturefederalcyber.com,iratemoses.xdr.accenturefederalcyber.com,jira.xdr.accenturefederalcyber.com,iratemoses.xdrtest.accenturefederalcyber.com,jira.xdrtest.accenturefederalcyber.com
+    export https_proxy=$HTTPS_PROXY
+    export http_proxy=$HTTP_PROXY
+    export no_proxy=$NO_PROXY
+  path: /etc/profile.d/proxy.sh
+- content: |
+    ${fqdn}
+  path: /etc/salt/minion_id
+- content: |
+    master: ${salt_master}
+  path: /etc/salt/minion
+- content: |
+    grains:
+      environment: ${ environment }
+      aws_partition: ${ aws_partition }
+      aws_partition_alias: ${ aws_partition_alias }
+  path: /etc/salt/minion.d/cloud_init_grains.conf
+
+#yum_repos:
+#  epel-release:
+#    baseurl: http://download.fedoraproject.org/pub/epel/7/$basearch
+#    enabled: false
+#    failovermethod: priority
+#    gpgcheck: true
+#    gpgkey: http://download.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7
+#    name: Extra Packages for Enterprise Linux 7 - Release
+
+packages:
+ - vim
+
+package_update: true # Always patch
+
+growpart:
+  mode: auto
+  devices: [ '/', '/var', '/var/log', '/var/log/audit', '/var/tmp', '/tmp', '/home' ]
+  ignore_growroot_disabled: false
+
+runcmd:
+# Standard stuff
+ - /bin/systemctl restart salt-minion
+ - /bin/systemctl enable salt-minion
+ - /bin/systemctl start amazon-ssm-agent
+ - /bin/systemctl enable amazon-ssm-agent
+ - /usr/sbin/aide --update --verbose=0
+ - /bin/cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
+
+
+# Either final message or power state, but probably not both
+final_message: "The system is up after $UPTIME seconds"
+#power_state:
+#  delay: "+30"
+#  mode: reboot
+#  message: "System configured after $UPTIME seconds"
+#  timeout: 300
+#  condition: true

+ 214 - 0
base/mailrelay/main.tf

@@ -0,0 +1,214 @@
+# Some instance variables
+locals {
+  ami_selection = "minion" # master, minion, ...
+}
+
+# Rather than pass in the aws security group, we just look it up. This will
+# probably be useful other places, as well.
+data "aws_security_group" "typical-host" {
+  name   = "typical-host"
+  vpc_id = var.vpc_id
+}
+
+# Use the default EBS key
+data "aws_kms_key" "ebs-key" {
+  key_id = "alias/ebs_root_encrypt_decrypt"
+}
+
+resource "aws_network_interface" "instance" {
+  subnet_id = var.subnets[0]
+  security_groups = [ data.aws_security_group.typical-host.id, aws_security_group.mailrelay_security_group.id ]
+  description = var.instance_name
+  tags = merge(var.standard_tags, var.tags, { Name = var.instance_name })
+}
+
+resource "aws_instance" "instance" {
+  tenancy = "default"
+  ebs_optimized = true
+  disable_api_termination = var.instance_termination_protection
+  instance_initiated_shutdown_behavior = "stop"
+  instance_type = var.instance_type
+  key_name = "msoc-build"
+  monitoring = false
+  iam_instance_profile = "msoc-default-instance-profile"
+
+  ami = local.ami_map[local.ami_selection]
+  # We need to ignore ebs_block_device changes, because if the AMI changes, so does the snapshot_id.
+  # If they add a feature to block more specific changes (eg `ebs_block_devices[*].snapshot_id`), then
+  # that could be removed.
+  lifecycle { ignore_changes = [ ami, key_name, user_data, ebs_block_device ] }
+
+  # These device definitions are optional, but added for clarity.
+  root_block_device {
+      volume_type = "gp2"
+      #volume_size = "60"
+      delete_on_termination = true
+      encrypted = true
+      kms_key_id = data.aws_kms_key.ebs-key.arn
+  }
+
+  ebs_block_device {
+    # swap
+    device_name = "/dev/xvdm"
+    #volume_size = 48
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    # Snapshot IDs need to be grabbed from the ami, or it will replace every time. It's ugly.
+    # This may prompt replacement when the AMI is updated.
+    # See:
+    #   https://github.com/hashicorp/terraform/issues/19958
+    #   https://github.com/terraform-providers/terraform-provider-aws/issues/13118
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvdm"].ebs.snapshot_id
+  }
+  ebs_block_device {
+    # /home
+    device_name = "/dev/xvdn"
+    # volume_size = xx
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvdn"].ebs.snapshot_id
+
+  }
+  ebs_block_device {
+    # /var
+    device_name = "/dev/xvdo"
+    # volume_size = xx
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvdo"].ebs.snapshot_id
+  }
+  ebs_block_device {
+    # /var/tmp
+    device_name = "/dev/xvdp"
+    # volume_size = xx
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvdp"].ebs.snapshot_id
+  }
+  ebs_block_device {
+    # /var/log
+    device_name = "/dev/xvdq"
+    # volume_size = xx
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvdq"].ebs.snapshot_id
+  }
+  ebs_block_device {
+    # /var/log/audit
+    device_name = "/dev/xvdr"
+    # volume_size = xx
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvdr"].ebs.snapshot_id
+  }
+  ebs_block_device {
+    # /tmp
+    device_name = "/dev/xvds"
+    # volume_size = xx
+    delete_on_termination = true
+    encrypted = true
+    kms_key_id = data.aws_kms_key.ebs-key.arn
+    snapshot_id = local.block_device_mappings[local.ami_selection]["/dev/xvds"].ebs.snapshot_id
+  }
+
+  network_interface {
+    device_index = 0
+    network_interface_id = aws_network_interface.instance.id
+  }
+
+  user_data = data.template_cloudinit_config.cloud_init_config.rendered
+  tags = merge( var.standard_tags, var.tags, { Name = var.instance_name })
+  volume_tags = merge( var.standard_tags, var.tags, { Name = var.instance_name })
+}
+
+module "private_dns_record" {
+  source = "../../submodules/dns/private_A_record"
+
+  name = var.instance_name
+  ip_addresses = [ aws_instance.instance.private_ip ]
+  dns_info = var.dns_info
+  reverse_enabled = var.reverse_enabled
+
+  providers = {
+    aws.c2 = aws.c2
+  }
+}
+
+#The Cloud init data is to prepare the instance for use. 
+data "template_file" "cloud_init" {
+  # Should these be in a common directory? I suspect they'd be reusable
+  template = "${file("${path.module}/cloud-init/cloud_init.tpl")}"
+
+  vars = {
+    hostname = var.instance_name
+    fqdn = "${var.instance_name}.${var.dns_info["private"]["zone"]}"
+    environment = var.environment
+    salt_master  = var.salt_master
+    proxy = var.proxy
+    aws_partition = var.aws_partition
+    aws_partition_alias = var.aws_partition_alias
+    aws_region = var.aws_region
+  }
+}
+
+# Render a multi-part cloud-init config making use of the part
+# above, and other source files
+data "template_cloudinit_config" "cloud_init_config" {
+  gzip          = true
+  base64_encode = true
+
+  # Main cloud-config configuration file.
+  part {
+    filename     = "init.cfg"
+    content_type = "text/cloud-config"
+    content      = data.template_file.cloud_init.rendered
+  }
+
+  #  part {
+  #  content_type = "text/cloud-boothook"
+  #  content      = file("${path.module}/cloud-init/repo_server_volumes.boothook")
+  #}
+}
+
+resource "aws_security_group" "mailrelay_security_group" {
+  name = "mailrelay_security_group"
+  description = "Security Group for the Mail Relay Server(s)"
+  vpc_id = var.vpc_id
+  tags = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_security_group_rule" "smtp-in" {
+  description = "inbound smtp requests"
+  type = "ingress"
+  from_port = 25
+  to_port = 25
+  protocol = "tcp"
+  cidr_blocks = [ "10.0.0.0/8" ]
+  security_group_id = aws_security_group.mailrelay_security_group.id
+}
+
+#resource "aws_security_group_rule" "smtp-out" {
+#  description = "outbound smtp requests"
+#  type = "egress"
+#  from_port = 25
+#  to_port = 25
+#  protocol = "tcp"
+#  cidr_blocks = [ "10.0.0.0/8" ]
+#  security_group_id = aws_security_group.mailrelay_security_group.id
+#}
+
+resource "aws_security_group_rule" "submission-out" {
+  description = "outbound submission (smtp-s) requests"
+  type = "egress"
+  from_port = 587
+  to_port = 587
+  protocol = "tcp"
+  cidr_blocks = [ "10.0.0.0/8" ]
+  security_group_id = aws_security_group.mailrelay_security_group.id
+}

+ 7 - 0
base/mailrelay/outputs.tf

@@ -0,0 +1,7 @@
+output instance_arn {
+  value = aws_instance.instance.arn
+}
+
+output instance_private_ip {
+  value = aws_instance.instance.private_ip
+}

+ 242 - 0
base/mailrelay/ses.tf

@@ -0,0 +1,242 @@
+######################
+# Access keys
+#
+# For rotation purposes, there are two of these. Delete the oldest one,
+# add a new one (with a higher version number), and then update the output.
+
+# ses_user
+resource "aws_iam_access_key" "ses_access_key-v0" {
+  user     = aws_iam_user.ses_user.name
+}
+
+resource "aws_iam_access_key" "ses_access_key-v1" {
+  user     = aws_iam_user.ses_user.name
+}
+
+# This just muddies the output, but is good for troubleshooting, so I'm just
+# commenting it out.
+#output ses_user_access_keys {
+#  value = {
+#    "current" = {
+#      "aws_access_key_id": aws_iam_access_key.ses_access_key-v1.id
+#      "aws_secret_access_key": aws_iam_access_key.ses_access_key-v1.secret
+#    },
+#    "previous" = {
+#      "aws_access_key_id": aws_iam_access_key.ses_access_key-v0.id
+#      "aws_secret_access_key": aws_iam_access_key.ses_access_key-v0.secret
+#    }
+#  }
+#}
+
+output ses_user_smtp_username {
+  value = aws_iam_access_key.ses_access_key-v1.id
+}
+
+output ses_user_smtp_password { 
+  value = aws_iam_access_key.ses_access_key-v1.ses_smtp_password_v4
+}
+
+
+# dps_portal
+resource "aws_iam_access_key" "dps_portal_key-v0" {
+  user     = aws_iam_user.dps_portal.name
+}
+
+resource "aws_iam_access_key" "dps_portal_key-v1" {
+  user     = aws_iam_user.dps_portal.name
+}
+
+# This just muddies the output, but is good for troubleshooting, so I'm just
+# commenting it out.
+#output dps_portal_access_keys {
+#  value = {
+#    "current" = {
+#      "aws_access_key_id": aws_iam_access_key.dps_portal_key-v1.id
+#      "aws_secret_access_key": aws_iam_access_key.dps_portal_key-v1.secret
+#    },
+#    "previous" = {
+#      "aws_access_key_id": aws_iam_access_key.dps_portal_key-v0.id
+#      "aws_secret_access_key": aws_iam_access_key.dps_portal_key-v0.secret
+#    }
+#  }
+#}
+
+output dps_portal_smtp_username {
+  value = aws_iam_access_key.dps_portal_key-v1.id
+}
+
+output dps_portal_smtp_password { 
+  value = aws_iam_access_key.dps_portal_key-v1.ses_smtp_password_v4
+}
+
+######################
+# SES Domain
+
+resource "aws_ses_domain_identity" "public" {
+  domain = var.dns_info["public"]["zone"]
+  provider = aws.ses
+}
+
+resource "aws_route53_record" "amazonses_verification_record" {
+  zone_id = var.dns_info["public"]["zone_id"]
+  name    = "_amazonses"
+  type    = "TXT"
+  ttl     = "600"
+  records = [ aws_ses_domain_identity.public.verification_token ]
+  provider = aws.mdr-common-services-commercial
+}
+
+resource "aws_ses_domain_identity_verification" "ses_verification" {
+  domain     = aws_ses_domain_identity.public.id
+  depends_on = [
+    aws_route53_record.amazonses_verification_record,
+    aws_route53_record.amazonses_dkim_record,
+    aws_route53_record.ses_spf_record,
+    aws_route53_record.ses_domain_mail_from_mx,
+  ]
+  provider = aws.ses
+}
+
+######################
+# DKIM
+
+resource "aws_ses_domain_dkim" "public" {
+  domain = aws_ses_domain_identity.public.domain
+  provider = aws.ses
+}
+
+resource "aws_route53_record" "amazonses_dkim_record" {
+  count   = 3
+  zone_id = var.dns_info["public"]["zone_id"]
+  name    = "${element(aws_ses_domain_dkim.public.dkim_tokens, count.index)}._domainkey"
+  type    = "CNAME"
+  ttl     = "600"
+  records = [ "${element(aws_ses_domain_dkim.public.dkim_tokens, count.index)}.dkim.amazonses.com" ]
+  provider = aws.mdr-common-services-commercial
+}
+
+######################
+# SPF
+
+resource "aws_route53_record" "ses_spf_record" {
+  zone_id = var.dns_info["public"]["zone_id"]
+  name    = "@"
+  type    = "TXT"
+  ttl     = "600"
+  records = ["v=spf1 include:amazonses.com -all"]
+  provider = aws.mdr-common-services-commercial
+}
+
+######################
+# MAIL FROM
+
+resource "aws_ses_domain_mail_from" "public" {
+  domain           = aws_ses_domain_identity.public.domain
+  mail_from_domain = "bounce.${aws_ses_domain_identity.public.domain}"
+  provider = aws.ses
+}
+
+######################
+# MX for MAIL FROM
+resource "aws_route53_record" "ses_domain_mail_from_mx" {
+  zone_id = var.dns_info["public"]["zone_id"]
+  name    = aws_ses_domain_mail_from.public.mail_from_domain
+  type    = "MX"
+  ttl     = "600"
+  records = ["10 feedback-smtp.${var.aws_partition}.amazonses.com"] 
+  provider = aws.mdr-common-services-commercial
+}
+
+#-----------------------------------------------
+# IAM user for smtp auth
+#-----------------------------------------------
+resource "aws_iam_user" "ses_user" {
+  name     = "ses_user"
+  path     = "/service_accounts/"
+}
+
+resource "aws_iam_user_policy" "ses_user" {
+  name     = "ses_user_policy"
+  user     = aws_iam_user.ses_user.name
+
+  policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": [
+        "ses:SendRawEmail"
+      ],
+      "Effect": "Allow",
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+}
+
+#-----------------------------------------------
+# IAM user for smtp auth for dps-portal
+#-----------------------------------------------
+resource "aws_iam_user" "dps_portal" {
+  name     = "dps_portal"
+  path     = "/service_accounts/"
+}
+
+resource "aws_iam_user_policy" "dps_portal" {
+  name     = "dps_portal_policy"
+  user     = aws_iam_user.dps_portal.name
+
+  policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": [
+        "ses:SendRawEmail"
+      ],
+      "Effect": "Allow",
+      "Resource": "*"
+    }
+  ]
+}
+EOF
+}
+
+#------------------------------------
+# SNS topic for bounce notifications
+#------------------------------------
+resource "aws_sns_topic" "bounces" {
+  name = "ses-notifications"
+  provider = aws.ses
+}
+
+resource "aws_ses_identity_notification_topic" "bounce_notification" {
+  topic_arn         = aws_sns_topic.bounces.arn
+  notification_type = "Bounce"
+  identity          = aws_ses_domain_identity.public.domain
+  provider          = aws.ses
+}
+
+resource "aws_ses_identity_notification_topic" "complaint_notification" {
+  topic_arn         = aws_sns_topic.bounces.arn
+  notification_type = "Complaint"
+  identity          = aws_ses_domain_identity.public.domain
+  provider          = aws.ses
+}
+#-----------------------------------------------
+# For DPS portal, needs SES connectivity
+#-----------------------------------------------
+#module "ses_user_for_portal" {
+#  source = "../modules/ses_iam_account"
+#  username = "dps_portal"
+#  pgp_key  = "${path.module}/../../common/duane_waddle.pgp"
+#}
+#
+#output portal_ses_username {
+#  value = "${module.ses_user_for_portal.username}"
+#}
+#
+#output portal_ses_password {
+#  value = "${module.ses_user_for_portal.password}"
+#}

+ 52 - 0
base/mailrelay/vars.tf

@@ -0,0 +1,52 @@
+variable "instance_name" {
+  description = "Hostname, DNS entry, etc."
+  type = string
+}
+
+variable "azs" {
+  type = list(string)
+}
+
+variable "subnets" {
+  type = list(string)
+}
+
+variable "vpc_id" {
+  type = string
+}
+
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+
+variable "instance_type" { 
+  type = string
+  default = "t3a.micro"
+}
+
+variable "reverse_enabled" { 
+  description = "Whether to create the reverse DNS entry."
+  type = bool
+  default = true
+}
+
+variable "trusted_ips" { type = list(string) }
+variable "xdr_interconnect" { type = list(string) }
+variable "nga_pop" { type = list(string) }
+variable "afs_azure_pop" { type = list(string) }
+variable "afs_pop" { type = list(string) }
+variable "proxy" { type = string }
+variable "salt_master" { type = string }
+
+variable "cidr_map" { type = map }
+variable "dns_info" { type = map }
+variable "standard_tags" { type = map }
+variable "environment" { type = string }
+variable "aws_region" { type = string }
+variable "aws_partition" { type = string }
+variable "aws_partition_alias" { type = string }
+variable "aws_account_id" { type = string }
+variable "common_services_account" { type = string }
+variable "instance_termination_protection" { type = bool }

+ 3 - 0
base/mailrelay/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.13"
+}

+ 1 - 1
base/standard_vpc/main.tf

@@ -33,7 +33,7 @@ module "vpc" {
       "${cidrsubnet(var.vpc_info["cidr"],3,6)}",
   ]
 
-  enable_nat_gateway = false
+  enable_nat_gateway = var.enable_nat_gateway
   enable_vpn_gateway = false
   enable_dns_hostnames = true
   enable_dhcp_options = true

+ 6 - 0
base/standard_vpc/vars.tf

@@ -8,6 +8,12 @@ variable "tgw_id" {
   type = string
 }
 
+variable "enable_nat_gateway" {
+  description = "Whether to add a NAT gateway to the VPC"
+  type = bool
+  default = false
+}
+
 variable "tgw_share_arn" {
   description = "The ARN of the share to accept."
   type        = string