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

Support TProxy for gateway-task submodule #271

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ FEATURES
- Adds the `CAP_NET_ADMIN` linux capability to the `mesh-init` container when `var.enable_transaparent_proxy` is set to `true`. This is needed to modify iptable rules within the ECS task.
- `mesh-init` container is run as a `root` user.
- Assign a UID of `5995` for the `consul-dataplane` container and `5996` for the `health-sync` container. This is done to selectively exclude the traffic flowing through these containers from the redirection rules.
* Add support for configuring transparent proxy for gateway specific ECS EC2 tasks. Following are the changes made to the `gateway-task` submodule[[GH-271](https://github.com/hashicorp/terraform-aws-consul-ecs/pull/271)]
- Adds the following variables
- `enable_transparent_proxy` - Defaults to `true`. Fargate based tasks should explicitly pass `false` to avoid validation errors during terraform planning phase.
- `enable_consul_dns` - Defaults to `false`. Indicates whether Consul DNS should be configured for this task. Enabling this makes Consul dataplane start up a proxy DNS server that forwards requests to the Consul DNS server. `var.enable_transparent_proxy` should be `true` to enable this setting.
- `exclude_inbound_ports` - List of inbound ports to exclude from traffic redirection.
- `exclude_outbound_ports` - List of outbound ports to exclude from traffic redirection.
- `exclude_outbound_cidrs` - List of additional IP CIDRs to exclude from outbound traffic redirection.
- `exclude_outbound_uids` - List of additional process UIDs to exclude from traffic redirection.
- Adds the `CAP_NET_ADMIN` linux capability to the `mesh-init` container when `var.enable_transaparent_proxy` is set to `true`. This is needed to modify iptable rules within the ECS task.
- `mesh-init` container is run as a `root` user.
- Assign a UID of `5995` for the `consul-dataplane` container and `5996` for the `health-sync` container. This is done to selectively exclude the traffic flowing through these containers from the redirection rules.

* Add support for provisioning API gateways as ECS tasks [[GH-234](https://github.com/hashicorp/terraform-aws-consul-ecs/pull/234)]
- Add `api-gateway` as an acceptable `kind` input.
Expand Down
15 changes: 14 additions & 1 deletion modules/gateway-task/config.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# SPDX-License-Identifier: MPL-2.0

locals {
loginExtra = lookup(var.consul_ecs_config, "consulLogin", {})
loginExtra = lookup(var.consul_ecs_config, "consulLogin", {})
tProxyExtra = lookup(var.consul_ecs_config, "transparentProxy", {})

consulLogin = var.acls ? {
enabled = var.acls
Expand Down Expand Up @@ -33,6 +34,17 @@ locals {
var.grpc_config
)

transparentProxy = {
enabled = var.enable_transparent_proxy
excludeInboundPorts = var.exclude_inbound_ports
excludeOutboundPorts = var.exclude_outbound_ports
excludeOutboundCIDRs = var.exclude_outbound_cidrs
excludeUIDs = var.exclude_uids
consulDNS = {
enabled = var.enable_consul_dns
}
}

config = {
consulLogin = merge(local.consulLogin, local.loginExtra)
gateway = {
Expand Down Expand Up @@ -63,6 +75,7 @@ locals {
http = local.httpSettings
grpc = local.grpcSettings
}
transparentProxy = merge(local.tProxyExtra, local.transparentProxy)
}

encoded_config = jsonencode(local.config)
Expand Down
105 changes: 57 additions & 48 deletions modules/gateway-task/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,61 @@ locals {

https_ca_cert_arn = var.consul_https_ca_cert_arn != "" ? var.consul_https_ca_cert_arn : var.consul_ca_cert_arn
grpc_ca_cert_arn = var.consul_grpc_ca_cert_arn != "" ? var.consul_grpc_ca_cert_arn : var.consul_ca_cert_arn

mesh_init_container_definition = {
name = "consul-ecs-mesh-init"
image = var.consul_ecs_image
essential = false
logConfiguration = var.log_configuration
command = ["mesh-init"]
mountPoints = [
local.consul_data_mount_read_write,
{
sourceVolume = local.consul_binary_volume_name
containerPath = "/bin/consul-inject"
readOnly = true
}
]
cpu = 0
volumesFrom = []
environment = [
{
name = "CONSUL_ECS_CONFIG_JSON",
value = local.encoded_config
}
]
linuxParameters = {
initProcessEnabled = true
capabilities = var.enable_transparent_proxy ? { add = ["NET_ADMIN"] } : {}
}
secrets = flatten(
concat(
var.tls ? [
concat(
local.https_ca_cert_arn != "" ? [
{
name = "CONSUL_HTTPS_CACERT_PEM"
valueFrom = local.https_ca_cert_arn
},
] : [],
local.grpc_ca_cert_arn != "" ? [
{
name = "CONSUL_GRPC_CACERT_PEM"
valueFrom = local.grpc_ca_cert_arn
}
] : [],
[]
)
] : [],
[]
)
)
}

# Additional user attribute that needs to be added to run the mesh-init
# container with root access.
additional_user_attr = var.enable_transparent_proxy ? { user = "0" } : {}

Choose a reason for hiding this comment

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

Question - When var.enable_transparent_proxy is false what will be the value of user in finalized_mesh_init_container_definition ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it will be empty or the user who starts the container. Definitely not root

finalized_mesh_init_container_definition = merge(local.mesh_init_container_definition, local.additional_user_attr)
}

resource "aws_ecs_task_definition" "this" {
Expand Down Expand Up @@ -152,58 +207,12 @@ resource "aws_ecs_task_definition" "this" {
flatten(
concat(
[
{
name = "consul-ecs-mesh-init"
image = var.consul_ecs_image
essential = false
logConfiguration = var.log_configuration
command = ["mesh-init"]
mountPoints = [
local.consul_data_mount_read_write,
{
sourceVolume = local.consul_binary_volume_name
containerPath = "/bin/consul-inject"
readOnly = true
}
]
cpu = 0
volumesFrom = []
environment = [
{
name = "CONSUL_ECS_CONFIG_JSON",
value = local.encoded_config
}
]
linuxParameters = {
initProcessEnabled = true
}
secrets = flatten(
concat(
var.tls ? [
concat(
local.https_ca_cert_arn != "" ? [
{
name = "CONSUL_HTTPS_CACERT_PEM"
valueFrom = local.https_ca_cert_arn
},
] : [],
local.grpc_ca_cert_arn != "" ? [
{
name = "CONSUL_GRPC_CACERT_PEM"
valueFrom = local.grpc_ca_cert_arn
}
] : [],
[]
)
] : [],
[]
)
)
},
local.finalized_mesh_init_container_definition,
{
name = "consul-dataplane"
image = var.consul_dataplane_image
essential = true
user = "5995"
logConfiguration = var.log_configuration
entryPoint = ["/consul/consul-ecs", "envoy-entrypoint"]
command = ["consul-dataplane", "-config-file", "/consul/consul-dataplane.json"] # consul-ecs-mesh-init dumps the dataplane's config into consul-dataplane.json
Expand Down
4 changes: 3 additions & 1 deletion modules/gateway-task/validation.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ locals {
create_xor_modify_security_group = var.lb_create_security_group && var.lb_modify_security_group ? file("ERROR: Only one of lb_create_security_group or lb_modify_security_group may be true") : null
require_sg_id_for_modify = var.lb_modify_security_group && var.lb_modify_security_group_id == "" ? file("ERROR: lb_modify_security_group_id is required when lb_modify_security_group is true") : null

custom_lb_config_check = var.lb_enabled && length(var.custom_load_balancer_config) > 0 ? file("ERROR: custom_load_balancer_config must only be supplied when var.lb_enabled is false") : null
custom_lb_config_check = var.lb_enabled && length(var.custom_load_balancer_config) > 0 ? file("ERROR: custom_load_balancer_config must only be supplied when var.lb_enabled is false") : null
require_ec2_compability_for_tproxy_support = var.enable_transparent_proxy && (length(var.requires_compatibilities) != 1 || var.requires_compatibilities[0] != "EC2") ? file("ERROR: transparent proxy is supported only in ECS EC2 mode") : null
require_tproxy_enabled_for_consul_dns = var.enable_consul_dns && !var.enable_transparent_proxy ? file("ERROR: var.enable_transparent_proxy must be set to true for Consul DNS to be enabled") : null
}
56 changes: 54 additions & 2 deletions modules/gateway-task/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ variable "consul_ecs_config" {
EOT

validation {
error_message = "Only 'consulLogin' field is allowed in consul_ecs_config."
error_message = "Only 'consulLogin' and 'transparentProxy' fields are allowed in consul_ecs_config."
condition = alltrue([
for key in keys(var.consul_ecs_config) :
contains(["consulLogin"], key)
contains(["consulLogin", "transparentProxy"], key)
])
}

Expand All @@ -339,6 +339,23 @@ variable "consul_ecs_config" {
]))
}

validation {
error_message = "Only the 'enabled','excludeInboundPorts','excludeOutboundPorts','excludeOutboundCIDRs', 'excludeUIDs' and 'consulDNS' fields are allowed in consul_ecs_config.transparentProxy."
condition = alltrue([
for key in keys(lookup(var.consul_ecs_config, "transparentProxy", {})) :
contains(["enabled", "excludeInboundPorts", "excludeOutboundPorts", "excludeUIDs", "excludeOutboundCIDRs", "consulDNS"], key)
])
}

validation {
error_message = "Only the 'enabled' field is allowed in consul_ecs_config.transparentProxy.consulDNS."
condition = alltrue(flatten([
for tproxy in [lookup(var.consul_ecs_config, "transparentProxy", {})] : [
for key in keys(lookup(tproxy, "consulDNS", {})) :
contains(["enabled"], key)
]
]))
}
}

variable "http_config" {
Expand Down Expand Up @@ -383,3 +400,38 @@ variable "volumes" {
default = []
}

variable "enable_transparent_proxy" {
description = "Whether transparent proxy should be enabled for this task. Defaults to true"
type = bool
default = true
}

variable "enable_consul_dns" {
description = "Whether Consul DNS should be configured for this task. This rewrites the /etc/resolv.conf file to point to the Consul dataplane's DNS server as the primary nameserver. Defaults to false"
type = bool
default = false
}

variable "exclude_inbound_ports" {
description = "List of inbound ports to exclude from traffic redirection."
type = list(number)
default = []
}

variable "exclude_outbound_ports" {
description = "List of outbound ports to exclude from traffic redirection."
type = list(number)
default = []
}

variable "exclude_outbound_cidrs" {
description = "List of additional IP CIDRs to exclude from outbound traffic redirection."
type = list(string)
default = []
}

variable "exclude_uids" {
description = "List of additional UIDs to exclude from outbound traffic redirection."
type = list(string)
default = []
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ module "test_gateway" {
lb_subnets = var.lb_subnets
custom_load_balancer_config = var.custom_lb_config
lb_create_security_group = var.lb_enabled
enable_transparent_proxy = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ module "test_gateway" {
ecs_cluster_arn = "cluster"
subnets = ["subnets"]
security_groups = var.security_groups
enable_transparent_proxy = false
kind = var.kind
gateway_count = var.gateway_count
consul_server_hosts = "localhost:8500"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module "test_gateway" {
ecs_cluster_arn = "cluster"
subnets = ["subnets"]
kind = var.kind
enable_transparent_proxy = false
gateway_count = var.gateway_count
consul_server_hosts = "localhost:8500"
tls = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0

provider "aws" {
region = "us-west-2"
}

variable "requires_compatibilities" {
description = "Set of launch types required by the task."
type = list(string)
}

variable "enable_transparent_proxy" {
description = "Whether to enable or disable transparent proxy for the task"
type = bool
default = true
}

variable "enable_consul_dns" {
description = "Whether to enable or disable Consul DNS for the task"
type = bool
default = true
}

module "test_gateway" {
source = "../../../../../../modules/gateway-task"
family = "family"
subnets = ["test-subnet"]
kind = "terminating-gateway"
ecs_cluster_arn = "test-cluster-arn"
consul_server_hosts = "consul.dc1.host"
lb_create_security_group = false
security_groups = ["test-security-group"]
requires_compatibilities = var.requires_compatibilities
enable_transparent_proxy = var.enable_transparent_proxy
enable_consul_dns = var.enable_consul_dns
}
65 changes: 65 additions & 0 deletions test/acceptance/tests/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1154,3 +1154,68 @@ func TestValidation_TProxy(t *testing.T) {
})
}
}

func TestValidation_TProxy_Gateway(t *testing.T) {
t.Parallel()

cases := map[string]struct {
requiresCompatibilities []string
disableTProxy bool
error bool
errorStr string
}{
"only EC2": {
requiresCompatibilities: []string{"EC2"},
},
"only Fargate": {
requiresCompatibilities: []string{"FARGATE"},
error: true,
errorStr: "transparent proxy is supported only in ECS EC2 mode.",
},
"both Fargate and EC2": {
requiresCompatibilities: []string{"FARGATE", "EC2"},
error: true,
errorStr: "transparent proxy is supported only in ECS EC2 mode.",
},
"Consul DNS does not work without enabling tproxy": {
requiresCompatibilities: []string{"FARGATE", "EC2"},
disableTProxy: true,
error: true,
errorStr: "var.enable_transparent_proxy must be set to true for Consul DNS to be enabled.",
},
}

terraformOptions := &terraform.Options{
TerraformDir: "./terraform/tproxy-gateway-validate",
NoColor: true,
}
terraform.Init(t, terraformOptions)

for name, c := range cases {
c := c

t.Run(name, func(t *testing.T) {
t.Parallel()

vars := map[string]interface{}{
"requires_compatibilities": c.requiresCompatibilities,
}
if c.disableTProxy {
vars["enable_transparent_proxy"] = false
}

out, err := terraform.PlanE(t, &terraform.Options{
TerraformDir: terraformOptions.TerraformDir,
NoColor: true,
Vars: vars,
})

if c.error {
require.Error(t, err)
require.Regexp(t, c.errorStr, out)
} else {
require.NoError(t, err)
}
})
}
}
Loading