Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update security baseline components and add encrypted/custom AMI support #203

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f0cea5f
Added configuration for passing initial security baseline scanning.
Oct 27, 2021
7e971be
Test if null
Oct 27, 2021
1f838ab
Forgot validation at core module
Oct 27, 2021
3ed11e8
Don't change description for existing SGs to avoid force recreates.
Oct 27, 2021
ab3d7f2
Added the ability to have encrypted images supported by ASG
Oct 28, 2021
559721b
Enforce kms grant before creating ASG
Oct 28, 2021
604aa98
Add aws provider to tflint and update aws provider version
Oct 28, 2021
a697f40
Need 'can' for the variable check for cloudwatch
Oct 28, 2021
77be70a
Allow updating an existing aws-cli install
Oct 28, 2021
613eb7e
Use bash to run the installer
Oct 28, 2021
df78eb9
Add ami copy functionality
Oct 28, 2021
fa9ffb2
Fix tags for ami copy
Oct 28, 2021
7323e43
Remnant from testing.
Oct 28, 2021
f1d0f22
Wrong key for the encrypted value set
Oct 28, 2021
71d8cfc
Made requested changes from main module developers.
Nov 1, 2021
75ba37d
Removed ami search as requested and replaced with ami_id only which is a
Nov 1, 2021
7fbbfba
Reverted all non-security related changes in tests folder
Nov 1, 2021
2c84514
Forgot to run fmt
Nov 1, 2021
054efb0
Added back random descriptions...
Nov 1, 2021
ad30fd9
And added random blank lines back.
Nov 1, 2021
494015b
Updating versions for the remaining aws provider versions
Nov 1, 2021
c28145d
Added custom_fqdn flag
Nov 1, 2021
5f2bdb6
Add ACM cert which is missing on NLB https port
Nov 2, 2021
f58e23e
Change to TLS type rather than TCP
Nov 2, 2021
bf094cd
Try using same health-check for NLB as for ALB
Nov 2, 2021
547b81b
Properly name target-groups for NLB
Nov 2, 2021
ac1a985
Missing ID of the ami_id for test
Nov 2, 2021
81f766e
Default to hairpin advertisements
Feb 24, 2022
12528c6
User hairpin value of '1'
Feb 24, 2022
b8165cc
Add database tags
Feb 25, 2022
ed2c00d
Accidentally missed adding logging bucket to object_storage
Mar 9, 2022
776e3e9
Add tags to SG
Mar 10, 2022
dff3269
Added redirect http port for TFE
Apr 4, 2022
35d0b72
Try flipping off hairpinning
Mar 14, 2023
0a2444d
Update postgres minor version
Mar 14, 2023
9030ea3
Go back to hairpinning on
Mar 14, 2023
b63a294
Switch build runner to 'legacy'
Mar 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
repos:
aaron-lane marked this conversation as resolved.
Show resolved Hide resolved
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-symlinks
- id: check-yaml
- id: check-json
- id: check-xml
- id: detect-aws-credentials
- id: detect-private-key
- id: trailing-whitespace

- repo: git://github.com/antonbabenko/pre-commit-terraform
rev: v1.52.0
hooks:
- id: terraform_docs
args: ['--args=--indent 3']
- id: terraform_fmt
- id: terraform_tflint
- id: checkov
args: ['--quiet']
7 changes: 7 additions & 0 deletions .tflint.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ config {
disabled_by_default = false
}

plugin "aws" {
enabled = true
deep_check = false
source = "github.com/terraform-linters/tflint-ruleset-aws"
version = "0.8.0"
}

rule "terraform_deprecated_index" {
enabled = true
}
Expand Down
2 changes: 1 addition & 1 deletion examples/existing-image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## About This Example

This example functions as a reference for how to use this module to install
This example functions as a reference for how to use this module to install
Terraform Enterprise with a custom image (AMI) in AWS.

## Module Prerequisites
Expand Down
37 changes: 33 additions & 4 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
data "aws_region" "current" {}

# Seach for ubuntu AMI based on supplied AMI parameters
data "aws_ami" "ubuntu" {
most_recent = true

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
values = [var.ami_name_filter]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}

owners = ["099720109477"] # Canonical
owners = [var.ami_owner]
}

# Find encryption status of located image
locals {
block_device_mappings = {
for device in data.aws_ami.ubuntu.block_device_mappings : device.device_name => device
}
}

