initial commit
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 {}
|
||||
Reference in New Issue
Block a user