EC2
Note: This documentation is also available in a rendered format here.
Deploys secure EC2 instances with KMS-encrypted EBS volumes, managed key pairs stored in Secrets Manager, configurable security groups, and CloudFormation Init bootstrap configurations for both Linux and Windows. Common scenarios include deploying bastion hosts, DataSync agents, database clients, or other utility compute that your data environment requires within a VPC.
Deployed Resources
This module deploys and integrates the following resources:
- KMS CMK: Customer-managed KMS key created if an existing key is not provided. Used to encrypt instance EBS volumes and key pair secrets.
- EC2 Key Pairs: Created for use by EC2 instances, with private key material stored in Secrets Manager. Key pairs and secrets are retained post stack deletion.
- EC2 Security Groups: Controls network access for instances. Supports CIDR, prefix list, and security group-based rules.
- EC2 Instances: Instances with termination protection enabled and retained post stack deletion. AMI-configured volumes should be accounted for in config to support encryption.
- CloudFormation Init: Bootstrap configurations for package installation, file creation, command execution, and service management on both Linux and Windows instances.

Related Modules
- Roles — Create IAM roles for EC2 instance profiles
- DataSync — Deploy DataSync agents on EC2 instances for data transfer
Security/Compliance Details
This module is designed in alignment with MDAA security/compliance principles and CDK nag rulesets. Additional review is recommended prior to production deployment, ensuring organization-specific compliance requirements are met.
- Encryption at Rest:
- All EBS volumes encrypted with customer-managed KMS key
- Key pair private keys encrypted in Secrets Manager with the same KMS key
- Least Privilege:
- Admin roles granted scoped KMS key admin/usage permissions and Secrets Manager access for key pair retrieval
- Instance profiles use dedicated IAM roles
- Data Protection:
- Termination protection enabled by default
- Key pairs and secrets retained post stack deletion
- Network Isolation:
- Security groups deny all ingress by default
- All egress allowed by default (configurable)
- Egress rules configurable with CIDR, prefix list, and security group targets
AWS Service Endpoints
The following VPC endpoints may be required if public AWS service endpoint connectivity is unavailable (e.g., private subnets without NAT gateway, firewalled environments, or PrivateLink-only architectures):
| AWS Service | Endpoint Service Name | Type |
|---|---|---|
| EC2 | com.amazonaws.{region}.ec2 |
Interface |
| EC2 Messages | com.amazonaws.{region}.ec2messages |
Interface |
| KMS | com.amazonaws.{region}.kms |
Interface |
| Secrets Manager | com.amazonaws.{region}.secretsmanager |
Interface |
| CloudWatch Logs | com.amazonaws.{region}.logs |
Interface |
| STS | com.amazonaws.{region}.sts |
Interface |
| SSM | com.amazonaws.{region}.ssm |
Interface |
| SSM Messages | com.amazonaws.{region}.ssmmessages |
Interface |
| S3 | com.amazonaws.{region}.s3 |
Gateway |
Configuration
MDAA Config
Add the following snippet to your mdaa.yaml under the modules: section of a domain/env in order to use this module:
ec2: # Module Name can be customized
module_path: '@aws-mdaa/ec2' # Must match module NPM package name
module_configs:
- ./ec2.yaml # Filename/path can be customized
Module Config Samples and Variants
Copy the contents of the relevant sample config below into the ./ec2.yaml file referenced in the MDAA config snippet above.
Minimal Configuration
Deploys a single EC2 instance with a security group. Start here for a basic instance deployment with default encryption and termination protection.
# Contents available via above link
# Minimal EC2 module configuration.
# Deploys a single EC2 instance with a security group.
# See CONFIGURATION.md for role reference options (name, arn, id).
# Roles granted access to the KMS key and KeyPair secrets
adminRoles:
- name: Admin
# (Optional) Security group for the instance
securityGroups:
my-sg:
# VPC ID for security group
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/vpc/id
vpcId: vpc-testvpc
# (Optional) EC2 instances — the module's primary resource.
instances:
my-instance:
securityGroup: my-sg
# VPC ID for EC2 instance deployment
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/vpc/id
vpcId: vpc-testvpc
# Subnet ID for EC2 instance placement
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/subnet/id
subnetId: subnet-testsubnet
availabilityZone: '{{region}}a'
instanceType: t3.medium
amiId: ami-test
instanceRole:
name: instance-role
blockDevices:
- deviceName: '/dev/sda1'
volumeSizeInGb: 32
ebsType: gp3
osType: linux
Comprehensive Configuration
Provisions EC2 instances with key pairs, security groups, and CloudFormation Init bootstrapping, supporting both Linux and Windows instances with user data scripts and cfnInit configurations. Start here when evaluating all available options for key pairs, security group rules, cfnInit bootstrapping, and multi-OS support.
sample-config-comprehensive.yaml
# Contents available via above link
# EC2 module configuration.
# Provisions EC2 instances with key pairs, security groups, and
# CloudFormation Init bootstrapping. Supports both Linux and Windows
# instances with user data scripts and cfnInit configurations.
# This comprehensive config exercises every compatible property at
# full depth.
# See CONFIGURATION.md for role reference options (name, arn, id).
# Roles granted access to the KMS key and KeyPair secrets.
# Roles can be referenced by name, arn, or id.
adminRoles:
- name: Admin
- arn: arn:{{partition}}:iam::{{account}}:role/some-admin-role
- name: EC2Admin
# (Optional) Map of key pair names to key pair configurations.
# Private keys are stored in Secrets Manager.
keyPairs:
# Key pair with default settings
test-key-pair: {}
# Key pair with custom KMS encryption
test-key-pair2:
# (Optional) KMS key ARN to encrypt the private key in
# Secrets Manager
kmsKeyArn: 'arn:{{partition}}:kms:{{region}}:{{account}}:key/test-key'
# (Optional) Map of security group names to configurations
securityGroups:
sg1:
# VPC ID for the security group
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/vpc/id
vpcId: vpc-testvpc
# (Optional) Add bidirectional self-referencing rule allowing
# instances in this group to communicate with each other
addSelfReferenceRule: true
# (Optional) Inbound traffic rules
ingressRules:
# Rules for IPv4 CIDR-based ingress
ipv4:
- cidr: 10.0.0.0/28
port: 443
protocol: tcp
# (Optional) Description for the rule
description: HTTPS from internal subnet
# (Optional) Ending port for a port range
toPort: 443
# Rules for prefix list-based ingress
prefixList:
- prefixList: pl-4ea54027
description: prefix list for DynamoDB endpoint
protocol: tcp
port: 443
# (Optional) Ending port for a port range
toPort: 443
# Rules for security group-based ingress
sg:
- sgId: sg-ingresssource
port: 8080
protocol: tcp
# (Optional) Description for the rule
description: Ingress from app tier SG
# (Optional) Ending port for a port range
toPort: 8080
# (Optional) Outbound traffic rules
egressRules:
prefixList:
- prefixList: pl-4ea54027
description: prefix list for DynamoDB endpoint
protocol: tcp
port: 443
- prefixList: pl-7da54014
description: prefix list for S3 endpoint
protocol: tcp
port: 443
ipv4:
- cidr: 10.0.0.0/28
port: 443
protocol: tcp
sg:
- sgId: ssm:/ml/sm/sg/id
port: 5472
protocol: tcp
# (Optional) Map of named CloudFormation Init configurations.
# Referenced by instances via initName.
cfnInit:
initWindows:
# Map of config set names to ordered config lists
configSets:
default:
# Ordered list of config names to execute
configs:
- 'awscli'
- 'Preinstall'
confgiset2:
configs:
- 'Preinstall'
- 'awscli'
# Map of config names to config definitions
configs:
awscli:
# (Optional) Packages to install
packages:
awspackage:
# Package manager (msi, rpm, python, yum, apt, gem)
packageManager: msi
# Package download location
packageLocation: 'https://awscli.amazonaws.com//AWSCLI64.msi'
# (Optional) Identifier key for MSI/RPM packages
key: awscli-msi
# (Optional) Restart associated services after install
restartRequired: true
anotherpackage:
packageManager: msi
packageLocation: 'https://awscli.amazonaws.com//thisisanotherpackage.msi'
Preinstall:
packages:
git:
packageManager: msi
packageLocation: 'https://awscli.amazonaws.com/somepackagefromconfig.msi'
# (Optional) Commands to execute (run in lexicographic order
# of key names)
commands:
01testCommand:
# Shell command string
shellCommand: 'echo "this is a command"'
02anotherTestCommand:
shellCommand: 'echo "this TOO is a command"'
# (Optional) Test command; success skips main command
testCommand: 'echo "this is test command"'
# (Optional) Working directory for the command
workingDir: '/some/dir/'
# (Optional) Resume cfn-init after reboot
waitForever: true
# (Optional) Restart service after command completes
restartRequired: true
03commandWithArgvs:
# (Optional) Command as argument vector (mutually
# exclusive with shellCommand)
argvs:
- 'powershell.exe'
- '-Command'
- 'Write-Host "argv command"'
# (Optional) Environment variables for the command
env:
MY_VAR: my-value
ANOTHER_VAR: another-value
# (Optional) Continue if this command fails
ignoreErrors: true
# (Optional) Minutes to wait after completion (Windows)
waitAfterCompletion: 2
04commandWithWaitNone:
shellCommand: 'echo "fire and forget"'
# (Optional) Do not wait after command completes
waitNone: true
# (Optional) Files to create on the instance
files:
testfile.txt:
# Path to source file
filePath: './somefile.txt'
restartRequired: true
# (Optional) Services to manage
services:
cfn-hup:
# Whether the service should be enabled
enabled: true
# Ensure the service is running
ensureRunning: true
# Restart after file/package/command changes
restartRequired: true
# (Optional) Explicitly disable and stop a service
unused-svc:
# (Optional) Disable and stop the service
disabled: true
initLinux:
configSets:
default:
configs:
- 'Apache'
- 'Prereq'
confgiset2:
configs:
- 'Prereq'
- 'Apache'
configs:
Prereq:
packages:
git:
packageManager: yum
packageName: git
packageVersions: []
rpmpackage:
packageManager: rpm
packageLocation: 'https://awscli.amazonaws.com//rpmpackage.rpm'
jqpackage:
packageManager: yum
packageName: jq
packageVersions: []
# (Optional) Linux/UNIX groups to create (not Windows)
groups:
app-group:
# (Optional) Specific numeric group ID
gid: '501'
# (Optional) Linux/UNIX user accounts to create (not Windows)
users:
app-user:
# Groups the user belongs to
groups:
- app-group
# Home directory path
homeDir: /home/app-user
# (Optional) Specific numeric user ID
uid: '1001'
# (Optional) Archive files to download and extract
sources:
/opt/app:
# URL of the archive to extract into the target directory
source: 'https://example.com/app-archive.tar.gz'
Apache:
packages:
apachepackage:
packageManager: yum
packageName: httpd
packageVersions: []
# (Optional) Map of instance names to EC2 instance configurations
instances:
# Linux instance with named init, key pair, and user data
instance-1:
# Reference to a security group from the securityGroups section
securityGroup: sg1
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/vpc/id
vpcId: vpc-testvpc
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/subnet/id
subnetId: subnet-testsubnet
availabilityZone: '{{region}}a'
instanceType: t3.medium
amiId: ami-linux
# Instance profile role (by arn, name, or id)
instanceRole:
arn: arn:{{partition}}:iam::{{account}}:role/instance-role
# (Optional) EBS block device mappings
blockDevices:
- # Device name (must include root volume for unencrypted AMIs)
deviceName: '/dev/sda1'
# Volume size in GB
volumeSizeInGb: 32
# EBS volume type (gp2, gp3, io1, io2, sc1, st1, standard)
ebsType: gp3
# OS type (linux, windows, unknown)
osType: linux
# (Optional) Path to user data script relative to this config
userDataScriptPath: './userdata.sh'
# (Optional) Name of a key pair from the keyPairs section
keyPairName: test-key-pair
# (Optional) Name of a cfnInit configuration to apply
initName: initLinux
# (Optional) Disable source/destination checking for NAT or
# routing instances
sourceDestCheck: false
# Windows instance with existing security group, custom KMS, and
# init options
instance-2:
# ID of an existing security group (not from this config)
securityGroupId: sg-123412412
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/vpc/id
vpcId: vpc-testvpc
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/subnet/id
subnetId: subnet-testsubnet
instanceType: t3.medium
availabilityZone: '{{region}}b'
amiId: ami-windows
instanceRole:
name: some-instance-role-name
blockDevices:
- deviceName: '/dev/sda1'
volumeSizeInGb: 32
ebsType: gp3
- deviceName: '/dev/sdb1'
volumeSizeInGb: 16
ebsType: gp2
# (Optional) IOPS for io1/io2 volumes
iops: 3000
# (Optional) KMS key ARN for EBS volume encryption
kmsKeyArn: 'arn:{{partition}}:kms:{{region}}:{{account}}:key/test-key'
osType: windows
userDataScriptPath: './userdata.ps1'
# (Optional) Whether user data changes trigger instance
# replacement
userDataCausesReplacement: false
# (Optional) Name of an existing key pair (created outside
# this config)
existingKeyPairName: 'rsa-key'
initName: initWindows
# (Optional) Init execution options
initOptions:
# (Optional) Config sets to run (default: ['default'])
configSets: ['confgiset2']
# (Optional) Include IAM role in cfn-init call
includeRole: true
# (Optional) Embed config fingerprint in UserData for
# automatic replacement on config change (default: true)
embedFingerprint: false
# (Optional) Continue instance creation even if cfn-init
# fails (default: false)
ignoreFailures: false
# (Optional) Include --url argument for custom CloudFormation
# endpoint
includeUrl: true
# (Optional) Print cfn-init output to EC2 System Log
printLog: true
# (Optional) Max time in minutes to wait for init
# (default: 5)
timeout: 30
# (Optional) Number of success signals required before
# CREATE_COMPLETE
signalCount: 1
# (Optional) Timeout for creation policy (ISO 8601 duration)
creationTimeOut: PT25M
Inline Init Configuration
Demonstrates using an inline CloudFormation Init definition directly on an instance (via the "init" property) instead of referencing a named init from the top-level cfnInit section. Choose this variant when you prefer to co-locate bootstrap configuration with the instance definition rather than referencing shared init blocks.
sample-config-inline-init.yaml
# Contents available via above link
# EC2 module configuration — inline init variant.
# Demonstrates using an inline CloudFormation Init definition directly
# on an instance (via the "init" property) instead of referencing a
# named init from the top-level cfnInit section. Also exercises the
# osType "unknown" enum value.
# See CONFIGURATION.md for role reference options (name, arn, id).
# Roles granted access to the KMS key and KeyPair secrets
adminRoles:
- name: Admin
# (Optional) Map of instance names to EC2 instance configurations
instances:
# Instance with inline init and osType unknown
instance-inline:
securityGroupId: sg-inlinetest
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/vpc/id
vpcId: vpc-testvpc
# Often created by your VPC/networking stack.
# Example SSM: ssm:/path/to/subnet/id
subnetId: subnet-testsubnet
availabilityZone: '{{region}}a'
instanceType: t3.micro
amiId: ami-generic
instanceRole:
name: inline-instance-role
blockDevices:
- deviceName: '/dev/sda1'
volumeSizeInGb: 20
ebsType: gp3
# OS type for the instance
osType: linux
# (Optional) Inline CloudFormation Init configuration
# (alternative to initName referencing a top-level cfnInit entry)
init:
configSets:
default:
configs:
- 'setup'
configs:
setup:
commands:
01hello:
shellCommand: 'echo "inline init"'