# If the user has selected to do a copy-down of the AMI to their local account, do so and use it as the default.
# This prevents AMI lifecycle events (disappearing AMIs for instance) from affecting ASG
resource "aws_ami_copy" "tfe_copy" {
count = var.ami_copy ? 1 : 0
name = "${var.friendly_name_prefix}-${data.aws_ami.ubuntu.name}-local-copy"
source_ami_id = data.aws_ami.ubuntu.id
source_ami_region = data.aws_region.current.name
encrypted = local.block_device_mappings["/dev/sda1"].ebs.encrypted
tags = {
Name = "${var.friendly_name_prefix}-${data.aws_ami.ubuntu.name}-local-copy"
}
aaron-lane marked this conversation as resolved.
Show resolved Hide resolved
}

resource "aws_kms_key" "tfe_key" {
deletion_window_in_days = var.kms_key_deletion_window
description = "AWS KMS Customer-managed key to encrypt TFE and other resources"
enable_key_rotation = false
enable_key_rotation = true
is_enabled = true
key_usage = "ENCRYPT_DECRYPT"

Expand All @@ -37,7 +58,8 @@ resource "aws_kms_alias" "key_alias" {

locals {
active_active = var.node_count >= 2
ami_id = local.default_ami_id ? data.aws_ami.ubuntu.id : var.ami_id
ami_id_fixup = coalesce(join("", aws_ami_copy.tfe_copy.*.id), data.aws_ami.ubuntu.id)
ami_id = local.default_ami_id ? local.ami_id_fixup : var.ami_id
default_ami_id = var.ami_id == ""
fqdn = "${var.tfe_subdomain}.${var.domain_name}"
}
Expand Down Expand Up @@ -106,10 +128,12 @@ module "database" {
db_backup_window = var.db_backup_window
engine_version = var.postgres_engine_version
friendly_name_prefix = var.friendly_name_prefix
monitoring_interval = var.db_monitoring_interval
network_id = local.network_id
network_private_subnet_cidrs = var.network_private_subnet_cidrs
network_subnets_private = local.network_private_subnets
tfe_instance_sg = module.vm.tfe_instance_sg
enabled_cloudwatch_logs = var.db_enabled_cloudwatch_logs
}

module "user_data" {
Expand Down Expand Up @@ -152,6 +176,8 @@ module "load_balancer" {
network_public_subnets = local.network_public_subnets
network_private_subnets = local.network_private_subnets
ssl_policy = var.ssl_policy
logging_bucket = var.logging_bucket
logging_prefix = var.logging_prefix
}

module "private_tcp_load_balancer" {
Expand All @@ -166,6 +192,8 @@ module "private_tcp_load_balancer" {
network_id = local.network_id
network_private_subnets = local.network_private_subnets
ssl_policy = var.ssl_policy
logging_bucket = var.logging_bucket
logging_prefix = var.logging_prefix
}

module "vm" {
Expand All @@ -174,6 +202,7 @@ module "vm" {
active_active = local.active_active
aws_iam_instance_profile = module.service_accounts.aws_iam_instance_profile
ami_id = local.ami_id
ami_kms_key_arn = var.ami_kms_key_arn
aws_lb = var.load_balancing_scheme == "PRIVATE_TCP" ? null : module.load_balancer[0].aws_lb_security_group
aws_lb_target_group_tfe_tg_443_arn = var.load_balancing_scheme == "PRIVATE_TCP" ? module.private_tcp_load_balancer[0].aws_lb_target_group_tfe_tg_443_arn : module.load_balancer[0].aws_lb_target_group_tfe_tg_443_arn
aws_lb_target_group_tfe_tg_8800_arn = var.load_balancing_scheme == "PRIVATE_TCP" ? module.private_tcp_load_balancer[0].aws_lb_target_group_tfe_tg_8800_arn : module.load_balancer[0].aws_lb_target_group_tfe_tg_8800_arn
Expand Down
29 changes: 21 additions & 8 deletions modules/application_load_balancer/main.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
resource "aws_security_group" "tfe_lb_allow" {
name = "${var.friendly_name_prefix}-tfe-lb-allow"
vpc_id = var.network_id
name = "${var.friendly_name_prefix}-tfe-lb-allow"
description = "Managed by Terraform"
vpc_id = var.network_id
}

resource "aws_security_group_rule" "tfe_lb_allow_inbound_http" {
Expand Down Expand Up @@ -35,8 +36,9 @@ resource "aws_security_group_rule" "tfe_lb_allow_inbound_dashboard" {
}

resource "aws_security_group" "tfe_outbound_allow" {
name = "${var.friendly_name_prefix}-tfe-outbound-allow"
vpc_id = var.network_id
name = "${var.friendly_name_prefix}-tfe-outbound-allow"
description = "Managed by Terraform"
vpc_id = var.network_id
}

resource "aws_security_group_rule" "tfe_outbound_allow_all" {
Expand All @@ -51,10 +53,21 @@ resource "aws_security_group_rule" "tfe_outbound_allow_all" {
}

resource "aws_lb" "tfe_lb" {
name = "${var.friendly_name_prefix}-tfe-web-alb"
internal = (var.load_balancing_scheme == "PRIVATE")
load_balancer_type = "application"
subnets = var.load_balancing_scheme == "PRIVATE" ? var.network_private_subnets : var.network_public_subnets
#checkov:skip=CKV_AWS_150:We will expect that people might want to spin up/down infrastructure quickly. Skip checking for delete protection.
name = "${var.friendly_name_prefix}-tfe-web-alb"
internal = (var.load_balancing_scheme == "PRIVATE")
load_balancer_type = "application"
subnets = var.load_balancing_scheme == "PRIVATE" ? var.network_private_subnets : var.network_public_subnets
drop_invalid_header_fields = true

dynamic "access_logs" {
for_each = var.logging_bucket != null ? [var.logging_bucket] : []
content {
bucket = var.logging_bucket
prefix = var.logging_prefix
enabled = true
}
}

security_groups = [
aws_security_group.tfe_lb_allow.id,
Expand Down
13 changes: 13 additions & 0 deletions modules/application_load_balancer/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ variable "load_balancing_scheme" {
}

variable "ssl_policy" {
default = "ELBSecurityPolicy-TLS-1-2-2017-01"
description = "The name of the SSL policy to assign to the load balancer listeners."
type = string
}
Expand Down Expand Up @@ -57,3 +58,15 @@ variable "friendly_name_prefix" {
type = string
description = "(Required) Friendly name prefix used for tagging and naming AWS resources."
}

variable "logging_bucket" {
type = string
description = "S3 bucket name for logging of resources to. Requires a bucket in the same region that TFE is in."
default = null
}

variable "logging_prefix" {
type = string
description = "Optional prefix to prepend to TFE resource logs in S3 bucket"
default = null
}
46 changes: 27 additions & 19 deletions modules/database/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ resource "aws_security_group" "postgresql" {

resource "aws_security_group_rule" "postgresql_tfe_ingress" {
security_group_id = aws_security_group.postgresql.id
description = "Inbound traffic to postgresql port from TFE"
type = "ingress"
from_port = 5432
to_port = 5432
Expand All @@ -20,6 +21,7 @@ resource "aws_security_group_rule" "postgresql_tfe_ingress" {

resource "aws_security_group_rule" "postgresql_tfe_egress" {
security_group_id = aws_security_group.postgresql.id
description = "Outbound ICMP traffic from RDS"
type = "egress"
from_port = 0
to_port = 0
Expand All @@ -29,6 +31,7 @@ resource "aws_security_group_rule" "postgresql_tfe_egress" {

resource "aws_security_group_rule" "postgresql_ingress" {
security_group_id = aws_security_group.postgresql.id
description = "Inbound traffic to postgresql port from VPC"
type = "ingress"
from_port = 5432
to_port = 5432
Expand All @@ -38,6 +41,7 @@ resource "aws_security_group_rule" "postgresql_ingress" {

resource "aws_security_group_rule" "postgresql_egress" {
security_group_id = aws_security_group.postgresql.id
description = "Outbound traffic from postgresql port to VPC"
type = "egress"
from_port = 5432
to_port = 5432
Expand All @@ -51,31 +55,35 @@ resource "aws_db_subnet_group" "tfe" {
}

resource "aws_db_instance" "postgresql" {
#checkov:skip=CKV_AWS_129:We allow enabling this, but it's not on by default so skip the check.
allocated_storage = 20
engine = "postgres"
instance_class = var.db_size
password = random_string.postgresql_password.result
# no special characters allowed
username = "espdtfe"

allow_major_version_upgrade = false
apply_immediately = true
auto_minor_version_upgrade = true
backup_retention_period = var.db_backup_retention
backup_window = var.db_backup_window
db_subnet_group_name = aws_db_subnet_group.tfe.name
delete_automated_backups = true
deletion_protection = false
engine_version = var.engine_version
identifier_prefix = "${var.friendly_name_prefix}-tfe"
max_allocated_storage = 0
multi_az = true
iam_database_authentication_enabled = true
allow_major_version_upgrade = false
apply_immediately = true
auto_minor_version_upgrade = true
backup_retention_period = var.db_backup_retention
backup_window = var.db_backup_window
db_subnet_group_name = aws_db_subnet_group.tfe.name
delete_automated_backups = true
deletion_protection = false
engine_version = var.engine_version
identifier_prefix = "${var.friendly_name_prefix}-tfe"
max_allocated_storage = 0
multi_az = true
monitoring_interval = var.monitoring_interval
# no special characters allowed
name = "espdtfe"
port = 5432
publicly_accessible = false
skip_final_snapshot = true
storage_encrypted = true
storage_type = "gp2"
vpc_security_group_ids = [aws_security_group.postgresql.id]
name = "espdtfe"
port = 5432
publicly_accessible = false
skip_final_snapshot = true
storage_encrypted = true
storage_type = "gp2"
vpc_security_group_ids = [aws_security_group.postgresql.id]
enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs
}
17 changes: 17 additions & 0 deletions modules/database/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ variable "engine_version" {
description = "PostgreSQL version."
}

variable "monitoring_interval" {
type = number
description = "Interval in seconds for monitoring of the RDS database. Any value other than null will enable enhanced monitoring."
default = null
}

variable "network_subnets_private" {
description = <<-EOD
A list of the identities of the private subnetworks in which the PostgreSQL RDS instance will be deployed.
Expand All @@ -52,3 +58,14 @@ variable "network_private_subnet_cidrs" {
description = "(Optional) List of private subnet CIDR ranges to create in VPC."
default = ["10.0.32.0/20", "10.0.48.0/20"]
}


variable "enabled_cloudwatch_logs" {
type = list(string)
description = "List of enabled cloudwatch log export types. From list here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance#enabled_cloudwatch_logs_exports"
default = null
validation {
condition = var.enabled_cloudwatch_logs != null ? can(contains(["postgresql", "upgrade"], var.enabled_cloudwatch_logs)) : true
error_message = "Allowed cloudwatch log export types don't match allowed. Must be: postgresql, upgrade."
}
}
19 changes: 15 additions & 4 deletions modules/network_load_balancer/main.tf
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
resource "aws_lb" "tfe_lb" {
name = "${var.friendly_name_prefix}-tfe-nlb"
internal = true
load_balancer_type = "network"
subnets = var.network_private_subnets
#checkov:skip=CKV_AWS_150:Given we are automating this for the most part we can recover from a deleted load-balancer. Okay for now.
name = "${var.friendly_name_prefix}-tfe-nlb"
internal = true
load_balancer_type = "network"
enable_cross_zone_load_balancing = true
subnets = var.network_private_subnets

dynamic "access_logs" {
for_each = var.logging_bucket != null ? [var.logging_bucket] : []
content {
bucket = var.logging_bucket
prefix = var.logging_prefix
enabled = true
}
}
}

resource "aws_lb_listener" "tfe_listener_443" {
Expand Down
12 changes: 12 additions & 0 deletions modules/network_load_balancer/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,15 @@ variable "friendly_name_prefix" {
type = string
description = "(Required) Friendly name prefix used for tagging and naming AWS resources."
}

variable "logging_bucket" {
type = string
description = "S3 bucket name for logging of resources to. Requires a bucket in the same region that TFE is in."
default = null
}

variable "logging_prefix" {
type = string
description = "Optional prefix to prepend to TFE resource logs in S3 bucket"
default = null
}
9 changes: 9 additions & 0 deletions modules/object_storage/main.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
resource "aws_s3_bucket" "tfe_data_bucket" {
#checkov:skip=CKV_AWS_144:While cross-region might make sense someday, right now it doesn't for TFE
bucket = "${var.friendly_name_prefix}-tfe-data"
acl = "private"

Expand All @@ -15,6 +16,14 @@ resource "aws_s3_bucket" "tfe_data_bucket" {
}
}

dynamic "logging" {
for_each = var.logging_bucket == null ? [] : [var.logging_bucket]
content {
target_bucket = logging.value
target_prefix = var.logging_prefix
}
}

force_destroy = true
}

Expand Down
Loading