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

Add VPC endpoints #93

Merged
merged 23 commits into from
Jan 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6b091f0
Remove NAT gateway and associated route
jsf9k Jan 21, 2021
a16b36a
Use a single route table for all private subnets
jsf9k Jan 21, 2021
da4cf75
Add permissions for creating, modifying, and deleting VPC endpoints
jsf9k Jan 21, 2021
6e68e6d
Add security groups for instances using VPC endpoints to access the S…
jsf9k Jan 21, 2021
b1e39fa
Add VPC endpoints requires to use the STS, SSM, and S3 services
jsf9k Jan 21, 2021
1464d41
Add a route for S3 access via the S3 VPC gateway endpoint
jsf9k Jan 21, 2021
1941acd
Modify desktop gateway SG rules to use VPC endpoints
jsf9k Jan 21, 2021
f108ee6
Add some CloudWatch VPC interface endpoints
jsf9k Jan 21, 2021
62be561
Allow operations subnet to use the VPC endpoints in private subnet
jsf9k Jan 21, 2021
d3ee217
Add a route so the operations subnet can use the S3 gateway endpoint
jsf9k Jan 21, 2021
597a541
Revert "Remove NAT gateway and associated route"
jsf9k Jan 21, 2021
a8ec8bb
Add SG and ACL rules to allow guacamole to use the NAT GW for HTTPS
jsf9k Jan 21, 2021
7095452
Improve some ACL rule comments
jsf9k Jan 22, 2021
24d3c09
Allow the operations subnet to use the STS VPC endpoint
jsf9k Jan 22, 2021
6bc7a6a
Specify the region name and region-specific endpoint URL for boto3 ST…
jsf9k Jan 22, 2021
864048c
Specify the region name and region-specific endpoint URL for AWS CLI …
jsf9k Jan 22, 2021
93fa57e
Consistently name AWS region variable
jsf9k Jan 22, 2021
5c087ea
Add issue link to a comment
jsf9k Jan 22, 2021
5a40a3a
Update comment to agree with reality
jsf9k Jan 22, 2021
02d26ca
Capitalize "Guacamole" and "Docker" in comments
jsf9k Jan 26, 2021
2d42f57
Add comments to clearly separate VPC interface and gateway endpoints
jsf9k Jan 26, 2021
9ad872d
Clean up comment formatting
jsf9k Jan 26, 2021
d9efdca
Reword comment for consistency with similar comments made elsewhere
jsf9k Jan 26, 2021
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
21 changes: 20 additions & 1 deletion cloud-init/install-certificates.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import boto3

# Inputs from terraform
AWS_REGION = "${aws_region}"
CERT_BUCKET_NAME = "${cert_bucket_name}"
CERT_READ_ROLE_ARN = "${cert_read_role_arn}"
SERVER_FQDN = "${server_fqdn}"
Expand All @@ -24,7 +25,25 @@
}

# Create STS client
sts = boto3.client("sts")
#
# STS used to be un-regioned, like S3, but now it is regioned. This
# is the one case where boto3 _does not_ do the right thing when you
# set the region. We have to set the region-specific endpoint URL
# manually.
#
# This is important since the STS VPC endpoint _only_ sets a local DNS
# record to override the _local region's_ public STS endpoint. If we
# don't set the endpoint URL then boto3 will reach out to the _global_
# https://sts.amazonaws.com URL, and that DNS entry will still point
# to an external IP.
#
# See this link for more information about boto3's perverse behavior
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boto3's perverse behavior

🤣

# in the case of STS: https://github.com/boto/boto3/issues/1859.
sts = boto3.client(
"sts",
region_name=AWS_REGION,
endpoint_url=f"https://sts.{AWS_REGION}.amazonaws.com",
)

# Assume the role that can read the certificate
stsresponse = sts.assume_role(
Expand Down
6 changes: 5 additions & 1 deletion cloud-init/nessus-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ activation_code_to_apply="${nessus_activation_code}"
echo "Assuming role that can read Nessus-related SSM Parameter Store parameters"

# shellcheck disable=SC2154
assumed_role_output=$(aws sts assume-role --role-arn "${ssm_nessus_read_role_arn}" --role-session-name "cloud-init-nessus-setup")
assumed_role_output=$(aws --region "${aws_region}" \
--endpoint-url "https://sts.${aws_region}.amazonaws.com" \
sts assume-role \
--role-arn "${ssm_nessus_read_role_arn}" \
--role-session-name "cloud-init-nessus-setup")

aws_access_key_id=$(echo "$assumed_role_output" | jq -r .Credentials.AccessKeyId)
export AWS_ACCESS_KEY_ID=$aws_access_key_id
Expand Down
23 changes: 21 additions & 2 deletions cloud-init/render-guac-connection-sql-template.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)

# Inputs from terraform
AWS_REGION = "${aws_region}"
SSM_READ_ROLE_ARN = "${ssm_vnc_read_role_arn}"
# nosec on following line tells bandit (pre-commit hook) to ignore security
# warnings; otherwise bandit complains about "Possible hardcoded password"
Expand All @@ -31,7 +32,25 @@
SSM_KEY_VNC_USER_PRIVATE_SSH_KEY = "${ssm_key_vnc_user_private_ssh_key}"

# Create STS client
sts = boto3.client("sts")
#
jsf9k marked this conversation as resolved.
Show resolved Hide resolved
# STS used to be un-regioned, like S3, but now it is regioned. This
# is the one case where boto3 _does not_ do the right thing when you
# set the region. We have to set the region-specific endpoint URL
# manually.
#
# This is important since the STS VPC endpoint _only_ sets a local DNS
# record to override the _local region's_ public STS endpoint. If we
# don't set the endpoint URL then boto3 will reach out to the _global_
# https://sts.amazonaws.com URL, and that DNS entry will still point
# to an external IP.
#
# See this link for more information about boto3's perverse behavior
# in the case of STS: https://github.com/boto/boto3/issues/1859.
sts = boto3.client(
"sts",
region_name=AWS_REGION,
endpoint_url=f"https://sts.{AWS_REGION}.amazonaws.com",
)

# Assume the role that can read the SSM parameters
stsresponse = sts.assume_role(
Expand All @@ -44,7 +63,7 @@
# Create a new client to access SSM using the temporary credentials
ssm = boto3.client(
"ssm",
region_name="${aws_region}",
region_name=AWS_REGION,
aws_access_key_id=newsession_id,
aws_secret_access_key=newsession_key,
aws_session_token=newsession_token,
Expand Down
47 changes: 47 additions & 0 deletions cloudwatch_endpoint_sg_rules.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Allow ingress via HTTPS from the desktop gateway security group
resource "aws_security_group_rule" "ingress_from_desktop_gw_to_cloudwatch_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.cloudwatch.id
type = "ingress"
protocol = "tcp"
source_security_group_id = aws_security_group.desktop_gateway.id
from_port = 443
to_port = 443
}

# Allow ingress via HTTPS from the operations security group
resource "aws_security_group_rule" "ingress_from_operations_to_cloudwatch_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.cloudwatch.id
type = "ingress"
protocol = "tcp"
source_security_group_id = aws_security_group.operations.id
from_port = 443
to_port = 443
}

# Allow ingress via HTTPS from the PenTest Portal security group
resource "aws_security_group_rule" "ingress_from_pentestportal_to_cloudwatch_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.cloudwatch.id
type = "ingress"
protocol = "tcp"
source_security_group_id = aws_security_group.pentestportal.id
from_port = 443
to_port = 443
}

# Allow ingress via HTTPS from the Debian Desktop security group
resource "aws_security_group_rule" "ingress_from_debiandesktop_to_cloudwatch_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.cloudwatch.id
type = "ingress"
protocol = "tcp"
source_security_group_id = aws_security_group.debiandesktop.id
from_port = 443
to_port = 443
}
69 changes: 65 additions & 4 deletions desktop_gateway_sg_rules.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ resource "aws_security_group_rule" "desktop_gw_egress_to_ops_via_ssh" {
to_port = 22
}

