Skip to content

Commit fcfdbb5

Browse files
authored
Merge pull request #517 from Facets-cloud/feat/ngf-241-chart-hooks
feat: port custom hooks to NGF 2.4.1 chart
2 parents da04fd9 + 4401534 commit fcfdbb5

4 files changed

Lines changed: 154 additions & 74 deletions

File tree

Binary file not shown.

modules/ingress/nginx_gateway_fabric_legacy/1.0/facets.yaml

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,22 +298,32 @@ spec:
298298
type: string
299299
title: Path
300300
description: Path of the application (required for HTTP routes)
301-
pattern: ^(/[^/]+)*(/)?$
302-
x-ui-placeholder: Enter path (e.g., / or /api)
303-
x-ui-error-message: "Value doesn't match pattern, eg: / or /api"
301+
pattern: ^(/[^\s{};^]*)$
302+
x-ui-placeholder: "Enter path (e.g., / or /api or /api/v[0-9]+)"
303+
x-ui-error-message: "Path must start with /"
304304
x-ui-visible-if:
305305
field: spec.rules.{{this}}.grpc_config.enabled
306306
values:
307307
- false
308308
- null
309+
disable_auth:
310+
type: boolean
311+
title: Disable Auth for this Route
312+
description: Disable basic authentication for this specific route (only relevant when basic_auth is enabled)
313+
default: false
314+
x-ui-visible-if:
315+
field: spec.basic_auth
316+
values:
317+
- true
309318
path_type:
310319
type: string
311320
title: Path Type
312-
description: "Path matching type. PathPrefix (default) matches paths starting with the specified prefix. Use Exact for exact path matching."
321+
description: "Path matching type. RegularExpression (default) matches paths using regex for ingress-nginx parity. Use PathPrefix for prefix matching or Exact for exact path matching."
313322
enum:
314-
- Exact
323+
- RegularExpression
315324
- PathPrefix
316-
default: PathPrefix
325+
- Exact
326+
default: RegularExpression
317327
x-ui-visible-if:
318328
field: spec.rules.{{this}}.grpc_config.enabled
319329
values:
@@ -778,6 +788,7 @@ spec:
778788
- namespace
779789
x-ui-order:
780790
- disable
791+
- disable_auth
781792
- domain_prefix
782793
- service_name
783794
- namespace
@@ -800,6 +811,11 @@ spec:
800811
type: boolean
801812
title: Force SSL Redirection
802813
description: Force HTTP to HTTPS redirection
814+
basic_auth:
815+
type: boolean
816+
title: Basic Authentication
817+
description: Enable/disable basic auth for all routes (individual routes can opt out with disable_auth)
818+
default: false
803819
body_size:
804820
type: string
805821
title: Max Body Size
@@ -841,6 +857,7 @@ spec:
841857
- disable_base_domain
842858
- domains
843859
- force_ssl_redirection
860+
- basic_auth
844861
- body_size
845862
- disable_endpoint_validation
846863
- data_plane
@@ -859,6 +876,7 @@ sample:
859876
private: false
860877
disable_base_domain: false
861878
force_ssl_redirection: true
879+
basic_auth: false
862880
body_size: 150m
863881
data_plane:
864882
scaling:

modules/ingress/nginx_gateway_fabric_legacy/1.0/main.tf

Lines changed: 123 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,10 @@ locals {
238238
}
239239
} : {}
240240

