1
0

initial commit

This commit is contained in:
xpk
2026-02-13 15:44:24 +08:00
parent 66be8224f4
commit 09ce4c881a
570 changed files with 61807 additions and 0 deletions
@@ -0,0 +1,57 @@
<!-- This readme file is generated with terraform-docs -->
# LambdaZipBuilder
Download pip packages and create ./lambda\_layer.zip
Optionally upload archive to s3 bucket
## Requirements
| Name | Version |
|------|---------|
| archive | >= 2.7.1 |
| aws | ~> 5.100.0 |
| null | >= 3.2.4 |
## Providers
| Name | Version |
|------|---------|
| archive | >= 2.7.1 |
| aws | ~> 5.100.0 |
| null | >= 3.2.4 |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [aws_s3_object.object](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource |
| [null_resource.pip_download](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [null_resource.remove_temp_downloads](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [archive_file.layer1](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| pip\_packages | List of pip packages, separated by space | `string` | n/a | yes |
| pip\_path | Path to pip. Defaults to /usr/bin/pip3 | `string` | `"/usr/bin/pip3"` | no |
| s3\_bucket\_name | Name of s3 bucket for storing lambda layer archive | `string` | `null` | no |
| upload\_archive\_to\_s3 | Whether to upload layer archive to s3. Use this for zipped size >50M or unzipped size >250M. Limit is imposed by AWS API | `bool` | `false` | no |
## Outputs
| Name | Description |
|------|-------------|
| archive\_checksum | Hash of archive |
| archive\_path | Full path to archive file |
| archive\_size | Size of archive |
| s3\_object\_hash | S3 object hash, useful for triggering lambda layer update |
| s3\_object\_key | S3 object key |
---
## Authorship
This module was developed by xpk.
@@ -0,0 +1,11 @@
module "lambda-zip" {
source = "../"
pip_packages = "pandas numpy"
}
output "zip_archive" {
value = {
checksum : module.lambda-zip.archive_checksum
size : module.lambda-zip.archive_size
}
}
+45
View File
@@ -0,0 +1,45 @@
/**
* # LambdaZipBuilder
*
* Download pip packages and create ./lambda_layer.zip
* Optionally upload archive to s3 bucket
*
*/
resource "null_resource" "pip_download" {
provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = "${var.pip_path} install --only-binary=all --target /tmp/LambdaZipBuilder/python ${var.pip_packages}"
}
triggers = {
date = timestamp()
}
}
resource "null_resource" "remove_temp_downloads" {
depends_on = [data.archive_file.layer1]
provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = "rm -rf /tmp/LambdaZipBuilder || true"
}
triggers = {
cleanup = data.archive_file.layer1.output_base64sha256
}
}
data "archive_file" "layer1" {
depends_on = [null_resource.pip_download]
type = "zip"
source_dir = "/tmp/LambdaZipBuilder"
output_path = "lambda_layer.zip"
}
resource "aws_s3_object" "object" {
count = var.upload_archive_to_s3 ? 1 : 0
bucket = var.s3_bucket_name
key = "LambdaZipBuilder-${sha256(var.pip_packages)}.zip"
source = data.archive_file.layer1.output_path
checksum_algorithm = "SHA256"
source_hash = data.archive_file.layer1.output_base64sha256
}
@@ -0,0 +1,24 @@
output "archive_path" {
description = "Full path to archive file"
value = data.archive_file.layer1.output_path
}
output "archive_checksum" {
description = "Hash of archive"
value = data.archive_file.layer1.output_base64sha256
}
output "archive_size" {
description = "Size of archive"
value = data.archive_file.layer1.output_size
}
output "s3_object_key" {
description = "S3 object key"
value = try(aws_s3_object.object[0].key, null)
}
output "s3_object_hash" {
description = "S3 object hash, useful for triggering lambda layer update"
value = try(aws_s3_object.object[0].checksum_sha256, null)
}
@@ -0,0 +1,16 @@
terraform {
required_providers {
null = {
source = "hashicorp/null"
version = ">= 3.2.4"
}
archive = {
source = "hashicorp/archive"
version = ">= 2.7.1"
}
aws = {
source = "hashicorp/aws"
version = "~> 5.100.0"
}
}
}
@@ -0,0 +1,22 @@
variable "pip_packages" {
type = string
description = "List of pip packages, separated by space"
}
variable "pip_path" {
type = string
description = "Path to pip. Defaults to /usr/bin/pip3"
default = "/usr/bin/pip3"
}
variable "upload_archive_to_s3" {
type = bool
description = "Whether to upload layer archive to s3. Use this for zipped size >50M or unzipped size >250M. Limit is imposed by AWS API"
default = false
}
variable "s3_bucket_name" {
type = string
description = "Name of s3 bucket for storing lambda layer archive"
default = null
}
+66
View File
@@ -0,0 +1,66 @@
<!-- This readme file is generated with terraform-docs -->
# LaunchTemplate
This module created EC2 launch template. If a single instance type is specified
it will create launch template with that instance type. If multiple types are specified
then a launch template with instance\_requirements will be created.
Root ebs volume is always encrypted - either with the aws/ebs key or a customer managed key
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 1.3.0 |
| aws | >= 5.0 |
## Providers
| Name | Version |
|------|---------|
| aws | >= 5.0 |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [aws_launch_template.lt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource |
| [aws_ami.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| cpu\_count\_max | Maximum vcpu count for setting up instance\_requirements | `number` | `null` | no |
| cpu\_count\_min | Minimum vcpu count for setting up instance\_requirements | `number` | `null` | no |
| description | Description of launch template | `string` | n/a | yes |
| ebs\_volume\_kms\_key\_id | KMS key id for EBS encryption - a default key will be used if not specified | `string` | `null` | no |
| image\_id | AMI id of launch template | `string` | n/a | yes |
| imdsv2\_required | Use IMDSv2 for ec2 instance | `bool` | `true` | no |
| instance\_initiated\_shutdown\_behavior | Shutdown behavior for the instance - stop (default) or terminate | `string` | `"stop"` | no |
| instance\_profile\_name | Name of iam instance profile | `string` | `null` | no |
| instance\_types | Types of instances allowed for this launch template | `list(string)` | n/a | yes |
| key\_name | Name of keypair | `string` | `null` | no |
| mem\_mib\_max | Maximum memory size (mib) for setting up instance\_requirements | `number` | `null` | no |
| mem\_mib\_min | Minimum memory size (mib) for setting up instance\_requirements | `number` | `null` | no |
| name | Name of launch template | `string` | n/a | yes |
| root\_volume\_size | Size of root volume in GB | `number` | n/a | yes |
| root\_volume\_type | Root volume type - default gp3 | `string` | `"gp3"` | no |
| security\_grouo\_ids | List of security group ids | `list(string)` | `[]` | no |
| tag\_specifications | Tags to be added to instance and volume | `map(string)` | n/a | yes |
| update\_default\_version | Point default version to the latest | `bool` | `true` | no |
| userdata\_base64 | Base64 encoded userdata | `string` | n/a | yes |
## Outputs
| Name | Description |
|------|-------------|
| launch\_template\_id | ID of launch template |
---
## Authorship
This module was developed by xpk.
+84
View File
@@ -0,0 +1,84 @@
/**
* # LaunchTemplate
*
* This module created EC2 launch template. If a single instance type is specified
* it will create launch template with that instance type. If multiple types are specified
* then a launch template with instance_requirements will be created.
*
* Root ebs volume is always encrypted - either with the aws/ebs key or a customer managed key
*/
data "aws_ami" "this" {
filter {
name = "image-id"
values = [var.image_id]
}
}
resource "aws_launch_template" "template" {
name = var.name
description = var.description
image_id = var.image_id
instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior
key_name = var.key_name
vpc_security_group_ids = var.security_grouo_ids
user_data = var.userdata_base64
update_default_version = var.update_default_version
iam_instance_profile {
name = var.instance_profile_name
}
monitoring {
enabled = true
}
dynamic "tag_specifications" {
for_each = toset(["instance", "volume"])
content {
resource_type = tag_specifications.value
tags = merge(var.tag_specifications, {
os_platform = coalesce(data.aws_ami.this.platform, "Linux")
architecture = data.aws_ami.this.architecture
ami_name = data.aws_ami.this.name
})
}
}
block_device_mappings {
device_name = data.aws_ami.this.platform == "Windows" ? "/dev/sda1" : "/dev/xvda"
ebs {
volume_size = var.root_volume_size
volume_type = var.root_volume_type
delete_on_termination = true
encrypted = true
kms_key_id = var.ebs_volume_kms_key_id
}
}
dynamic "metadata_options" {
for_each = var.imdsv2_required ? [1] : []
content {
http_endpoint = "enabled" # Enables instance metadata service endpoint
http_tokens = "required" # Enforces IMDSv2
http_put_response_hop_limit = 2 # 1 default, 2 for containers
}
}
instance_type = length(var.instance_types) == 1 ? var.instance_types[0] : null
dynamic "instance_requirements" {
for_each = length(var.instance_types) > 1 ? [1] : []
content {
vcpu_count {
min = var.cpu_count_min
max = var.cpu_count_max
}
memory_mib {
min = var.mem_mib_min
max = var.mem_mib_max
}
allowed_instance_types = var.instance_types
}
}
}
@@ -0,0 +1,4 @@
output launch_template_id {
description = "ID of launch template"
value = aws_launch_template.template.id
}
+110
View File
@@ -0,0 +1,110 @@
variable "instance_initiated_shutdown_behavior" {
default = "stop"
type = string
description = "Shutdown behavior for the instance - stop (default) or terminate"
}
variable "name" {
type = string
description = "Name of launch template"
}
variable "description" {
type = string
description = "Description of launch template"
}
variable "image_id" {
type = string
description = "AMI id of launch template"
}
variable "key_name" {
type = string
description = "Name of keypair"
default = null
}
variable "security_grouo_ids" {
type = list(string)
description = "List of security group ids"
default = []
}
variable "userdata_base64" {
type = string
description = "Base64 encoded userdata"
validation {
condition = can(base64decode(var.userdata_base64))
error_message = "Userdata must be encoded in base64"
}
}
variable "tag_specifications" {
type = map(string)
description = "Tags to be added to instance and volume"
}
variable "root_volume_size" {
type = number
description = "Size of root volume in GB"
}
variable "root_volume_type" {
default = "gp3"
type = string
description = "Root volume type - default gp3"
}
variable "ebs_volume_kms_key_id" {
type = string
description = "KMS key id for EBS encryption - a default key will be used if not specified"
default = null
}
variable "imdsv2_required" {
default = true
type = bool
description = "Use IMDSv2 for ec2 instance"
}
variable "instance_types" {
type = list(string)
description = "Types of instances allowed for this launch template"
}
variable "cpu_count_min" {
type = number
description = "Minimum vcpu count for setting up instance_requirements"
default = null
}
variable "cpu_count_max" {
type = number
description = "Maximum vcpu count for setting up instance_requirements"
default = null
}
variable "mem_mib_min" {
type = number
description = "Minimum memory size (mib) for setting up instance_requirements"
default = null
}
variable "mem_mib_max" {
type = number
description = "Maximum memory size (mib) for setting up instance_requirements"
default = null
}
variable "update_default_version" {
type = bool
default = true
description = "Point default version to the latest"
}
variable "instance_profile_name" {
type = string
description = "Name of iam instance profile"
default = null
}
@@ -0,0 +1,9 @@
terraform {
required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
@@ -0,0 +1,16 @@
import boto3
import os
import json
# reference: https://aws.amazon.com/premiumsupport/knowledge-center/start-stop-lambda-eventbridge/
ec2 = boto3.client('ec2', region_name=os.environ['AWS_REGION'])
def lambda_handler(event, context):
if event['action'] == 'start':
resp = ec2.start_instances(InstanceIds=json.loads(os.environ['instances']))
elif event['action'] == 'stop':
resp = ec2.stop_instances(InstanceIds=json.loads(os.environ['instances']))
else:
resp = "Event action not provided"
return resp
@@ -0,0 +1,52 @@
<!-- This readme file is generated with terraform-docs -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 1.3.0 |
| aws | >= 5.0 |
## Providers
| Name | Version |
|------|---------|
| archive | n/a |
| aws | >= 5.0 |
| random | n/a |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [aws_iam_role.eventscheduler](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_lambda_function.ec2-start-stop](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_lambda_permission.lambda_permission](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
| [aws_scheduler_schedule.start](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/scheduler_schedule) | resource |
| [aws_scheduler_schedule.stop](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/scheduler_schedule) | resource |
| [random_id.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
| [archive_file.lambda-package](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
| [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| description | A description of instances to be started/stopped on schedule | `string` | n/a | yes |
| instance-ids | Instances to be automatically started/stopped on schedule | `list(string)` | n/a | yes |
| instance-start-cron-expression | Cron expression for instance start schedule | `string` | n/a | yes |
| instance-stop-cron-expression | Cron expression for instance stop schedule | `string` | n/a | yes |
## Outputs
No outputs.
---
## Authorship
This module was developed by UPDATE_THIS.
@@ -0,0 +1,203 @@
data "aws_caller_identity" "this" {}
resource "random_id" "this" {
byte_length = 4
}
resource "aws_iam_role" "eventscheduler" {
name = "EventSchedulerRole-${random_id.this.dec}"
assume_role_policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Service" : "scheduler.amazonaws.com"
},
"Action" : "sts:AssumeRole"
}
]
}
)
}
resource "aws_iam_role_policy_attachment" "default" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEventBridgeSchedulerFullAccess"
role = aws_iam_role.eventscheduler.name
}
resource "aws_iam_role" "this" {
name = "lambda-startstop-ec2-${var.description}"
assume_role_policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Service" : "lambda.amazonaws.com"
},
"Action" : "sts:AssumeRole"
}
]
}
)
}
resource "aws_iam_role_policy" "this" {
policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Sid" : "AllowCreationOfCloudwatchLogGroup",
"Effect" : "Allow",
"Action" : "logs:CreateLogGroup",
"Resource" : "arn:aws:logs:ap-east-1:${data.aws_caller_identity.this.account_id}:*"
},
{
"Sid" : "AllowWritingToCloudwatchLogGroup",
"Effect" : "Allow",
"Action" : [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource" : [
"arn:aws:logs:ap-east-1:${data.aws_caller_identity.this.account_id}:log-group:/aws/lambda/*"
]
},
{
"Sid" : "AllowStartingStoppingOfEc2Instance",
"Action" : [
"ec2:StopInstances",
"ec2:StartInstances",
"kms:CreateGrant"
],
"Effect" : "Allow",
"Resource" : "*"
}
]
}
)
role = aws_iam_role.this.id
name = "LambdaExecutionPolicy"
}
resource "aws_iam_role_policy" "eventscheduler" {
policy = jsonencode(
{
"Version" : "2012-10-17",
"Statement" : [
{
"Sid" : "AllowInvocationOfLambdaFunction",
"Effect" : "Allow",
"Action" : "lambda:InvokeFunction",
"Resource" : "*"
}
]
}
)
role = aws_iam_role.eventscheduler.id
name = "LambdaInvocation"
}
resource "aws_scheduler_schedule" "start" {
name = "scheduled-start-of-${var.description}-instances"
description = "Starts ${var.description} ec2 instance"
flexible_time_window {
mode = "OFF"
}
schedule_expression = var.instance-start-cron-expression
target {
arn = aws_lambda_function.ec2-start-stop.arn
role_arn = aws_iam_role.eventscheduler.arn
input = jsonencode({ "action" : "start" })
retry_policy {
maximum_event_age_in_seconds = 600
maximum_retry_attempts = 1
}
}
}
resource "aws_scheduler_schedule" "stop" {
name = "scheduled-stop-of-${var.description}-instances"
description = "Stops ${var.description} ec2 instance"
flexible_time_window {
mode = "OFF"
}
schedule_expression = var.instance-stop-cron-expression
target {
arn = aws_lambda_function.ec2-start-stop.arn
role_arn = aws_iam_role.eventscheduler.arn
input = jsonencode({ "action" : "stop" })
retry_policy {
maximum_event_age_in_seconds = 600
maximum_retry_attempts = 1
}
}
}
#
#resource "aws_cloudwatch_event_rule" "start" {
# name = "scheduled-start-of-${var.description}-instances"
# description = "Starts automation ec2 instance"
# schedule_expression = var.instance-start-cron-expression
#}
#
#resource "aws_cloudwatch_event_rule" "stop" {
# name = "scheduled-stop-of-${var.description}-instances"
# description = "Stops automation ec2 instance"
# schedule_expression = var.instance-stop-cron-expression
#}
#
#resource "aws_cloudwatch_event_target" "start" {
# rule = aws_cloudwatch_event_rule.start.name
# arn = aws_lambda_function.ec2-start-stop.arn
# input = "{\"action\": \"start\"}"
#}
#
#resource "aws_cloudwatch_event_target" "stop" {
# rule = aws_cloudwatch_event_rule.stop.name
# arn = aws_lambda_function.ec2-start-stop.arn
# input = "{\"action\": \"stop\"}"
#}
# Lambda function for instance scheduler
data "archive_file" "lambda-package" {
type = "zip"
source_file = "${path.module}/Ec2Scheduler.py"
output_path = "lambda-package.zip"
}
resource "aws_lambda_function" "ec2-start-stop" {
function_name = "${var.description}-ec2-start-stop"
filename = data.archive_file.lambda-package.output_path
source_code_hash = data.archive_file.lambda-package.output_base64sha256
handler = "Ec2Scheduler.lambda_handler"
runtime = "python3.12"
role = aws_iam_role.this.arn
timeout = 30
environment {
variables = {
instances = jsonencode(var.instance-ids)
}
}
}
resource "aws_lambda_permission" "lambda_permission" {
statement_id = "AllowCloudWatchToInvokeLambda"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.ec2-start-stop.function_name
principal = "events.amazonaws.com"
}
resource "aws_cloudwatch_log_group" "this" {
name = "/aws/lambda/${var.description}-ec2-start-stop"
retention_in_days = var.cloudwatchlog-retention
}
@@ -0,0 +1,25 @@
variable "instance-start-cron-expression" {
type = string
description = "Cron expression for instance start schedule"
}
variable "instance-stop-cron-expression" {
type = string
description = "Cron expression for instance stop schedule"
}
variable "instance-ids" {
type = list(string)
description = "Instances to be automatically started/stopped on schedule"
}
variable "description" {
type = string
description = "A description of instances to be started/stopped on schedule"
}
variable "cloudwatchlog-retention" {
type = number
description = "Cloudwatch log retention days"
default = 30
}
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
archive = {
source = "hashicorp/archive"
}
}
}
+80
View File
@@ -0,0 +1,80 @@
<!-- This readme file is generated with terraform-docs -->
## Requirements
| Name | Version |
|------|---------|
| terraform | >= 1.3.0 |
| aws | ~> 5.35.0 |
## Providers
| Name | Version |
|------|---------|
| aws | ~> 5.35.0 |
| random | n/a |
| tls | n/a |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [aws_ebs_volume.data-volumes](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ebs_volume) | resource |
| [aws_eip.ec2-eip](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource |
| [aws_instance.ec2-instance](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource |
| [aws_key_pair.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/key_pair) | resource |
| [aws_secretsmanager_secret.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret_version.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_volume_attachment.data-volume-attachments](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/volume_attachment) | resource |
| [random_id.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
| [tls_private_key.this](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
| [aws_default_tags.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/default_tags) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| UseRsaKeyForWindows | Set true to use RSA key for Windows instances | `bool` | `false` | no |
| additional-tags | Additional tags to be assigned on top of provider default tags. Useful for setting backup tags. | `map(string)` | n/a | yes |
| ami-id | Image id of EC2 instance | `string` | n/a | yes |
| asso-eip | Whether to associate Elastic IP | `bool` | n/a | yes |
| asso-public-ip | Whether to associate ephemeral public IP | `bool` | n/a | yes |
| create-ssh-key | Set true to create ssh key and store on secret manager | `bool` | `false` | no |
| data-volumes | Attach additional data volumes | <pre>map(object({<br> size = number<br> type = string<br> }))</pre> | n/a | yes |
| delete-on-termination | Whether to delete volumes on termination | `bool` | `true` | no |
| disable\_secure\_idmsv2 | If set to true, the insecure IDMSv1 will be used. | `bool` | `false` | no |
| ebs-encrypted | Whether to enable EBS encryption | `bool` | `true` | no |
| enable-detail-monitoring | Set true to enable detail monitoring | `bool` | `false` | no |
| enable-termination-protection | Whether to enable prevent accidential deletion of instance | `bool` | `false` | no |
| get\_password\_data | Get password data for windows instance, save in secretsmanager | `bool` | `false` | no |
| instance-name | Name of ec2 instance | `string` | n/a | yes |
| instance-profile | Ec2 instance profile name | `string` | `""` | no |
| instance-type | Instance type | `string` | n/a | yes |
| key-name | Instance ssh key name | `string` | `""` | no |
| kms-key-id | Disk encryption KMS key id | `string` | n/a | yes |
| private-ip | Specify private IP to be used on this instance | `string` | `null` | no |
| root-volume-size | Size of root volume | `number` | n/a | yes |
| root-volume-type | Root volume type | `string` | `"gp3"` | no |
| security-groups | List of security groups for Ec2 instance | `list(string)` | n/a | yes |
| spot-max-price | Max hourly price for spot instance. If greater than zero, spot instance will be used. | `number` | `0` | no |
| subnet-id | Id of subnet to deploy Ec2 instance to | `string` | n/a | yes |
| user-data | Ec2 user-data | `string` | `""` | no |
## Outputs
| Name | Description |
|------|-------------|
| ec2-id-ip | Ec2 instance id and private ip |
| elastic-ip | Ec2 instance EIP |
| instance-id | Ec2 instance id |
| private-ip | Ec2 instance private IP |
| public-ip | Ec2 instance ephemeral public IP |
| ssh-key-name | Ec2 instance ssh key name |
| ssh-key-secret-arn | Secretsmanager arn for ec2 instance ssh key |
---
## Authorship
This module was developed by xpk.
+135
View File
@@ -0,0 +1,135 @@
resource "aws_instance" "ec2-instance" {
ami = var.ami-id
instance_type = var.instance-type
associate_public_ip_address = var.asso-public-ip
// availability_zone = var.az
iam_instance_profile = var.instance-profile
key_name = var.create-ssh-key ? aws_key_pair.this[0].key_name : var.key-name
private_ip = var.private-ip
enable_primary_ipv6 = var.use-ipv6
root_block_device {
encrypted = var.ebs-encrypted
volume_size = var.root-volume-size
volume_type = var.root-volume-type
kms_key_id = var.kms-key-id
delete_on_termination = var.delete-on-termination
# terraform volume_tags is the only way to create volume and tag them in the same operation
# so do not use tagging in root_block_device
}
ebs_optimized = true
subnet_id = var.subnet-id
vpc_security_group_ids = var.security-groups
get_password_data = var.get_password_data
# IMDSv2 requirement
dynamic "metadata_options" {
for_each = var.disable_secure_idmsv2 == false ? { set_idmsv2 : true } : {}
content {
http_endpoint = "enabled"
http_tokens = "required"
http_put_response_hop_limit = 2
}
}
# spot instance option
dynamic "instance_market_options" {
for_each = var.spot-max-price > 0 ? { use_spot : true } : {}
content {
market_type = "spot"
dynamic "spot_options" {
for_each = { use_spot : true }
content {
max_price = var.spot-max-price
}
}
}
}
disable_api_termination = var.enable-termination-protection
user_data = var.user-data
monitoring = var.enable-detail-monitoring
tags = merge(var.additional-tags, { "Name" : var.instance-name })
volume_tags = merge({ "Name" : "${var.instance-name}-root" }, data.aws_default_tags.this.tags)
# do not redeploy instance when a new ami is released
# do not update volume_tags after initial deployment
lifecycle {
ignore_changes = [ami, volume_tags]
}
}
resource "aws_ebs_volume" "data-volumes" {
for_each = var.data-volumes
availability_zone = aws_instance.ec2-instance.availability_zone
size = each.value["size"]
type = each.value["type"]
iops = try(each.value["iops"], null)
kms_key_id = aws_instance.ec2-instance.root_block_device[0].kms_key_id
encrypted = aws_instance.ec2-instance.root_block_device[0].encrypted
tags = merge(
{ Name : "${var.instance-name}-${each.key}" },
data.aws_default_tags.this.tags
)
}
locals {
a_to_z = split(",", "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z")
}
resource "aws_volume_attachment" "data-volume-attachments" {
count = length(aws_ebs_volume.data-volumes)
volume_id = [for v in aws_ebs_volume.data-volumes : v.id][count.index]
instance_id = aws_instance.ec2-instance.id
device_name = "/dev/xvda${element(local.a_to_z, count.index)}"
}
resource "aws_eip" "ec2-eip" {
count = var.asso-eip ? 1 : 0
instance = aws_instance.ec2-instance.id
domain = "vpc"
}
resource "tls_private_key" "this" {
count = var.create-ssh-key ? 1 : 0
algorithm = var.UseRsaKeyForWindows ? "RSA" : "ED25519"
rsa_bits = var.UseRsaKeyForWindows ? 3072 : null
ecdsa_curve = var.UseRsaKeyForWindows ? null : "P384"
}
resource "aws_key_pair" "this" {
count = var.create-ssh-key ? 1 : 0
key_name = "${var.instance-name}-sshkey"
public_key = tls_private_key.this[0].public_key_openssh
}
resource "random_id" "this" {
byte_length = 2
}
resource "aws_secretsmanager_secret" "this" {
count = var.create-ssh-key ? 1 : 0
name = "${var.instance-name}-sshkey-${random_id.this.dec}"
description = "Private key for ${aws_instance.ec2-instance.id}"
}
resource "aws_secretsmanager_secret_version" "this" {
count = var.create-ssh-key ? 1 : 0
secret_id = aws_secretsmanager_secret.this[0].id
secret_string = jsonencode({
"PrivateKey" = tls_private_key.this[0].private_key_openssh
"WindowsAdminPassword" = var.get_password_data ? rsadecrypt(aws_instance.ec2-instance.password_data, tls_private_key.this[0].private_key_openssh) : ""
})
}
data "aws_default_tags" "this" {
lifecycle {
postcondition {
condition = length(self.tags) >= 1
error_message = "Validation failed: Provider default_tags not set."
}
}
}
+37
View File
@@ -0,0 +1,37 @@
output "ec2-id-ip" {
description = "Ec2 instance id and private ip"
value = {
instance-id = aws_instance.ec2-instance.id
private-ip = aws_instance.ec2-instance.private_ip
}
}
output "instance-id" {
description = "Ec2 instance id"
value = aws_instance.ec2-instance.id
}
output "private-ip" {
description = "Ec2 instance private IP"
value = aws_instance.ec2-instance.private_ip
}
output "ssh-key-name" {
description = "Ec2 instance ssh key name"
value = var.create-ssh-key ? aws_key_pair.this[0].key_name : var.key-name
}
output "ssh-key-secret-arn" {
description = "Secretsmanager arn for ec2 instance ssh key"
value = var.create-ssh-key ? aws_secretsmanager_secret.this[0].arn : null
}
output "elastic-ip" {
description = "Ec2 instance EIP"
value = var.asso-eip ? aws_eip.ec2-eip[0].public_ip : null
}
output "public-ip" {
description = "Ec2 instance ephemeral public IP"
value = var.asso-public-ip ? aws_instance.ec2-instance.public_ip : null
}
+122
View File
@@ -0,0 +1,122 @@
variable "instance-type" {
type = string
description = "Instance type"
}
variable "ami-id" {
type = string
description = "Image id of EC2 instance"
}
variable "asso-public-ip" {
type = bool
description = "Whether to associate ephemeral public IP"
}
variable "instance-profile" {
type = string
default = ""
description = "Ec2 instance profile name"
}
variable "key-name" {
type = string
description = "Instance ssh key name"
default = ""
}
variable "ebs-encrypted" {
type = bool
default = true
description = "Whether to enable EBS encryption"
}
variable "root-volume-size" {
type = number
description = "Size of root volume"
}
variable "root-volume-type" {
type = string
default = "gp3"
description = "Root volume type"
}
variable "kms-key-id" {
type = string
description = "Disk encryption KMS key id"
}
variable "delete-on-termination" {
type = bool
default = true
description = "Whether to delete volumes on termination"
}
variable "subnet-id" {
type = string
description = "Id of subnet to deploy Ec2 instance to"
}
variable "security-groups" {
type = list(string)
description = "List of security groups for Ec2 instance"
}
variable "instance-name" {
type = string
description = "Name of ec2 instance"
}
variable "asso-eip" {
type = bool
description = "Whether to associate Elastic IP"
}
variable "data-volumes" {
type = map(object({
size = number
type = string
}))
description = "Attach additional data volumes"
}
variable "private-ip" {
type = string
default = null
description = "Specify private IP to be used on this instance"
}
variable "additional-tags" {
type = map(string)
description = "Additional tags to be assigned on top of provider default tags. Useful for setting backup tags."
}
variable "disable_secure_idmsv2" {
type = bool
default = false
description = "If set to true, the insecure IDMSv1 will be used."
}
variable "enable-termination-protection" {
type = bool
default = false
description = "Whether to enable prevent accidential deletion of instance"
}
variable "user-data" {
type = string
default = ""
description = "Ec2 user-data"
}
variable "enable-detail-monitoring" {
type = bool
default = false
description = "Set true to enable detail monitoring"
}
variable "spot-max-price" {
type = number
description = "Max hourly price for spot instance. If greater than zero, spot instance will be used."
default = 0
}
variable "create-ssh-key" {
type = bool
default = false
description = "Set true to create ssh key and store on secret manager"
}
variable "UseRsaKeyForWindows" {
type = bool
default = false
description = "Set true to use RSA key for Windows instances"
}
variable "get_password_data" {
type = bool
default = false
description = "Get password data for windows instance, save in secretsmanager"
}
variable use-ipv6 {
type = bool
default = false
description = "Enable primary IPv6 address"
}
+52
View File
@@ -0,0 +1,52 @@
# security-groups-gen2
This module create security groups from a map
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:-----:|
| tags | tags | List | n/a | yes |
| vpc-id | VPC id | string | n/a | yes |
| security-groups | See example below | map | n/a | yes |
### security-groups input
Below is a sample security-groups map this module ingests. The rule list needs to have
the id column to prevent list from being randomly sorted.
```hcl
module "headdesk-sg" {
source = "../../modules/compute/security-groups"
security-groups = [
{
name = "WebAccess"
description = "Public web access"
rules = [
[1, "tcp", "0.0.0.0/0", "80", "80", "ingress", "web"],
[2, "tcp", "0.0.0.0/0", "443", "443", "ingress", "web"],
[3, "tcp", "0.0.0.0/0", "25", "25", "ingress", "mail"],
[4, "tcp", "0.0.0.0/0", "587", "587", "ingress", "mail"],
[5, "tcp", "0.0.0.0/0", "11993", "11993", "ingress", "mail"],
[6, "-1", "0.0.0.0/0", "0", "0", "egress", "Allow outbound traffic"],
[7, "tcp", "0.0.0.0/0", "2201", "2201", "ingress", "ssh"]
]
},
{
name = "MgmtAccess"
description = "Allow management access"
rules = [
[1, "tcp", "223.18.148.85/32", "22", "22", "ingress", "xpk"]
]
}
]
tags = local.default-tags
vpc-id = module.vpc-subnet.vpc_id
}
```
## Outputs
| Name | Description |
|------|-------------|
| sg-id-name | A map of SG id and their names |
+45
View File
@@ -0,0 +1,45 @@
resource "aws_security_group" "sg" {
count = length(var.security-groups)
name = var.security-groups[count.index].name
description = var.security-groups[count.index].description
vpc_id = var.vpc-id
tags = { Name = var.security-groups[count.index].name }
}
// see https://www.terraform.io/docs/configuration/functions/flatten.html
locals {
rules = flatten([
for sg_key, sg in var.security-groups : [
for rule_key, rule in sg.rules : {
sg_key = trimspace(sg.name)
rule_key = rule[0]
sg_name = sg.name
protocol = rule[1]
cidr_blocks = rule[2]
from_port = rule[3]
to_port = rule[4]
type = rule[5]
description = rule[6]
}
]
])
}
resource "aws_security_group_rule" "rules" {
for_each = {
for rule in local.rules : "${rule.sg_key}.${rule.rule_key}" => rule
}
security_group_id = matchkeys(aws_security_group.sg.*.id, aws_security_group.sg.*.name, [each.value.sg_name])[0]
protocol = each.value.protocol
source_security_group_id = substr(each.value.cidr_blocks,0,2) == "sg" ? each.value.cidr_blocks : null
cidr_blocks = substr(each.value.cidr_blocks,0,2) != "sg" ? [each.value.cidr_blocks] : null
from_port = each.value.from_port
to_port = each.value.to_port
type = each.value.type
description = "${each.value.description} (${each.value.sg_name}.${each.value.rule_key})"
}
@@ -0,0 +1,14 @@
/*
output sg-id-name {
value = [
for id, name in zipmap(
sort(aws_security_group.sg.*.id),
sort(aws_security_group.sg.*.name)) :
tomap(id, name)
]
}
*/
output sg-ids {
value = aws_security_group.sg.*.id
}
@@ -0,0 +1,3 @@
variable security-groups {}
variable vpc-id {}
variable tags {}
+43
View File
@@ -0,0 +1,43 @@
<!-- This readme file is generated with terraform-docs -->
## Requirements
No requirements.
## Providers
| Name | Version |
|------|---------|
| aws | n/a |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [aws_security_group.sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws_vpc_security_group_egress_rule.egress-rules](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource |
| [aws_vpc_security_group_ingress_rule.ingress-rules](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource |
| [aws_default_tags.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/default_tags) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| description | Description of SG | `string` | n/a | yes |
| egress | Map of string where each string is a comma-separated Egress SG rule. For example r1 = "-1,-1,-1,0.0.0.0/0,Allow All" | `map(string)` | n/a | yes |
| ingress | Map of string where each string is a comma-separated Ingress SG rule. For example r1 = "-1,-1,-1,0.0.0.0/0,Allow All" | `map(string)` | n/a | yes |
| name | Name of SG | `string` | n/a | yes |
| vpc-id | ID of VPC | `string` | n/a | yes |
## Outputs
| Name | Description |
|------|-------------|
| id | n/a |
---
## Authorship
This module was developed by xpk.
@@ -0,0 +1,32 @@
module "example-sg" {
source = "../"
name = "bastion-sg"
description = "SG of EC2 bastion instances"
vpc-id = "vpc-12345678"
ingress = {
r1 = "tcp,4750,4750,1.2.3.4/32,Patch Management Tool"
r2 = "tcp,22,22,1.2.3.4/32,Patch Management Tool"
r3 = "tcp,52311,52311,${aws_ec2_managed_prefix_list.example.id},BigFix server to client"
}
egress = {
r1 = "-1,-1,-1,0.0.0.0/0,Allow Ingress from all"
}
}
resource "aws_ec2_managed_prefix_list" "example" {
name = "Omprem subnets"
address_family = "IPv4"
max_entries = 5
dynamic "entry" {
for_each = toset([
"192.168.99.0/24",
"192.168.100.0/24"
])
content {
cidr = entry.value
description = "Onprem management subnets"
}
}
}
+39
View File
@@ -0,0 +1,39 @@
data "aws_default_tags" "this" {
lifecycle {
postcondition {
condition = length(self.tags) >= 1
error_message = "Validation failed: Provider default_tags not set."
}
}
}
resource "aws_security_group" "sg" {
name = var.name
description = var.description
vpc_id = var.vpc-id
tags = { Name = var.name }
}
resource "aws_vpc_security_group_ingress_rule" "ingress-rules" {
for_each = var.ingress
security_group_id = aws_security_group.sg.id
ip_protocol = split(",", each.value)[0]
from_port = split(",", each.value)[1]
to_port = split(",", each.value)[2]
cidr_ipv4 = substr(split(",", each.value)[3], 2, 1) != "-" ? split(",", each.value)[3] : null
referenced_security_group_id = substr(split(",", each.value)[3], 0, 2) == "sg" ? split(",", each.value)[3] : null
prefix_list_id = substr(split(",", each.value)[3], 0, 2) == "pl" ? split(",", each.value)[3] : null
description = split(",", each.value)[4]
}
resource "aws_vpc_security_group_egress_rule" "egress-rules" {
for_each = var.egress
security_group_id = aws_security_group.sg.id
ip_protocol = split(",", each.value)[0]
from_port = split(",", each.value)[1]
to_port = split(",", each.value)[2]
cidr_ipv4 = substr(split(",", each.value)[3], 2, 1) != "-" ? split(",", each.value)[3] : null
referenced_security_group_id = substr(split(",", each.value)[3], 0, 2) == "sg" ? split(",", each.value)[3] : null
prefix_list_id = substr(split(",", each.value)[3], 0, 2) == "pl" ? split(",", each.value)[3] : null
description = split(",", each.value)[4]
}
@@ -0,0 +1,3 @@
output id {
value = aws_security_group.sg.id
}
@@ -0,0 +1,20 @@
variable "name" {
description = "Name of SG"
type = string
}
variable "description" {
description = "Description of SG"
type = string
}
variable "vpc-id" {
description = "ID of VPC"
type = string
}
variable "ingress" {
description = "Map of string where each string is a comma-separated Ingress SG rule. For example r1 = \"-1,-1,-1,0.0.0.0/0,Allow All\""
type = map(string)
}
variable "egress" {
description = "Map of string where each string is a comma-separated Egress SG rule. For example r1 = \"-1,-1,-1,0.0.0.0/0,Allow All\""
type = map(string)
}
+43
View File
@@ -0,0 +1,43 @@
# security-groups-gen2
This module create security groups from a map
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:-----:|
| tags | tags | List | n/a | yes |
| vpc-id | VPC id | string | n/a | yes |
| security-groups | See example below | map | n/a | yes |
### security-groups input
Below is a sample security-groups map this module ingests. The rule list needs to have
the id column to prevent list from being randomly sorted.
```hcl
module "endpoint_sg" {
source = "../../../../whk1-bea-sys-ss-prd-codecommit-sharedmodules/Compute/sg_default_tags"
name = "vwhk1-bea-sys-ss-prd-vpc01-ep-sg"
description = "vpc endpoint security group"
egress-sg-rules = {
name = "outbound access"
rules = [
[1, "-1", "0.0.0.0/0", "0", "0", "outbound access"]
]
}
ingress-sg-rules = {
name = "HttpsAccessToVpcEndpoints"
rules = [
[1, "tcp", data.aws_vpc.vpc1.cidr_block, "443", "443", "TLS from VPC"]
]
}
vpc-id = var.vpc-id
}
```
## Outputs
| Name | Description |
|------|-------------|
| sg-id-name | A map of SG id and their names |
+71
View File
@@ -0,0 +1,71 @@
data "aws_default_tags" "this" {
lifecycle {
postcondition {
condition = length(self.tags) >= 1
error_message = "Validation failed: Provider default_tags not set."
}
}
}
resource "aws_security_group" "sg" {
name = var.name
description = var.description
vpc_id = var.vpc-id
}
// see https://www.terraform.io/docs/configuration/functions/flatten.html
locals {
ingress_rules = flatten([
for rule_key, rule in var.ingress-sg-rules.rules : {
sg_key = "ingress"
rule_key = rule[0]
protocol = rule[1]
cidr_blocks = rule[2]
from_port = rule[3]
to_port = rule[4]
description = rule[5]
}
])
egress_rules = flatten([
for rule_key, rule in var.egress-sg-rules.rules : {
sg_key = "egress"
rule_key = rule[0]
protocol = rule[1]
cidr_blocks = rule[2]
from_port = rule[3]
to_port = rule[4]
description = rule[5]
}
])
}
resource "aws_vpc_security_group_egress_rule" "egress_rules" {
for_each = {
for rule in local.egress_rules : "${rule.sg_key}.${rule.rule_key}" => rule
}
security_group_id = aws_security_group.sg.id
ip_protocol = each.value.protocol
referenced_security_group_id = substr(each.value.cidr_blocks, 0, 2) == "sg" ? each.value.cidr_blocks : null
cidr_ipv4 = substr(each.value.cidr_blocks, 0, 2) != "sg" ? each.value.cidr_blocks : null
from_port = each.value.protocol == "-1" ? null : each.value.from_port
to_port = each.value.protocol == "-1" ? null : each.value.to_port
description = each.value.description
}
resource "aws_vpc_security_group_ingress_rule" "ingress_rules" {
for_each = {
for rule in local.ingress_rules : "${rule.sg_key}.${rule.rule_key}" => rule
}
security_group_id = aws_security_group.sg.id
ip_protocol = each.value.protocol
referenced_security_group_id = substr(each.value.cidr_blocks, 0, 2) == "sg" ? each.value.cidr_blocks : null
cidr_ipv4 = substr(each.value.cidr_blocks, 0, 2) != "sg" ? each.value.cidr_blocks : null
from_port = each.value.protocol == "-1" ? null : each.value.from_port
to_port = each.value.protocol == "-1" ? null : each.value.to_port
description = each.value.description
}
@@ -0,0 +1,14 @@
/*
output sg-id-name {
value = [
for id, name in zipmap(
sort(aws_security_group.sg.*.id),
sort(aws_security_group.sg.*.name)) :
tomap(id, name)
]
}
*/
output sg-ids {
value = aws_security_group.sg.*.id
}
@@ -0,0 +1,6 @@
# variable security-groups {}
variable vpc-id {}
variable ingress-sg-rules {}
variable egress-sg-rules {}
variable name {}
variable description {}