Skip to content

RussellGilmore/red-instance

Repository files navigation

Red Instance

Red EC2 Module

A practical EC2 module — SSM-only by design. No SSH keys are created, stored, or attached; you reach the instance through AWS Session Manager.

Requirements:

  1. Terraform >= 1.15.0
  2. Trivy >= 0.68.2

Trivy can be installed via Homebrew on macOS with the command:

brew install aquasecurity/trivy/trivy

Security posture

  • No SSH key pair — access is via SSM Session Manager. No private key is generated, written to disk, or stored in Terraform state.
  • IMDSv2 required; root EBS volume encrypted
  • Instance role limited to AmazonSSMManagedInstanceCore plus an optional bucket-scoped S3 policy
  • Ingress and egress are both caller-controlled. Ingress opens only the ports you pass (examples open 80/443, never 22). Egress defaults to open because SSM and OS updates need it, and can be narrowed via egress_rules.
  • Scanned with Trivy and gitleaks; integration-tested with Terratest across both networking paths (module-created and caller-supplied)

Features

  • Provisions an EC2 instance accessed exclusively via AWS SSM Session Manager — no SSH key pair is created or attached
  • IMDSv2 required and root EBS volume encrypted by default
  • Dynamically creates ingress security group rules from a caller-supplied list
  • Configurable egress rules, defaulting to the outbound access SSM and OS updates require
  • Optionally provisions a minimal public VPC, or drops into an existing network you supply
  • Optionally creates a public Route 53 DNS record for the instance
  • Optionally passes a user data script into instance creation
  • Optionally attaches a bucket-scoped S3 access policy to the instance role

Usage

Self-contained (creates a minimal public VPC) — see examples/complete:

provider "aws" {
  region = "us-east-1"
}

module "instance" {
  source = "RussellGilmore/red-instance/aws"

  project_name  = "my-project"
  instance_name = "web"

  ingress_rules = [{
    description = "HTTPS from anywhere"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }]
}

Connect with: aws ssm start-session --target <instance_id>.

To place the instance in an existing network (e.g. red-network), set create_vpc = false and pass vpc_id/subnet_id — see examples/with-network.

To use with red-network, set create_vpc = false and pass vpc_id/subnet_id — below is an example configuration.

variable "region" {
  type    = string
  default = "us-east-1"
}

variable "project_name" {
  type    = string
  default = "red-networked"
}

provider "aws" {
  region = var.region
}

module "network" {
  source  = "RussellGilmore/red-network/aws"
  version = "~> 3.0"

  project_name = var.project_name
  vpc_name     = "${var.project_name}-vpc"
  vpc_cidr     = "10.0.0.0/16"

  subnets = {
    public-1a = {
      name              = "${var.project_name}-public-1a"
      cidr_block        = "10.0.1.0/24"
      availability_zone = "${var.region}a"
      type              = "public"
    }
  }
}

module "red_instance" {
  source  = "RussellGilmore/red-instance/aws"
  version = "~> 3.0"

  project_name  = var.project_name
  instance_name = "red-networked"

  # Consume the network red-network built.
  create_vpc = false
  vpc_id     = module.network.vpc_id
  subnet_id  = module.network.public_subnet_ids[0]

  # SSM-only access; open just the public service ports.
  ingress_rules = [
    {
      description = "HTTPS from anywhere"
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  ]

  # egress_rules is omitted, so it defaults to unrestricted outbound
  # (required for SSM connectivity and OS updates).

  additional_tags = {
    Environment = "example"
  }
}

output "instance_id" {
  value = module.red_instance.instance_id
}

output "public_ip" {
  value = module.red_instance.public_ip
}

output "vpc_id" {
  value = module.red_instance.vpc_id
}

Requirements

Name Version
terraform >= 1.15.0
aws >= 6.47.0

Providers

Name Version
aws >= 6.47.0

Modules

No modules.

Resources

Name Type
aws_eip.red_instance_eip resource
aws_iam_instance_profile.red_instance_profile resource
aws_iam_role.red_role resource
aws_iam_role_policy.s3_bucket_policy resource
aws_iam_role_policy_attachment.red_ssm_policy_attachment resource
aws_instance.red-instance resource
aws_internet_gateway.igw resource
aws_route53_record.red_instance_dns resource
aws_route_table.public resource
aws_route_table_association.public resource
aws_security_group.red_sg resource
aws_subnet.public resource
aws_vpc.main resource
aws_ami.red_ami data source
aws_route53_zone.zone data source

Inputs

Name Description Type Default Required
additional_tags Additional tags to apply to all resources created by this module. map(string) {} no
allocate_eip Controls whether an Elastic IP should be allocated. bool true no
ami_name The name (or name pattern) of the AMI to use for the instance. string "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-arm64-server-20250610" no
ami_owner The owner account ID of the AMI. string "099720109477" no
apex_domain Apex domain whose hosted zone holds the DNS record. string "" no
availability_zone Availability zone for the created subnet. Leave empty for default placement. string "" no
create_vpc Create a minimal public VPC (single subnet + IGW) for the instance. Set false to place the instance in an existing VPC via vpc_id/subnet_id. bool true no
disable_api_stop Controls whether API stop is disabled. bool false no
disable_api_termination Controls whether API termination is disabled. bool false no
dns_name FQDN for the public DNS record. string "" no
egress_rules List of egress rules for the instance security group. Defaults to unrestricted outbound, which SSM Session Manager and OS package updates require. Narrow this if your environment provides SSM VPC endpoints.
list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
[
{
"cidr_blocks": [
"0.0.0.0/0"
],
"description": "Allow all outbound traffic",
"from_port": 0,
"protocol": "-1",
"to_port": 0
}
]
no
enable_public_dns Create a public Route53 A record pointing at the instance's EIP. bool false no
enable_s3_bucket_policy Attach an S3 access policy (scoped to s3_bucket_name) to the instance role. bool false no
ingress_rules List of ingress rules for the instance security group. Access is via SSM Session Manager by default; only open inbound ports you actually serve (e.g. 80/443).
list(object({
description = string
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
n/a yes
instance_name The name of the instance. string n/a yes
instance_tags Tags to apply only to the EC2 instance resource. map(string) {} no
instance_type The instance type to use for the instance. string "t4g.small" no
project_name Project name used for naming and the Project tag. string n/a yes
s3_bucket_name Name of the S3 bucket the instance role may access. string "" no
subnet_id ID of an existing subnet to use when create_vpc is false. string "" no
user_data_script_path Path to a user data script to run on first boot. string "" no
volume_size The size of the root volume in GB. number 30 no
vpc_id ID of an existing VPC to use when create_vpc is false. string "" no

Outputs

Name Description
instance_id The ID of the EC2 instance.
public_dns The public DNS name of the instance.
public_ip The public IP address of the instance (EIP when allocated).
security_group_id The ID of the instance security group.
subnet_id The subnet ID the instance is deployed in — created by the module when create_vpc is true, or the supplied subnet_id when false.
vpc_id The VPC ID the instance is deployed in — created by the module when create_vpc is true, or the supplied vpc_id when false.

About

A simple standalone AWS EC2 Terraform infrastructure module that can deployed in preexisiting network infrastructure or can setup it's own public network configuration.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages