Skip to content

Commit

Permalink
for v2.4 of discriminat (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
new23d authored Mar 16, 2022
1 parent fd6f817 commit 9a02b6d
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 53 deletions.
43 changes: 30 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# discrimiNAT, ENI architecture

[discrimiNAT firewall](https://chasersystems.com/discrimiNAT/) for egress filtering by FQDNs on AWS. Just specify the allowed destination hostnames in the respective applications' native Security Groups and the firewall will take care of the rest.
[discrimiNAT firewall](https://chasersystems.com/discriminat) for egress filtering by FQDNs on AWS. Just specify the allowed destination hostnames in the respective applications' native Security Groups and the firewall will take care of the rest.

![](https://chasersystems.com/media/aws-protocol-tls.gif)
![](https://chasersystems.com/img/aws-protocol-tls.gif)

**Architecture with [ENIs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html) in VPCs for Private Subnets' route table entries to the Internet.**

[Demo Videos](https://chasersystems.com/discrimiNAT/demo/) | [discrimiNAT FAQ](https://chasersystems.com/discrimiNAT/faq/)
[Demo Video](https://chasersystems.com/discriminat/aws/demo) | [discrimiNAT FAQ](https://chasersystems.com/discriminat/faq)

## Pentest Ready

Expand All @@ -15,31 +15,48 @@ discrimiNAT enforces the use of contemporary encryption standards such as TLS 1.
## Highlights

* Creates ENIs, backed by instances in an Auto-Scaling group for high availability, for use as targets in route tables' destination `0.0.0.0/0` (i.e. the Internet) entries.
* Can accommodate pre-allocated Elastic IPs for use with the NAT function. Making use of this is of course, optional.
* VMs _without_ public IPs will need Security Groups with rules specifying what egress FQDNs and protocols are to be allowed. Default behaviour is to deny everything.
* Can accommodate pre-allocated Elastic IPs for use with the NAT function. Just tag allocated EIPs with the key `discriminat` and any value.
* VMs Or Lambdas (in VPC) _without_ public IPs will need Security Groups with rules specifying what egress FQDNs and protocols are to be allowed. Default behaviour is to deny everything.

## Considerations

* A deployment per zone is advised, just like the AWS NAT Gateways – which are not needed with discrimiNAT deployed.
* VMs _without_ public IPs will need to be in a subnet (typically the Private Subnet) with routing through the ENIs created by this module to access the Internet at all.
* VMs and Lambdas _without_ public IPs will need to be in a subnet (typically the Private Subnet) with routing through the ENIs created by this module to access the Internet at all.
* You must be subscribed to the [discrimiNAT firewall from the AWS Marketplace](https://aws.amazon.com/marketplace/pp/B07YLBH34R?ref=_ptnr_gthb).

## Elastic IPs

If a Public IP is not found attached to a discrimiNAT instance, it will look for any allocated but unassociated Elastic IPs that have a tag-key named `discriminat` (set to any value.) One of such Elastic IPs will be attempted to be associated with itself then.

>This allows you to have a stable set of static IPs to share with your partners, who may wish to allowlist/whitelist them.
The IAM permissions needed to do this are already a part of this module. Specifically, they are:

```
ec2:DescribeAddresses
ec2:AssociateAddress
```

An EC2 VPC Endpoint is needed for this mechanism to work though – since making the association needs access to the EC2 API. In the [aws_vpc example](examples/aws_vpc/), this is demonstrated by deploying the endpoint alongwith with the VPC.

It is always possible to not choose this mechanism and have a Public IP associated with the network interfaces of the discrimiNAT right from the onset. This also used to be the case before v2.4 of the discrimiNAT.

## Next Steps

* [Understand how to configure the enhanced Security Groups](https://chasersystems.com/discrimiNAT/aws/config-ref/) after deployment, from our main documentation.
* Contact our DevSecOps at devsecops@chasersystems.com for queries at any stage of your journey even on the eve of a pentest!
* [Understand how to configure the enhanced Security Groups](https://chasersystems.com/docs/discriminat/aws/config-ref) after deployment, from our main documentation.
* Contact our DevSecOps at devsecops@chasersystems.com for queries at any stage of your journey even on the eve of a pentest!

## Discover

Perhaps use the `see-thru` mode to discover what needs to be in the allowlist for an application, by monitoring its outbound network activity first. Follow our [building an allowlist from scratch](https://chasersystems.com/discrimiNAT/aws/logs-ref/#building-an-allowlist-from-scratch) recipe for use with CloudWatch.
Perhaps use the `see-thru` mode to discover what needs to be in the allowlist for an application, by monitoring its outbound network activity first. Follow our [building an allowlist from scratch](https://chasersystems.com/docs/discriminat/aws/logs-ref#building-an-allowlist-from-scratch) recipe for use with CloudWatch.

![](https://chasersystems.com/media/aws-see-thru.gif)
![](https://chasersystems.com/img/aws-see-thru.gif)

## Post-deployment Security Group Example

```hcl
# This Security Group must be associated with its intended, respective application - whether that is
# in EC2, Fargate or EKS, etc. as long as a Security Group can be associated with it.
# This Security Group must be associated with its intended, respective application whether that is
# in EC2, Lambda, Fargate or EKS, etc. as long as a Security Group can be associated with it.
resource "aws_security_group" "foo" {
# You could use a data source or get a reference from another resource for the VPC ID.
vpc_id = "vpc-1234example5678"
Expand All @@ -58,7 +75,7 @@ resource "aws_security_group_rule" "saas_monitoring" {
cidr_blocks = ["0.0.0.0/0"]
# You could simply embed the allowed FQDNs, comma-separated, like below.
# Full syntax at https://chasersystems.com/discrimiNAT/aws/quick-start/#vii-security-groups
# Full syntax at https://chasersystems.com/docs/discriminat/aws/config-ref
description = "discriminat:tls:app.datadoghq.com,collector.newrelic.com"
}
Expand Down
61 changes: 32 additions & 29 deletions discriminat.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,6 @@ variable "public_subnets" {

## Defaults

variable "elastic_ips" {
type = list(string)
description = "Specific, pre-allocated Elastic IP addresses if you wish to use these for egress, NATed traffic. If none specified, ephemeral public IP addressess will be allocated automatically. If specifed, should be equal to the number of public subnets and NOT be associated with other instances or NAT Gateways. For example: [\"198.51.100.5\", \"203.0.113.2\"]"
default = []
}

variable "tags" {
type = map(any)
description = "Map of key-value tag pairs to apply to resources created by this module. See examples for use."
Expand All @@ -30,24 +24,30 @@ variable "instance_size" {
variable "key_pair_name" {
type = string
description = "Strongly suggested to leave this to the default, that is to NOT associate any key-pair with the instances. In case SSH access is desired, provide the name of a valid EC2 Key Pair."
default = ""
default = null
}

variable "startup_script_base64" {
type = string
description = "Strongly suggested to NOT run custom, startup scripts on the firewall instances. But if you had to, supply a base64 encoded version here."
default = ""
default = null
}

##
variable "ami_owner" {
type = string
description = "Reserved for use with Chaser support. Allows overriding the source AMI account for discrimiNAT."
default = null
}

## Lookups
variable "ami_name" {
type = string
description = "Reserved for use with Chaser support. Allows overriding the source AMI version for discrimiNAT."
default = null
}

data "aws_eip" "elastic_ip" {
count = length(var.elastic_ips)
##

public_ip = var.elastic_ips[count.index]
}
## Lookups

data "aws_subnet" "public_subnet" {
count = length(var.public_subnets)
Expand All @@ -60,7 +60,7 @@ data "aws_vpc" "context" {
}

data "aws_ami" "discriminat" {
owners = ["aws-marketplace"]
owners = [var.ami_owner == null ? "aws-marketplace" : var.ami_owner]
most_recent = true

filter {
Expand All @@ -69,8 +69,8 @@ data "aws_ami" "discriminat" {
}

filter {
name = "product-code"
values = ["a83las5cq95zkg3x8i17x6wyy"]
name = var.ami_owner == null ? "product-code" : "name"
values = [var.ami_owner == null ? "a83las5cq95zkg3x8i17x6wyy" : var.ami_name]
}
}

Expand All @@ -89,13 +89,6 @@ resource "aws_network_interface" "static_egress" {
tags = local.tags
}

resource "aws_eip_association" "static_egress" {
count = length(data.aws_eip.elastic_ip)

network_interface_id = aws_network_interface.static_egress[count.index].id
allocation_id = data.aws_eip.elastic_ip[count.index].id
}

resource "aws_security_group" "discriminat" {
name_prefix = "discriminat-"
lifecycle {
Expand Down Expand Up @@ -142,6 +135,14 @@ resource "aws_launch_template" "discriminat" {
http_tokens = "required"
}

block_device_mappings {
device_name = data.aws_ami.discriminat.root_device_name
ebs {
encrypted = true
volume_size = tolist(data.aws_ami.discriminat.block_device_mappings)[0].ebs.volume_size
}
}

network_interfaces {
network_interface_id = aws_network_interface.static_egress[count.index].id
}
Expand All @@ -155,8 +156,8 @@ resource "aws_launch_template" "discriminat" {
tags = local.tags
}

key_name = var.key_pair_name == "" ? null : var.key_pair_name
user_data = var.startup_script_base64 == "" ? null : var.startup_script_base64
key_name = var.key_pair_name
user_data = var.startup_script_base64

tags = local.tags
}
Expand Down Expand Up @@ -227,7 +228,9 @@ resource "aws_iam_policy" "discriminat" {
"Effect": "Allow",
"Action": [
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeSecurityGroups"
"ec2:DescribeSecurityGroups",
"ec2:DescribeAddresses",
"ec2:AssociateAddress"
],
"Resource": "*"
}
Expand Down Expand Up @@ -282,7 +285,7 @@ locals {
tags = merge(
{
"Name" : "discrimiNAT",
"documentation" : "https://chasersystems.com/discrimiNAT/aws/"
"documentation" : "https://chasersystems.com/docs/discriminat/aws/installation-overview"
},
var.tags
)
Expand Down Expand Up @@ -314,7 +317,7 @@ terraform {
output "target_network_interfaces" {
value = { for i, z in data.aws_subnet.public_subnet :
z.availability_zone => aws_network_interface.static_egress[i].id }
description = "Map of zones to ENI IDs suitable for setting as Network Interface targets in routing tables of Private Subnets. A Terraform example of using these in an \"aws_route\" resource can be found at https://github.com/ChaserSystems/terraform-aws-discriminat-eni/blob/main/examples/aws_vpc/example.tf#L20-L27"
description = "Map of zones to ENI IDs suitable for setting as Network Interface targets in routing tables of Private Subnets. A Terraform example of using these in an \"aws_route\" resource can be found at https://github.com/ChaserSystems/terraform-aws-discriminat-eni/blob/main/examples/aws_vpc/example.tf"
}

##
13 changes: 5 additions & 8 deletions examples/aws_vpc/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# discrimiNAT, ENI architecture, alongside "terraform-aws-modules/vpc/aws" example

Demonstrates how to install discrimiNAT egress filtering in a VPC provisioned with the [terraform-aws-modules/vpc/aws](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws) module from the Terraform Registry.
Demonstrates how to install discrimiNAT egress filtering in a VPC provisioned with the [terraform-aws-modules/vpc/aws](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws) v2 module from the Terraform Registry.

## Example

See file `example.tf` in the _Source Code_ link above.
## Elastic IPs

## Considerations
Elastic IPs for the NAT function have been defined in a separate file, `eip.tf`, to encourage independent allocation and handling. Although the contents of `eip.tf` will be allocated if `terraform` is run in this directory, users should ensure Elastic IPs are managed separately so they are not accidentally deleted.

If creating the VPC and a discrimiNAT deployment at the same time, it may be useful to create just the VPC first so the discrimiNAT module has a clear idea of the setup. The following sequence of commands are specific to this example in order to resolve a `Invalid count argument` error message, should you encounter it.
## Example

1. `terraform apply -target=module.aws_vpc`
1. `terraform apply`
See file `example.tf` in the _Source Code_ link above.
19 changes: 19 additions & 0 deletions examples/aws_vpc/eip.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
resource "aws_eip" "nat_a" {
tags = {
"discriminat" : "some-comment"
}

lifecycle {
prevent_destroy = false
}
}

resource "aws_eip" "nat_b" {
tags = {
"discriminat" : "any-remark"
}

lifecycle {
prevent_destroy = false
}
}
33 changes: 31 additions & 2 deletions examples/aws_vpc/example.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,42 @@ module "aws_vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "> 2, < 3"

name = "discrimiNATed"
name = "discriminat-example"

cidr = "172.16.0.0/16"

enable_dns_support = true
enable_dns_hostnames = true

azs = ["eu-west-2a", "eu-west-2b"]
public_subnets = ["172.16.11.0/24", "172.16.21.0/24"]
private_subnets = ["172.16.12.0/24", "172.16.22.0/24"]

map_public_ip_on_launch = false

manage_default_security_group = true
default_security_group_ingress = []
default_security_group_egress = []

enable_ec2_endpoint = true
ec2_endpoint_private_dns_enabled = true
ec2_endpoint_security_group_ids = [aws_security_group.vpce_ec2.id]
}

resource "aws_security_group" "vpce_ec2" {
name = "vpce-ec2"
description = "ingress from entire vpc to ec2 endpoint for connectivity to it without public ips"

vpc_id = module.aws_vpc.vpc_id

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [module.aws_vpc.vpc_cidr_block]

description = "only https standard port needed for ec2 api"
}
}

module "discriminat" {
Expand All @@ -18,7 +47,7 @@ module "discriminat" {
}

resource "aws_route" "discriminat" {
count = length(module.discriminat.target_network_interfaces)
count = length(module.aws_vpc.azs)

destination_cidr_block = "0.0.0.0/0"

Expand Down
4 changes: 4 additions & 0 deletions examples/retrofit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Demonstrates how to retrofit discrimiNAT egress filtering in a pre-existing VPC,

See file `example.tf` in the _Source Code_ link above.

## Elastic IPs

Elastic IPs for the NAT function have been defined in a separate file, `eip.tf`, to encourage independent allocation and handling. Although the contents of `eip.tf` will be allocated if `terraform` is run in this directory, users should ensure Elastic IPs are managed separately so they are not accidentally deleted.

## Considerations

1. Public Subnets with routing to the Internet via an [Internet Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html) must already exist.
9 changes: 9 additions & 0 deletions examples/retrofit/eip.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "aws_eip" "nat_a" {
tags = {
"discriminat" : "some-comment"
}

lifecycle {
prevent_destroy = false
}
}
42 changes: 41 additions & 1 deletion examples/retrofit/example.tf
Original file line number Diff line number Diff line change
@@ -1,14 +1,54 @@
variable "public_subnets" {
type = list(any)
description = "List of public subnets to deploy the discrimiNAT firewall in. These would be the same as where a NAT Gateway/Instance would normally be placed in your design and should have their default route set to an Internet Gateway."
}

module "discriminat" {
source = "ChaserSystems/discriminat-eni/aws"

public_subnets = ["subnet-1a3c5e7g", "subnet-2b4d6f8h"]
public_subnets = var.public_subnets

tags = {
"x" = "y"
"foo" = "bar"
}
}

data "aws_subnet" "public" {
id = var.public_subnets[0]
}

data "aws_vpc" "this" {
id = data.aws_subnet.public.vpc_id
}

data "aws_region" "this" {}

resource "aws_vpc_endpoint" "ec2" {
vpc_id = data.aws_subnet.public.vpc_id
service_name = "com.amazonaws.${data.aws_region.this.name}.ec2"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
security_group_ids = [aws_security_group.vpce_ec2.id]
subnet_ids = var.public_subnets
}

resource "aws_security_group" "vpce_ec2" {
name = "vpce-ec2"
description = "ingress from entire vpc to ec2 endpoint for connectivity to it without public ips"

vpc_id = data.aws_subnet.public.vpc_id

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [data.aws_vpc.this.cidr_block]

description = "only https standard port needed for ec2 api"
}
}

output "target_network_interfaces" {
value = module.discriminat.target_network_interfaces
description = "Map of zones to ENI IDs suitable for setting as targets in routing tables of Private Subnets."
Expand Down

0 comments on commit 9a02b6d

Please sign in to comment.