Skip to content

Commit

Permalink
Merge pull request #778 from cisagov/AL-email-EC2
Browse files Browse the repository at this point in the history
Create EC2 instance for sending emails
  • Loading branch information
cduhn17 authored Jan 30, 2025
2 parents 5de44fe + 7789222 commit 2850294
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 0 deletions.
25 changes: 25 additions & 0 deletions infrastructure/email-sender-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash

# Create temporary directory for SSM Agent installation
sudo mkdir -p /tmp/ssm
cd /tmp/ssm || return

# Download and install the SSM Agent
wget https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/debian_amd64/amazon-ssm-agent.deb
sudo dpkg -i amazon-ssm-agent.deb
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
rm amazon-ssm-agent.deb

# Update packages
sudo apt-get update -y

# Install Python3 and pip
sudo apt-get install -y python3 python3-pip

# Install necessary Python libraries
pip3 install boto3 pandas

# Create working directory for email script
sudo mkdir -p /var/www/email_sender
sudo chmod -R 755 /var/www/email_sender
82 changes: 82 additions & 0 deletions infrastructure/email-sender.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

resource "aws_iam_role" "email_sender" {
count = var.create_email_sender_instance ? 1 : 0
name = "crossfeed-email-sender-${var.stage}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF

tags = {
Project = var.project
Stage = var.stage
Owner = "Crossfeed managed resource"
}
}

resource "aws_iam_instance_profile" "email_sender" {
count = var.create_email_sender_instance ? 1 : 0
name = "crossfeed-email-sender-${var.stage}"
role = aws_iam_role.email_sender[0].id
}