# Allow egress via https to anywhere
# For: Guacamole fetches its SSL certificate via boto3 (which uses HTTPS)
resource "aws_security_group_rule" "desktop_gw_egress_to_anywhere_via_https" {
# Allow egress via https
#
# For: Guacamole access to DockerHub via the NAT gateway
resource "aws_security_group_rule" "desktop_gw_egress_anywhere_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.desktop_gateway.id
Expand All @@ -24,8 +25,68 @@ resource "aws_security_group_rule" "desktop_gw_egress_to_anywhere_via_https" {
to_port = 443
}

# Allow egress via https to any STS interface endpoint
#
# For: Guacamole assumes a role via STS. This role allows Guacamole
# to then fetch its SSL certificate from S3.
resource "aws_security_group_rule" "desktop_gw_egress_to_sts_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.desktop_gateway.id
type = "egress"
protocol = "tcp"
source_security_group_id = aws_security_group.sts.id
from_port = 443
to_port = 443
}

# Allow egress via https to any SSM interface endpoints
#
# For: Guacamole requires access to SSM for ssh access via the AWS
# control plane.
resource "aws_security_group_rule" "desktop_gw_egress_to_ssm_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.desktop_gateway.id
type = "egress"
protocol = "tcp"
source_security_group_id = aws_security_group.ssm.id
from_port = 443
to_port = 443
}

# Allow egress via https to any Cloudwatch interface endpoints
#
# For: Guacamole requires access to CloudWatch for CloudWatch log
# forwarding via the CloudWatch agent.
resource "aws_security_group_rule" "desktop_gw_egress_to_cloudwatch_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.desktop_gateway.id
type = "egress"
protocol = "tcp"
source_security_group_id = aws_security_group.cloudwatch.id
from_port = 443
to_port = 443
}

# Allow egress via https to the S3 gateway endpoint
#
# For: Guacamole requires access to S3 in order to download its
# certificate.
resource "aws_security_group_rule" "desktop_gw_egress_to_s3_via_https" {
provider = aws.provisionassessment

security_group_id = aws_security_group.desktop_gateway.id
type = "egress"
protocol = "tcp"
prefix_list_ids = [aws_vpc_endpoint.s3.prefix_list_id]
from_port = 443
to_port = 443
}