241-
# HTTPRoute Resources (HTTPS traffic - port 443)
241+
# HTTPRoute Resources (HTTPS traffic - port 443, and HTTP - port 80 when force_ssl_redirection is disabled)
242242
# Note: GatewayClass, Gateway, and NginxProxy are created by the Helm chart
243+
force_ssl_redirection = lookup(var.instance.spec, "force_ssl_redirection", false)
244+
243245
httproute_resources = {
244246
for k, v in local.rulesFiltered : "httproute-${lower(var.instance_name)}-${k}" => {
245247
apiVersion = "gateway.networking.k8s.io/v1"
@@ -252,21 +254,30 @@ locals {
252254
# Reference the correct listener(s) for this route's hostnames
253255
# If route has domain_prefix, reference the additional hostname listeners
254256
# If route has no domain_prefix, reference the base domain listeners
255-
parentRefs = lookup(v, "domain_prefix", null) == null || lookup(v, "domain_prefix", null) == "" ? [
256-
# No domain_prefix - use base domain listeners
257-
for domain_key, domain in local.domains : {
258-
name = local.name
259-
namespace = var.environment.namespace
260-
sectionName = "https-${domain_key}"
261-
}
262-
] : [
263-
# Has domain_prefix - use additional hostname listeners
264-
for domain_key, domain in local.domains : {
257+
# When force_ssl_redirection is disabled, also attach to HTTP listener so traffic is served on port 80
258+
parentRefs = concat(
259+
lookup(v, "domain_prefix", null) == null || lookup(v, "domain_prefix", null) == "" ? [
260+
# No domain_prefix - use base domain listeners
261+
for domain_key, domain in local.domains : {
262+
name = local.name
263+
namespace = var.environment.namespace
264+
sectionName = "https-${domain_key}"
265+
}
266+
] : [
267+
# Has domain_prefix - use additional hostname listeners
268+
for domain_key, domain in local.domains : {
269+
name = local.name
270+
namespace = var.environment.namespace
271+
sectionName = "https-${replace(replace("${lookup(v, "domain_prefix", null)}.${domain.domain}", ".", "-"), "*", "wildcard")}"
272+
}
273+
],
274+
# Also attach to HTTP listener when SSL redirection is disabled
275+
!local.force_ssl_redirection ? [{
265276
name = local.name
266277
namespace = var.environment.namespace
267-
sectionName = "https-${replace(replace("${lookup(v, "domain_prefix", null)}.${domain.domain}", ".", "-"), "*", "wildcard")}"
268-
}
269-
]
278+
sectionName = "http"
279+
}] : []
280+
)
270281

271282
# Include all domains in hostnames - Gateway API supports multiple hostnames per route
272283
hostnames = distinct([
@@ -282,7 +293,7 @@ locals {
282293
[merge(
283294
{
284295
path = {
285-
type = lookup(v, "path_type", "PathPrefix")
296+
type = lookup(v, "path_type", "RegularExpression")
286297
value = lookup(v, "path", "/")
287298
}
288299
},
@@ -314,6 +325,15 @@ locals {
314325
)
315326

316327
filters = concat(
328+
# Basic auth filter (applied when basic_auth is enabled and route doesn't have disable_auth)
329+
lookup(var.instance.spec, "basic_auth", false) && !lookup(v, "disable_auth", false) ? [{
330+
type = "ExtensionRef"
331+
extensionRef = {
332+
group = "gateway.nginx.org"
333+
kind = "AuthenticationFilter"
334+
name = "${local.name}-basic-auth"
335+
}
336+
}] : [],
317337
# Static filters
318338
[
319339
for filter in [
@@ -432,21 +452,30 @@ locals {
432452
# Reference the correct listener(s) for this route's hostnames
433453
# If route has domain_prefix, reference the additional hostname listeners
434454
# If route has no domain_prefix, reference the base domain listeners
435-
parentRefs = lookup(v, "domain_prefix", null) == null || lookup(v, "domain_prefix", null) == "" ? [
436-
# No domain_prefix - use base domain listeners
437-
for domain_key, domain in local.domains : {
438-
name = local.name
439-
namespace = var.environment.namespace
440-
sectionName = "https-${domain_key}"
441-
}
442-
] : [
443-
# Has domain_prefix - use additional hostname listeners
444-
for domain_key, domain in local.domains : {
455+
# When force_ssl_redirection is disabled, also attach to HTTP listener
456+
parentRefs = concat(
457+
lookup(v, "domain_prefix", null) == null || lookup(v, "domain_prefix", null) == "" ? [
458+
# No domain_prefix - use base domain listeners
459+
for domain_key, domain in local.domains : {
460+
name = local.name
461+
namespace = var.environment.namespace
462+
sectionName = "https-${domain_key}"
463+
}
464+
] : [
465+
# Has domain_prefix - use additional hostname listeners
466+
for domain_key, domain in local.domains : {
467+
name = local.name
468+
namespace = var.environment.namespace
469+
sectionName = "https-${replace(replace("${lookup(v, "domain_prefix", null)}.${domain.domain}", ".", "-"), "*", "wildcard")}"
470+
}
471+
],
472+
# Also attach to HTTP listener when SSL redirection is disabled
473+
!local.force_ssl_redirection ? [{
445474
name = local.name
446475
namespace = var.environment.namespace
447-
sectionName = "https-${replace(replace("${lookup(v, "domain_prefix", null)}.${domain.domain}", ".", "-"), "*", "wildcard")}"
448-
}
449-
]
476+
sectionName = "http"
477+
}] : []
478+
)
450479

451480
# Include all domains in hostnames - Gateway API supports multiple hostnames per route
452481
hostnames = distinct([
@@ -468,6 +497,16 @@ locals {
468497
}
469498
] : []
470499

500+
# Basic auth filter (applied when basic_auth is enabled and route doesn't have disable_auth)
501+
filters = lookup(var.instance.spec, "basic_auth", false) && !lookup(v, "disable_auth", false) ? [{
502+
type = "ExtensionRef"
503+
extensionRef = {
504+
group = "gateway.nginx.org"
505+
kind = "AuthenticationFilter"
506+
name = "${local.name}-basic-auth"
507+
}
508+
}] : []
509+
471510
backendRefs = [{
472511
name = v.service_name
473512
port = tonumber(v.port)
@@ -571,14 +610,36 @@ locals {
571610
}
572611
}
573612

613+
# AuthenticationFilter for basic auth (NGF native CRD)
614+
authenticationfilter_resources = lookup(var.instance.spec, "basic_auth", false) ? {
615+
"authfilter-${local.name}" = {
616+
apiVersion = "gateway.nginx.org/v1alpha1"
617+
kind = "AuthenticationFilter"
618+
metadata = {
619+
name = "${local.name}-basic-auth"
620+
namespace = var.environment.namespace
621+
}
622+
spec = {
623+
type = "Basic"
624+
basic = {
625+
realm = "Authentication required"
626+
secretRef = {
627+
name = "${local.name}-basic-auth"
628+
}
629+
}
630+
}
631+
}
632+
} : {}
633+
574634
# Merge all Gateway API resources
575635
gateway_api_resources = merge(
576636
local.http_redirect_resources,
577637
local.httproute_resources,
578638
local.grpcroute_resources,
579639
local.podmonitor_resources,
580640
local.referencegrant_resources,
581-
local.clientsettingspolicy_resources
641+
local.clientsettingspolicy_resources,
642+
local.authenticationfilter_resources
582643
)
583644
}
584645

@@ -808,7 +869,7 @@ module "http01_certificate_additional" {
808869
resource "helm_release" "nginx_gateway_fabric" {
809870
name = "${local.name}-nginx-fabric"
810871
wait = lookup(var.instance.spec, "helm_wait", true)
811-
chart = "${path.module}/charts/nginx-gateway-fabric-2.3.0.tgz"
872+
chart = "${path.module}/charts/nginx-gateway-fabric-2.4.1.tgz"
812873
namespace = var.environment.namespace
813874
max_history = 10
814875
skip_crds = false
@@ -835,7 +896,7 @@ resource "helm_release" "nginx_gateway_fabric" {
835896

836897
image = {
837898
repository = "facetscloud/nginx-gateway-fabric"
838-
tag = "2.3.0"
899+
tag = "v2.4.1"
839900
pullPolicy = "IfNotPresent"
840901
}
841902
imagePullSecrets = lookup(var.inputs, "artifactories", null) != null ? var.inputs.artifactories.attributes.registry_secrets_list : []
@@ -1097,36 +1158,39 @@ module "gateway_api_resources" {
10971158
resources_data = local.gateway_api_resources
10981159
advanced_config = {}
10991160

1100-
depends_on = [helm_release.nginx_gateway_fabric]
1161+
depends_on = [helm_release.nginx_gateway_fabric, kubernetes_secret.basic_auth]
1162+
}
1163+
1164+
# Basic Authentication using NGF AuthenticationFilter CRD
1165+
# NGF 2.4.1 supports native basic auth via AuthenticationFilter (gateway.nginx.org/v1alpha1)
1166+
# When basic_auth is enabled: auto-generates credentials, creates htpasswd Secret,
1167+
# and applies AuthenticationFilter to all HTTPRoute rules (per-rule disable_auth to exempt)
1168+
1169+
resource "random_string" "basic_auth_password" {
1170+
count = lookup(var.instance.spec, "basic_auth", false) ? 1 : 0
1171+
length = 10
1172+
special = false
11011173
}
11021174

1103-
# Basic Authentication
1104-
# NOTE: Basic auth is not natively supported in NGINX Gateway Fabric.
1105-
# Unlike ingress-nginx, NGF doesn't have auth annotations.
1106-
# Implementation would require SnippetsFilter + volume mounts which is complex and fragile.
1107-
# TODO: Implement when NGF adds native policy support or use app-level auth.
1108-
#
1109-
# resource "random_string" "basic_auth_password" {
1110-
# count = lookup(var.instance.spec, "basic_auth", false) ? 1 : 0
1111-
# length = 16
1112-
# special = true
1113-
# }
1114-
#
1115-
# resource "kubernetes_secret" "basic_auth" {
1116-
# count = lookup(var.instance.spec, "basic_auth", false) ? 1 : 0
1117-
#
1118-
# metadata {
1119-
# name = "${local.name}-basic-auth"
1120-
# namespace = var.environment.namespace
1121-
# }
1122-
#
1123-
# data = {
1124-
# username = "${var.instance_name}-user"
1125-
# password = random_string.basic_auth_password[0].result
1126-
# }
1127-
#
1128-
# type = "Opaque"
1129-
# }
1175+
resource "kubernetes_secret" "basic_auth" {
1176+
count = lookup(var.instance.spec, "basic_auth", false) ? 1 : 0
1177+
1178+
metadata {
1179+
name = "${local.name}-basic-auth"
1180+
namespace = var.environment.namespace
1181+
}
1182+
1183+
data = {
1184+
auth = "${var.instance_name}user:${bcrypt(random_string.basic_auth_password[0].result)}"
1185+
}
1186+
1187+
type = "nginx.org/htpasswd"
1188+
1189+
lifecycle {
1190+
ignore_changes = [data]
1191+
create_before_destroy = true
1192+
}
1193+
}
11301194

11311195
# Load Balancer Service Discovery
11321196
# Note: The LoadBalancer service is created by NGINX Gateway Fabric controller

modules/ingress/nginx_gateway_fabric_legacy/1.0/outputs.tf

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
locals {
2-
# Basic auth is not supported in NGINX Gateway Fabric (see main.tf)
3-
# username = lookup(var.instance.spec, "basic_auth", false) && length(random_string.basic_auth_password) > 0 ? "${var.instance_name}-user" : ""
4-
# password = lookup(var.instance.spec, "basic_auth", false) && length(random_string.basic_auth_password) > 0 ? random_string.basic_auth_password[0].result : ""
5-
# is_auth_enabled = length(local.username) > 0 && length(local.password) > 0 ? true : false
2+
username = lookup(var.instance.spec, "basic_auth", false) ? "${var.instance_name}user" : ""
3+
password = lookup(var.instance.spec, "basic_auth", false) && length(random_string.basic_auth_password) > 0 ? random_string.basic_auth_password[0].result : ""
4+
is_auth_enabled = length(local.username) > 0 && length(local.password) > 0
65

76
output_attributes = merge(
87
{
@@ -27,13 +26,12 @@ locals {
2726

2827
output_interfaces = {
2928
for route_key, route in local.rulesFiltered : route_key => {
30-
connection_string = "https://${route.host}"
29+
connection_string = local.is_auth_enabled ? "https://${local.username}:${local.password}@${route.host}" : "https://${route.host}"
3130
host = route.host
3231
port = 443
33-
# Basic auth not supported - username/password removed
34-
# username = local.username
35-
# password = local.password
36-
secrets = []
32+
username = local.username
33+
password = local.password
34+
secrets = local.is_auth_enabled ? ["connection_string", "password"] : []
3735
}
3836
}
3937
}

0 commit comments

Comments
 (0)