# Attach Policies to the Email EC2 Role
resource "aws_iam_policy_attachment" "email_sender_ec2_policy_1" {
count = var.create_email_sender_instance ? 1 : 0
name = "crossfeed-email-sender-${var.stage}"
roles = [aws_iam_role.email_sender[0].id, "AmazonSSMRoleForInstancesQuickSetup"]
policy_arn = "arn:${var.aws_partition}:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_policy_attachment" "email_sender_ec2_policy_2" {
count = var.create_email_sender_instance ? 1 : 0
name = "crossfeed-email-sender-${var.stage}"
roles = [aws_iam_role.email_sender[0].id]
policy_arn = "arn:${var.aws_partition}:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}

# EC2 Instance for SES
resource "aws_instance" "email_sender" {
count = var.create_email_sender_instance ? 1 : 0
ami = var.ami_id
instance_type = var.email_sender_instance_type
associate_public_ip_address = false

depends_on = [
aws_iam_instance_profile.email_sender[0],
aws_security_group.allow_internal,
aws_subnet.backend
]

tags = {
Project = var.project
Stage = var.stage
Name = "email_sender"
Owner = "Crossfeed managed resource"
}
root_block_device {
volume_size = 50
}

vpc_security_group_ids = [var.is_dmz ? aws_security_group.allow_internal[0].id : aws_security_group.allow_internal_lz[0].id]
subnet_id = var.is_dmz ? aws_subnet.backend[0].id : data.aws_ssm_parameter.subnet_db_1_id[0].value

iam_instance_profile = aws_iam_instance_profile.email_sender[0].name
user_data = file("./email-sender-install.sh")

lifecycle {
ignore_changes = [ami]
}

}
86 changes: 86 additions & 0 deletions infrastructure/emailSenderConnect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/bash

# Configuration
AWS_PROFILE=${EMAIL_AWS_PROFILE:-"default"}
INSTANCE_ID=${EMAIL_SENDER_INSTANCE_ID:-"your-instance-id"}
AVAILABILITY_ZONE="us-east-1b"
LOCAL_PORT=9995
REMOTE_PORT=22
SSH_USER="ubuntu"
SSH_KEY_PATH=${EMAIL_SSH_KEY_PATH:-""}

function log_info() {
echo "$(date '+%Y-%m-%d %H:%M:%S') INFO: $1"
}

function log_error() {
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: $1" >&2
}

# Check if the instance is running
function get_instance_status() {
aws ec2 describe-instance-status \
--instance-ids "$INSTANCE_ID" \
--profile "$AWS_PROFILE" \
--query 'InstanceStatuses[0].InstanceState.Name' \
--output text 2> /dev/null
}

# Start the instance if it's not running
function start_instance() {
log_info "Starting instance $INSTANCE_ID..."
aws ec2 start-instances \
--instance-ids "$INSTANCE_ID" \
--profile "$AWS_PROFILE" \
> /dev/null

log_info "Instance started. Waiting for initialization (2 minutes)..."
sleep 120
}

# Inject SSH Public Key using EC2 Instance Connect
function send_ssh_public_key() {
log_info "Sending SSH public key..."
if ! aws ec2-instance-connect send-ssh-public-key \
--instance-id "$INSTANCE_ID" \
--availability-zone "$AVAILABILITY_ZONE" \
--instance-os-user "$SSH_USER" \
--ssh-public-key "file://$SSH_KEY_PATH" \
--profile "$AWS_PROFILE"; then
log_error "Failed to send SSH public key."
exit 1
fi
}

# Start port forwarding with AWS SSM
function start_port_forwarding() {
log_info "Starting port forwarding via SSM..."
aws ssm start-session \
--target "$INSTANCE_ID" \
--document-name AWS-StartPortForwardingSession \
--parameters "{\"portNumber\":[\"$REMOTE_PORT\"], \"localPortNumber\":[\"$LOCAL_PORT\"]}" \
--profile "$AWS_PROFILE"
}

# Main script logic
log_info "Starting EC2 connection process..."
if [ -z "$INSTANCE_ID" ]; then
log_error "INSTANCE_ID is not set. Please set it as an environment variable or update the script."
exit 1
fi

STATUS=$(get_instance_status | tr -d '\r')

log_info "Current instance status: $STATUS"

if [[ "$STATUS" == "running" ]]; then
log_info "Instance is already running."
elif [[ "$STATUS" == "stopped" || "$STATUS" == "stopping" ]]; then
start_instance
else
log_error "Unexpected instance status: $STATUS"
exit 1
fi

send_ssh_public_key
start_port_forwarding
2 changes: 2 additions & 0 deletions infrastructure/integration.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,5 @@ ssm_redshift_user = "/crossfeed/integration/REDSHIFT_USER"
ssm_redshift_password = "/crossfeed/integration/REDSHIFT_PASSWORD"
create_elasticache_cluster = true
matomo_availability_zone = "us-east-1a"
create_email_sender_instance = false
email_sender_instance_type = "t3.small"
2 changes: 2 additions & 0 deletions infrastructure/prod.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,5 @@ ssm_cf_api_key = "/crossfeed/prod/CF_API_KEY"
ssm_intelx_api_key = "/crossfeed/prod/INTELX_API_KEY"
ssm_xpanse_api_key = "/crossfeed/prod/XPANSE_API_KEY"
ssm_xpanse_auth_id = "/crossfeed/prod/XPANSE_AUTH_ID"
create_email_sender_instance = false
email_sender_instance_type = "t3.small"
2 changes: 2 additions & 0 deletions infrastructure/stage-cd.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ ssm_redshift_database = "/crossfeed/staging/REDSHIFT_DATABASE"
ssm_redshift_user = "/crossfeed/staging/REDSHIFT_USER"
ssm_redshift_password = "/crossfeed/staging/REDSHIFT_PASSWORD"
create_elasticache_cluster = true
create_email_sender_instance = true
email_sender_instance_type = "t3.small"
2 changes: 2 additions & 0 deletions infrastructure/stage.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,5 @@ ssm_redshift_password = "/crossfeed/staging/REDSHIFT_PASSWORD"
ssm_pe_api_key = "/crossfeed/staging/PE_API_KEY"
ssm_cf_api_key = "/crossfeed/staging/CF_API_KEY"
create_elasticache_cluster = true
create_email_sender_instance = false
email_sender_instance_type = "t3.small"
12 changes: 12 additions & 0 deletions infrastructure/vars.tf
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,18 @@ variable "create_db_accessor_instance" {
default = false
}

variable "create_email_sender_instance" {
description = "Whether to create a email sending EC2 instance. This instance can be used to access AWS SES and is spun up in a private subnet. It can be accessed using AWS Systems Manager Session Manager."
type = bool
default = false
}

variable "email_sender_instance_type" {
description = "Instance type of the email sender instance."
type = string
default = false
}

variable "db_accessor_instance_class" {
description = "db_accessor_instance_class"
type = string
Expand Down

0 comments on commit 2850294

Please sign in to comment.