Skip to content

Commit b706d72

Browse files
authored
Merge pull request #14 from dnks0/feature/dbx-proxy
* added validation to avoid conflicts where the proxy health port is also used as a listener port * streamlined bootstrap deployments to also include a security group on the NLB * ingress is bypassed on the NLB SG due to limitations * ingress to the NLB is controlled via the endpoint-service * included NLB security groups into module outputs
2 parents 4f60e28 + 1edf2c0 commit b706d72

7 files changed

Lines changed: 87 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,4 @@ curl -sS -w '\nHTTP %{http_code}\n' http://<ncc-endpoint-rule-domain>:8080/statu
8686
```
8787

8888
### Limitations / Trade-Offs
89-
Before going to production, please review the following [limitations & trade-offs](terraform/README.md#limitations--tradeoffs-of-the-current-implementation).
89+
Before going to production, please review the following [limitations & trade-offs](terraform/README.md#limitations--tradeoffs-of-the-current-implementation).

terraform/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,3 +283,9 @@ This module is intentionally minimal right now. The following limitations are im
283283
- Databricks enforces limits around NCCs, private endpoints, and private endpoint rules (including limits on the number of domain names per rule).
284284
- Treat these as **external constraints** that influence how you model `dbx_proxy_listener`.
285285
- Reference: [Configure private connectivity to resources in your VPC](https://docs.databricks.com/aws/en/security/network/serverless-network-security/pl-to-internal-network).
286+
287+
- **PrivateLink NLB SG ingress enforcement**
288+
- Currently, if bootstrapped, the NLB gets created with a dedicated Security Group attached, which would allow to control ingress to the NLB, enforced with `enforce_security_group_inbound_rules_on_private_link_traffic = on`.
289+
- The above setting would also allow to see individual client-IPs as source in network packets (which would be favorable in general)
290+
- However, we are not able to lock-down the inbound rules on the NLB Security Group due to missing information like Security Group ID or VPC endpoint ID of the consumer side (Databricks Serverless).
291+
- Therefore, we are using `enforce_security_group_inbound_rules_on_private_link_traffic = on`, which bypasses the Security Group inbound rules, and solely rely on the endpoint service `allowed_principals` and manual acceptance to control inbound traffic to the NLB.

terraform/aws/modules/load-balancer/local.tf

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@ locals {
44
nlb_dns_name = var.bootstrap_load_balancer ? aws_lb.this[0].dns_name : data.aws_lb.this[0].dns_name
55
nlb_zone_id = var.bootstrap_load_balancer ? aws_lb.this[0].zone_id : data.aws_lb.this[0].zone_id
66

7-
nlb_has_security_groups = var.bootstrap_load_balancer ? false : length(data.aws_lb.this[0].security_groups) > 0
7+
nlb_security_group_ids = var.bootstrap_load_balancer ? aws_lb.this[0].security_groups : data.aws_lb.this[0].security_groups
88

9-
nlb_security_group_ids = local.nlb_has_security_groups ? data.aws_lb.this[0].security_groups : []
109

11-
nlb_listener_for_egress_rules = concat(
10+
nlb_ports_for_egress_rules = concat(
1211
[for l in var.dbx_proxy_listener : { port = l.port, description = "Databricks to NLB to dbx-proxy listener ${l.name}" }],
13-
contains([for l in var.dbx_proxy_listener : l.port], var.dbx_proxy_health_port) ? [] : [{ port = var.dbx_proxy_health_port, description = "Databricks to NLB to dbx-proxy health check" }],
12+
[{ port = var.dbx_proxy_health_port, description = "Databricks to NLB to dbx-proxy health check" }],
1413
)
1514

16-
nlb_sg_egress_rules = local.nlb_has_security_groups ? [
17-
for pair in setproduct(local.nlb_security_group_ids, local.nlb_listener_for_egress_rules, var.subnet_cidrs) : {
15+
nlb_sg_egress_rules = length(local.nlb_security_group_ids) > 0 ? [
16+
for pair in setproduct(local.nlb_security_group_ids, local.nlb_ports_for_egress_rules, var.subnet_cidrs) : {
1817
security_group_id = pair[0]
1918
description = pair[1].description
2019
port = pair[1].port

terraform/aws/modules/load-balancer/nlb.tf

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,63 @@ resource "aws_lb" "this" {
66
load_balancer_type = "network"
77
internal = true
88
subnets = var.subnet_ids
9+
security_groups = [aws_security_group.this[0].id]
910

1011
enable_cross_zone_load_balancing = true
1112
enable_deletion_protection = false
1213

14+
# PrivateLink traffic bypasses NLB SG ingress when this is off. We use off
15+
# because the service owner cannot restrict ingress by endpoint SG/CIDR without
16+
# consumer-provided details; access is instead controlled via endpoint service
17+
# allowed_principals and manual acceptance.
18+
enforce_security_group_inbound_rules_on_private_link_traffic = "off"
19+
1320
tags = var.tags
1421
}
1522

23+
resource "aws_security_group" "this" {
24+
count = var.bootstrap_load_balancer ? 1 : 0
25+
26+
name = "${var.prefix}-nlb-sg"
27+
description = "Security group for dbx-proxy NLB"
28+
vpc_id = var.vpc_id
29+
30+
# Outbound from NLB on any listener port
31+
dynamic "egress" {
32+
for_each = { for l in var.dbx_proxy_listener : l.name => l }
33+
content {
34+
description = "Databricks to NLB to dbx-proxy listener ${egress.key}"
35+
from_port = egress.value.port
36+
to_port = egress.value.port
37+
protocol = "tcp"
38+
cidr_blocks = var.subnet_cidrs
39+
}
40+
}
41+
42+
# Health check port
43+
egress {
44+
description = "Databricks to NLB to dbx-proxy health check"
45+
from_port = var.dbx_proxy_health_port
46+
to_port = var.dbx_proxy_health_port
47+
protocol = "tcp"
48+
cidr_blocks = var.subnet_cidrs
49+
}
50+
51+
# We can not lock down ingress to individual sources since we don't know the
52+
# source IP addresses or Security Group IDs the traffic is originating from.
53+
# Therefore, we allow all ingress traffic on the SG level. Ingress is controlled
54+
# by the endpoint service allowed_principals and manual acceptance.
55+
56+
tags = merge(
57+
var.tags,
58+
{
59+
Name = "${var.prefix}-nlb-sg"
60+
},
61+
)
62+
}
63+
1664
resource "aws_vpc_security_group_egress_rule" "this" {
17-
count = local.nlb_has_security_groups ? length(local.nlb_sg_egress_rules) : 0
65+
count = var.bootstrap_load_balancer ? 0 : length(local.nlb_sg_egress_rules)
1866

1967
security_group_id = local.nlb_sg_egress_rules[count.index].security_group_id
2068
from_port = local.nlb_sg_egress_rules[count.index].port
@@ -24,12 +72,7 @@ resource "aws_vpc_security_group_egress_rule" "this" {
2472
description = local.nlb_sg_egress_rules[count.index].description
2573
}
2674

27-
# Optional: expose the dbx-proxy health port via the NLB so callers can reach it directly
28-
# (e.g. through the PrivateLink endpoint). If the health port is already used as a regular
29-
# listener port, we skip creating this additional listener/TG to avoid a conflict.
3075
resource "aws_lb_target_group" "health" {
31-
count = contains([for l in var.dbx_proxy_listener : l.port], var.dbx_proxy_health_port) ? 0 : 1
32-
3376
name = "dbx-proxy-tg-health"
3477
port = var.dbx_proxy_health_port
3578
protocol = "TCP"
@@ -49,16 +92,21 @@ resource "aws_lb_target_group" "health" {
4992
}
5093

5194
resource "aws_lb_listener" "health" {
52-
count = length(aws_lb_target_group.health)
53-
5495
load_balancer_arn = local.nlb_arn
5596
port = var.dbx_proxy_health_port
5697
protocol = "TCP"
5798

5899
default_action {
59100
type = "forward"
60-
target_group_arn = aws_lb_target_group.health[0].arn
101+
target_group_arn = aws_lb_target_group.health.arn
61102
}
103+
104+
tags = merge(
105+
var.tags,
106+
{
107+
Name = "${var.prefix}-l-health"
108+
},
109+
)
62110
}
63111

64112
# One target group per listener port for simple configuration.
@@ -94,4 +142,11 @@ resource "aws_lb_listener" "this" {
94142
type = "forward"
95143
target_group_arn = each.value.arn
96144
}
145+
146+
tags = merge(
147+
var.tags,
148+
{
149+
Name = "${var.prefix}-l-${each.key}"
150+
},
151+
)
97152
}

terraform/aws/modules/load-balancer/outputs.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ output "nlb_target_group_arns" {
1212
description = "ARNs of the NLB target groups, keyed by listener name (plus health when created)."
1313
value = merge(
1414
{ for name, tg in aws_lb_target_group.this : name => tg.arn },
15-
length(aws_lb_target_group.health) > 0 ? { health = aws_lb_target_group.health[0].arn } : {},
15+
{ health = aws_lb_target_group.health.arn },
1616
)
1717
}
1818

19+
output "nlb_security_group_ids" {
20+
description = "Security group IDs attached to the NLB (if any)."
21+
value = tolist(local.nlb_security_group_ids)
22+
}
23+
1924
output "vpc_endpoint_service_arn" {
2025
description = "ARN of the VPC endpoint service if created; otherwise null."
2126
value = length(aws_vpc_endpoint_service.this) > 0 ? aws_vpc_endpoint_service.this[0].arn : null

terraform/aws/outputs.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ output "load_balancer" {
1717
nlb_arn = module.load_balancer.nlb_arn
1818
nlb_dns_name = module.load_balancer.nlb_dns_name
1919
nlb_target_group_arns = module.load_balancer.nlb_target_group_arns
20+
nlb_security_group_ids = module.load_balancer.nlb_security_group_ids
2021
vpc_endpoint_service_arn = module.load_balancer.vpc_endpoint_service_arn
2122
vpc_endpoint_service_name = module.load_balancer.vpc_endpoint_service_name
2223
}

terraform/aws/variables.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,8 @@ EOT
145145
}))
146146
}))
147147
default = []
148+
validation {
149+
condition = alltrue([for listener in var.dbx_proxy_listener : listener.port != var.dbx_proxy_health_port])
150+
error_message = "dbx_proxy_health_port must not overlap with any dbx_proxy_listener port."
151+
}
148152
}

0 commit comments

Comments
 (0)