diff --git a/modules/ithc-ingress-without-rds-caution/README.md b/modules/ithc-ingress-without-rds-caution/README.md new file mode 100644 index 00000000..0ff0fd86 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/README.md @@ -0,0 +1,39 @@ +# ITHC Ingress - Caution! + +This module introduces resources which deliberately bypass and / or reduce the efficacy of existing security measures. + +It's intended for use during an IT Health Check / Penetration Test situation. + +> You are **STRONGLY** advised never to use this module in production. + +## Adding Ingress in your project + +This module is designed to be reusable and temporary. To optimise on both these attributes it's advised to implement it as follows: + +### Locate the module invocation separately + +Invoke the module using the typical Terraform `module` construct. + +It's advised to put this block into the top-level of your environment folder, as a separate file with a name such as `ithc_ingress.tf` (so, for example, `environments/staging/ithc_ingress.tf`). There are a few reasons for this approach: + +1. It shows with a glance of the folder that this environment has ITHC ingress set up +2. It stops the `main.tf` becoming cluttered +3. When you are finished testing, each of the resources and components can be removed from your platform by simply deleting this file and re-applying the Terraform. + +## What gets created + +This module adds the following: + +* An IAM user for ITHC audit (the ARN for this is in the module outputs) +* An IAM group for ITHC audit - the audit user is placed into this group and the group has policies: + * ReadOnlyAccess + * SecurityAudit + * A custom policy as defined in [this file](ithc_iam_user.tf) which allows key management, MFA management, etc and blocks SSM access (among other things) +* An EC2 instance for VPC Scanning (the public DNS name for this is in the module outputs, and the username to use for ssh connection is `kali`) + +## Origins + +Adopted from: + +* https://github.com/Crown-Commercial-Service/ccs-corporate-website-terraform/tree/main/cgi_ithc +* https://github.com/Crown-Commercial-Service/ccs-digital-foundation-terraform/tree/main/cgi_ithc diff --git a/modules/ithc-ingress-without-rds-caution/ithc_iam_user.tf b/modules/ithc-ingress-without-rds-caution/ithc_iam_user.tf new file mode 100644 index 00000000..da540023 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/ithc_iam_user.tf @@ -0,0 +1,108 @@ +resource "aws_iam_user" "ithc_audit" { + name = "${var.resource_name_prefixes.hyphens_lower}-ithc-audit" +} + +resource "aws_iam_group" "ithc_audit" { + name = "${var.resource_name_prefixes.hyphens_lower}-ithc-audit" +} + +resource "aws_iam_user_group_membership" "ithc_audit" { + groups = [ + aws_iam_group.ithc_audit.name + ] + user = aws_iam_user.ithc_audit.name +} + +resource "aws_iam_group_policy_attachment" "ithc_audit__read_only_access" { + group = aws_iam_group.ithc_audit.name + policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess" +} + +resource "aws_iam_group_policy_attachment" "ithc_audit__security_audit" { + group = aws_iam_group.ithc_audit.name + policy_arn = "arn:aws:iam::aws:policy/SecurityAudit" +} + +data "aws_iam_policy_document" "custom_ithc_access_rules" { + statement { + sid = "AllowAccessKeyManagement" + effect = "Allow" + actions = [ + "iam:DeleteAccessKey", + "iam:UpdateAccessKey", + "iam:CreateAccessKey", + "iam:ListAccessKeys" + ] + resources = [ + "arn:aws:iam::*:user/$${aws:username}" + ] + } + + statement { + sid = "AllowManageOwnVirtualMFADevice" + effect = "Allow" + actions = [ + "iam:DeleteVirtualMFADevice", + "iam:CreateVirtualMFADevice", + ] + resources = [ + "arn:aws:iam::*:mfa/$${aws:username}" + ] + } + + statement { + sid = "AllowManageOwnUserMFA" + effect = "Allow" + actions = [ + "iam:ResyncMFADevice", + "iam:ListMFADevices", + "iam:EnableMFADevice", + "iam:DeactivateMFADevice", + ] + resources = [ + "arn:aws:iam::*:user/$${aws:username}" + ] + } + + statement { + sid = "BlockMostAccessUnlessSignedInWithMFA" + effect = "Deny" + condition { + test = "Bool" + values = [false] + variable = "aws:MultiFactorAuthPresent" + } + not_actions = [ + "sts:GetSessionToken", + "iam:ResyncMFADevice", + "iam:ListVirtualMFADevices", + "iam:ListUsers", + "iam:ListServiceSpecificCredentials", + "iam:ListSSHPublicKeys", + "iam:ListMFADevices", + "iam:ListAccountAliases", + "iam:ListAccessKeys", + "iam:GetAccountSummary", + "iam:EnableMFADevice", + "iam:DeleteVirtualMFADevice", + "iam:CreateVirtualMFADevice", + ] + resources = ["*"] + } + + statement { + sid = "DenyAllSSMAccess" + effect = "Deny" + actions = [ + "ssm:GetParameter*" + ] + resources = [ + "arn:aws:ssm:*:*:*" + ] + } +} + +resource "aws_iam_group_policy" "ithc_audit__custom_rules" { + group = aws_iam_group.ithc_audit.name + policy = data.aws_iam_policy_document.custom_ithc_access_rules.json +} diff --git a/modules/ithc-ingress-without-rds-caution/network_acls.tf b/modules/ithc-ingress-without-rds-caution/network_acls.tf new file mode 100644 index 00000000..06326596 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/network_acls.tf @@ -0,0 +1,14 @@ +# Override existing NACL - For rules on overriding see: +# https://github.com/Crown-Commercial-Service/ccs-migration-alpha-tools/tree/main/modules/four-tier-vpc#network-acls-and-customisation-of +# +resource "aws_network_acl_rule" "public__allow_ssh_everywhere_in" { + for_each = toset(var.ithc_operative_cidr_safelist) + cidr_block = each.value + egress = false + from_port = 22 + network_acl_id = var.public_subnets_nacl_id + protocol = "tcp" + rule_action = "allow" + rule_number = index(var.ithc_operative_cidr_safelist, each.value) + 1 + to_port = 22 +} diff --git a/modules/ithc-ingress-without-rds-caution/outputs.tf b/modules/ithc-ingress-without-rds-caution/outputs.tf new file mode 100644 index 00000000..0d067f06 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/outputs.tf @@ -0,0 +1,9 @@ +output "ithc_audit_iam_user_arn" { + description = "ARN of the IAM user created for ITHC audit" + value = aws_iam_user.ithc_audit.arn +} + +output "vpc_scanner_public_dns" { + description = "Public DNS name of the VPC Scanner instance" + value = aws_instance.vpc_scanner.public_dns +} diff --git a/modules/ithc-ingress-without-rds-caution/providers.tf b/modules/ithc-ingress-without-rds-caution/providers.tf new file mode 100644 index 00000000..8912fbb9 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/providers.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">=5.26.0" + } + } +} diff --git a/modules/ithc-ingress-without-rds-caution/variables.tf b/modules/ithc-ingress-without-rds-caution/variables.tf new file mode 100644 index 00000000..a90ad2d3 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/variables.tf @@ -0,0 +1,50 @@ +# See naming convention doc: +# https://crowncommercialservice.atlassian.net/wiki/spaces/GPaaS/pages/3561685032/AWS+3+Tier+Reference+Architecture +variable "ithc_operative_cidr_safelist" { + type = list(string) + description = "List of CIDR ranges to be allowed to access the EC2 instances" +} + +variable "public_subnets_nacl_id" { + type = string + description = "The ID of the existing NACL for public subnets" +} + +variable "resource_name_prefixes" { + type = object({ + normal = string, + hyphens = string, + hyphens_lower = string, + }) + description = "Prefix to apply to resources in AWS; options provided to satisfy divergent naming requirements across AWS" +} + +variable "vpc_cidr_block" { + type = string + description = "CIDR block of the VPC" +} + +variable "vpc_id" { + type = string + description = "The ID of the VPC" +} + +variable "vpc_scanner_instance_public_key" { + type = string + description = "Single-line public key (e.g. 'ssh-ed25519 AAAAver3rbrbr')" +} + +variable "vpc_scanner_instance_root_device_size_gb" { + type = number + description = "Required size in GB of the root device for the VPC Scanner instance" +} + +variable "vpc_scanner_instance_subnet_id" { + type = string + description = "ID of the subnet for the VPC Scanner instance - probably a public one" +} + +variable "vpc_scanner_instance_type" { + type = string + description = "Instance type for the VPC Scanner instance" +} diff --git a/modules/ithc-ingress-without-rds-caution/vpc_scanner_instance.tf b/modules/ithc-ingress-without-rds-caution/vpc_scanner_instance.tf new file mode 100644 index 00000000..e182a632 --- /dev/null +++ b/modules/ithc-ingress-without-rds-caution/vpc_scanner_instance.tf @@ -0,0 +1,107 @@ +# Instance used to scan for vulnerabilities from inside the VPC. +# +resource "aws_key_pair" "vpc_scanner" { + key_name = "${var.resource_name_prefixes.hyphens_lower}-vpc-scanner" + public_key = var.vpc_scanner_instance_public_key +} + +data "aws_ami" "kali_pinned_ami" { + most_recent = true + + filter { + name = "name" + values = ["kali-last-snapshot-amd64-2023.3.0-804fcc46-63fc-4eb6-85a1-50e66d6c7215"] + } + + owners = [ + "aws-marketplace" + ] +} + +resource "aws_instance" "vpc_scanner" { + associate_public_ip_address = true + ami = data.aws_ami.kali_pinned_ami.id + instance_type = var.vpc_scanner_instance_type + key_name = aws_key_pair.vpc_scanner.key_name + subnet_id = var.vpc_scanner_instance_subnet_id + vpc_security_group_ids = [ + aws_security_group.vpc_scanner_instance.id, + ] + + root_block_device { + encrypted = true + volume_size = var.vpc_scanner_instance_root_device_size_gb + } + + tags = { + "Name" = "${var.resource_name_prefixes.normal}:EC2:VPCSCAN" + } +} + +resource "aws_security_group" "vpc_scanner_instance" { + name = "${var.resource_name_prefixes.normal}:EC2:VPCSCAN" + description = "EC2 instance for VPC scanning" + vpc_id = var.vpc_id + + tags = { + Name = "${var.resource_name_prefixes.normal}:EC2:VPCSCAN" + } +} + +resource "aws_security_group_rule" "vpc_scanner_instance_ssh_in" { + cidr_blocks = var.ithc_operative_cidr_safelist + description = "Allow SSH from approved ranges into the VPC Scanner instance" + from_port = 22 + protocol = "tcp" + security_group_id = aws_security_group.vpc_scanner_instance.id + to_port = 22 + type = "ingress" +} + +resource "aws_security_group_rule" "vpc_scanner_http_anywhere_out" { + cidr_blocks = [ + "0.0.0.0/0", + ] + description = "Allows HTTP to anywhere" + from_port = 80 + to_port = 80 + protocol = "tcp" + security_group_id = aws_security_group.vpc_scanner_instance.id + type = "egress" +} + +resource "aws_security_group_rule" "vpc_scanner_https_anywhere_out" { + cidr_blocks = [ + "0.0.0.0/0", + ] + description = "Allows HTTPS to anywhere" + from_port = 443 + to_port = 443 + protocol = "tcp" + security_group_id = aws_security_group.vpc_scanner_instance.id + type = "egress" +} + +resource "aws_security_group_rule" "vpc_scanner_all_tcp_vpc_out" { + cidr_blocks = [ + var.vpc_cidr_block + ] + description = "Any TCP within VPC" + from_port = 0 + to_port = 65535 + protocol = "tcp" + security_group_id = aws_security_group.vpc_scanner_instance.id + type = "egress" +} + +resource "aws_security_group_rule" "vpc_scanner_all_udp_vpc_out" { + cidr_blocks = [ + var.vpc_cidr_block + ] + description = "Any UDP within VPC" + from_port = 0 + to_port = 65535 + protocol = "udp" + security_group_id = aws_security_group.vpc_scanner_instance.id + type = "egress" +}