# Allow ingress from COOL Shared Services VPN server CIDR block
# via port 443 (nginx/guacamole web)
# via port 443 (nginx/Guacamole web)
# For: Assessment team access to Guacamole web client
resource "aws_security_group_rule" "desktop_gw_ingress_from_trusted_via_port_443" {
provider = aws.provisionassessment
Expand Down
1 change: 1 addition & 0 deletions guacamole_cloud_init.tf
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ data "cloudinit_config" "guacamole_cloud_init_tasks" {
content_type = "text/x-shellscript"
content = templatefile(
"${path.module}/cloud-init/install-certificates.py", {
aws_region = var.aws_region
cert_bucket_name = var.cert_bucket_name
cert_read_role_arn = module.guacamole_certreadrole.role.arn
server_fqdn = local.guacamole_fqdn
Expand Down
54 changes: 43 additions & 11 deletions operations_acl_rules.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Allow ingress from private subnet via ssh
#
# For: DevOps ssh access from private subnet to operations subnet
resource "aws_network_acl_rule" "operations_ingress_from_private_via_ssh" {
provider = aws.provisionassessment
Expand All @@ -15,7 +16,9 @@ resource "aws_network_acl_rule" "operations_ingress_from_private_via_ssh" {
}

# Allow ingress from private subnet via VNC
# For: Assessment team VNC access from private subnet to operations subnet
#
# For: Assessment team VNC access from private subnet to operations
# subnet
resource "aws_network_acl_rule" "operations_ingress_from_private_via_vnc" {
provider = aws.provisionassessment
for_each = toset(var.private_subnet_cidr_blocks)
Expand All @@ -30,8 +33,26 @@ resource "aws_network_acl_rule" "operations_ingress_from_private_via_vnc" {
to_port = 5901
}

# Allow ingress from the private subnets via port 443. This is
# necessary so that the Guacamole instance can download the Docker
# images used in the Docker composition via the NAT gateway.
resource "aws_network_acl_rule" "operations_ingress_from_private_via_https" {
provider = aws.provisionassessment
for_each = toset(var.private_subnet_cidr_blocks)

network_acl_id = aws_network_acl.operations.id
egress = false
protocol = "tcp"
rule_number = 104 + index(var.private_subnet_cidr_blocks, each.value)
rule_action = "allow"
cidr_block = aws_subnet.private[each.value].cidr_block
from_port = 443
to_port = 443
}

# Allow ingress from anywhere via the TCP ports specified in
# var.operations_subnet_inbound_tcp_ports_allowed
#
# For: Assessment team operational use
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_allowed_tcp_ports" {
provider = aws.provisionassessment
Expand All @@ -49,6 +70,7 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_allowed_tc

# Allow ingress from anywhere via the UDP ports specified in
# var.operations_subnet_inbound_udp_ports_allowed
#
# For: Assessment team operational use
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_allowed_udp_ports" {
provider = aws.provisionassessment
Expand All @@ -64,9 +86,11 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_allowed_ud
to_port = each.value["to"]
}

# Allow ingress from anywhere via ephemeral TCP/UDP ports below 3389 (1024-3388)
# For: Assessment team operational use, but don't want to allow
# public access to RDP on port 3389
# Allow ingress from anywhere via ephemeral TCP/UDP ports below 3389
# (1024-3388)
#
# For: Assessment team operational use, but don't want to allow public
# access to RDP on port 3389
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_1024_thru_3388" {
provider = aws.provisionassessment
for_each = toset(local.tcp_and_udp)
Expand All @@ -82,8 +106,9 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_1024
}

# Allow ingress from anywhere via ephemeral TCP/UDP ports 3390-5900
# For: Assessment team operational use, but don't want to allow
# public access to RDP on port 3389 or VNC on port 5901
#
# For: Assessment team operational use, but don't want to allow public
# access to RDP on port 3389 or VNC on port 5901
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_3390_thru_5900" {
provider = aws.provisionassessment
for_each = toset(local.tcp_and_udp)
Expand All @@ -99,9 +124,9 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_3390
}

# Allow ingress from anywhere via ephemeral TCP/UDP ports 5901-50049
# For: Assessment team operational use, but don't want to allow
# public access to VNC on port 5901 or Cobalt Strike teamserver
# on port 50050
#
# For: Assessment team operational use, but don't want to allow public
# access to VNC on port 5901 or Cobalt Strike teamserver on port 50050
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_5902_thru_50049" {
provider = aws.provisionassessment
for_each = toset(local.tcp_and_udp)
Expand All @@ -117,8 +142,9 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_5902
}

# Allow ingress from anywhere via ephemeral TCP/UDP ports 50051-65535
# For: Assessment team operational use, but don't want to allow
# public access to Cobalt Strike teamserver on port 50050
#
# For: Assessment team operational use, but don't want to allow public
# access to Cobalt Strike teamserver on port 50050
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_50051_thru_65535" {
provider = aws.provisionassessment
for_each = toset(local.tcp_and_udp)
Expand All @@ -134,6 +160,7 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_ports_5005
}

# Allow ingress from anywhere via ICMP
#
# For: Assessment team operational use (e.g. ping responses)
resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_icmp" {
provider = aws.provisionassessment
Expand All @@ -149,7 +176,12 @@ resource "aws_network_acl_rule" "operations_ingress_from_anywhere_via_icmp" {
}

# Allow egress to anywhere via any protocol and port
#
# For: Assessment team operational use
#
# Note that this also covers the return traffic when the Guacamole
# instance downloads the Docker images used in the Docker composition
# via the NAT gateway in the operations subnet.
resource "aws_network_acl_rule" "operations_egress_to_anywhere_via_any_port" {
provider = aws.provisionassessment

Expand Down
12 changes: 10 additions & 2 deletions operations_routing.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,25 @@ resource "aws_default_route_table" "operations" {
}

# Route all COOL Shared Services traffic through the transit gateway
resource "aws_route" "cool_route" {
resource "aws_route" "cool_operations" {
provider = aws.provisionassessment

route_table_id = aws_default_route_table.operations.id
destination_cidr_block = local.cool_shared_services_cidr_block
transit_gateway_id = local.transit_gateway_id
}

# Associate the S3 gateway endpoint with the route table
resource "aws_vpc_endpoint_route_table_association" "s3_operations" {
provider = aws.provisionassessment

route_table_id = aws_default_route_table.operations.id
vpc_endpoint_id = aws_vpc_endpoint.s3.id
}

# Route all external (outside this VPC and outside the COOL) traffic
# through the internet gateway
resource "aws_route" "external_route" {
resource "aws_route" "external_operations" {
provider = aws.provisionassessment

route_table_id = aws_default_route_table.operations.id
Expand Down
Loading