diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go index f74b3245e..7d6dcc3fa 100644 --- a/internal/ingress/controller/nginx.go +++ b/internal/ingress/controller/nginx.go @@ -690,6 +690,10 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error { return err } + err = n.createLuaConfig(&cfg) + if err != nil { + return err + } err = createOpentelemetryCfg(&cfg) if err != nil { return err @@ -1079,6 +1083,32 @@ func createOpentelemetryCfg(cfg *ngx_config.Configuration) error { return os.WriteFile(cfg.OpentelemetryConfig, tmplBuf.Bytes(), file.ReadWriteByUser) } +func (n *NGINXController) createLuaConfig(cfg *ngx_config.Configuration) error { + luaconfigs := &ngx_template.LuaConfig{ + EnableMetrics: n.cfg.EnableMetrics, + ListenPorts: ngx_template.LuaListenPorts{ + HTTPSPort: strconv.Itoa(n.cfg.ListenPorts.HTTPS), + StatusPort: strconv.Itoa(nginx.StatusPort), + SSLProxyPort: strconv.Itoa(n.cfg.ListenPorts.SSLProxy), + }, + UseProxyProtocol: cfg.UseProxyProtocol, + UseForwardedHeaders: cfg.UseForwardedHeaders, + IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough, + HTTPRedirectCode: cfg.HTTPRedirectCode, + EnableOCSP: cfg.EnableOCSP, + MonitorBatchMaxSize: n.cfg.MonitorMaxBatchSize, + HSTS: cfg.HSTS, + HSTSMaxAge: cfg.HSTSMaxAge, + HSTSIncludeSubdomains: cfg.HSTSIncludeSubdomains, + HSTSPreload: cfg.HSTSPreload, + } + jsonCfg, err := json.Marshal(luaconfigs) + if err != nil { + return err + } + return os.WriteFile(luaCfgPath, jsonCfg, file.ReadWriteByUser) +} + func cleanTempNginxCfg() error { var files []string diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index b055c73cd..4b92519d3 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -194,13 +194,39 @@ func cleanConf(in, out *bytes.Buffer) error { } } -type LuaConfigs struct { - EnableMetrics bool `json:"enable_metrics"` - HTTPSPort string `json:"httpsPort"` - StatusPort string `json:"status_port"` - UseForwardedPorts bool `json:"use_forwarded_ports"` - EnableOCSP bool `json:"enable_ocsp"` - MonitorBatchMaxSize int `json:"monitor_batch_max_size"` +/* LuaConfig defines the structure that will be written as a config for lua scripts +The json format should follow what's expected by lua: + use_forwarded_headers = %t, + use_proxy_protocol = %t, + is_ssl_passthrough_enabled = %t, + http_redirect_code = %v, + listen_ports = { ssl_proxy = "%v", https = "%v" }, + + hsts = %t, + hsts_max_age = %v, + hsts_include_subdomains = %t, + hsts_preload = %t, +*/ + +type LuaConfig struct { + EnableMetrics bool `json:"enable_metrics"` + ListenPorts LuaListenPorts `json:"listen_ports"` + UseForwardedHeaders bool `json:"use_forwarded_headers"` + UseProxyProtocol bool `json:"use_proxy_protocol"` + IsSSLPassthroughEnabled bool `json:"is_ssl_passthrough_enabled"` + HTTPRedirectCode int `json:"http_redirect_code"` + EnableOCSP bool `json:"enable_ocsp"` + MonitorBatchMaxSize int `json:"monitor_batch_max_size"` + HSTS bool `json:"hsts"` + HSTSMaxAge string `json:"hsts_max_age"` + HSTSIncludeSubdomains bool `json:"hsts_include_subdomains"` + HSTSPreload bool `json:"hsts_preload"` +} + +type LuaListenPorts struct { + HTTPSPort string `json:"https"` + StatusPort string `json:"status_port"` + SSLProxyPort string `json:"ssl_proxy"` } // Write populates a buffer using a template with NGINX configuration @@ -441,13 +467,21 @@ func locationConfigForLua(l, a interface{}) string { return "{}" } - return fmt.Sprintf(`{ - force_ssl_redirect = %t, - ssl_redirect = %t, - force_no_ssl_redirect = %t, - preserve_trailing_slash = %t, - use_port_in_redirects = %t, - }`, + /* Lua expects the following vars + force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect), + ssl_redirect = string_to_bool(ngx.var.ssl_redirect), + force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect), + preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash), + use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects), + */ + + return fmt.Sprintf(` + set $force_ssl_redirect "%t"; + set $ssl_redirect "%t"; + set $force_no_ssl_redirect "%t"; + set $preserve_trailing_slash "%t"; + set $use_port_in_redirects "%t"; + `, location.Rewrite.ForceSSLRedirect, location.Rewrite.SSLRedirect, isLocationInLocationList(l, all.Cfg.NoTLSRedirectLocations), diff --git a/internal/ingress/controller/util.go b/internal/ingress/controller/util.go index 8851f323f..f7b75f21b 100644 --- a/internal/ingress/controller/util.go +++ b/internal/ingress/controller/util.go @@ -98,8 +98,9 @@ func rlimitMaxNumFiles() int { } const ( - defBinary = "/usr/bin/nginx" - cfgPath = "/etc/nginx/nginx.conf" + defBinary = "/usr/bin/nginx" + cfgPath = "/etc/nginx/nginx.conf" + luaCfgPath = "/etc/nginx/lua/cfg.json" ) // NginxExecTester defines the interface to execute diff --git a/rootfs/etc/nginx/lua/lua_ingress.lua b/rootfs/etc/nginx/lua/lua_ingress.lua index 65cf5a257..a513928cf 100644 --- a/rootfs/etc/nginx/lua/lua_ingress.lua +++ b/rootfs/etc/nginx/lua/lua_ingress.lua @@ -1,4 +1,5 @@ local ngx_re_split = require("ngx.re").split +local string_to_bool = require("util").string_to_bool local certificate_configured_for_current_request = require("certificate").configured_for_current_request @@ -108,7 +109,16 @@ end -- rewrite gets called in every location context. -- This is where we do variable assignments to be used in subsequent -- phases or redirection -function _M.rewrite(location_config) +function _M.rewrite() + + local location_config = { + force_ssl_redirect = string_to_bool(ngx.var.force_ssl_redirect), + ssl_redirect = string_to_bool(ngx.var.ssl_redirect), + force_no_ssl_redirect = string_to_bool(ngx.var.force_no_ssl_redirect), + preserve_trailing_slash = string_to_bool(ngx.var.preserve_trailing_slash), + use_port_in_redirects = string_to_bool(ngx.var.use_port_in_redirects), + } + ngx.var.pass_access_scheme = ngx.var.scheme ngx.var.best_http_host = ngx.var.http_host or ngx.var.host diff --git a/rootfs/etc/nginx/lua/nginx/ngx_rewrite.lua b/rootfs/etc/nginx/lua/nginx/ngx_rewrite.lua new file mode 100644 index 000000000..66fdd6d55 --- /dev/null +++ b/rootfs/etc/nginx/lua/nginx/ngx_rewrite.lua @@ -0,0 +1,5 @@ +local lua_ingress = require("lua_ingress") +local balancer = require("balancer") + +lua_ingress.rewrite() +balancer.rewrite() \ No newline at end of file diff --git a/rootfs/etc/nginx/lua/ngx_conf_init.lua b/rootfs/etc/nginx/lua/ngx_conf_init.lua index cb4ba8e28..6147dbc47 100644 --- a/rootfs/etc/nginx/lua/ngx_conf_init.lua +++ b/rootfs/etc/nginx/lua/ngx_conf_init.lua @@ -1,46 +1,54 @@ -local function initialize_ingress(statusport, enablemetrics, ocsp, ingress) - collectgarbage("collect") - -- init modules - local ok, res - ok, res = pcall(require, "lua_ingress") - if not ok then - error("require failed: " .. tostring(res)) - else - lua_ingress = res - lua_ingress.set_config(ingress) - end +local cjson = require("cjson.safe") - ok, res = pcall(require, "configuration") - if not ok then - error("require failed: " .. tostring(res)) - else - configuration = res - configuration.prohibited_localhost_port = statusport - end +collectgarbage("collect") +local f = io.open("/etc/nginx/lua/cfg.json", "r") +local content = f:read("*a") +f:close() +local configfile = cjson.decode(content) - ok, res = pcall(require, "balancer") +local luaconfig = ngx.shared.luaconfig +luaconfig:set("enablemetrics", configfile.enable_metrics) +luaconfig:set("listen_https_ports", configfile.listen_ports.https) +luaconfig:set("use_forwarded_headers", configfile.use_forwarded_headers) +-- init modules +local ok, res +ok, res = pcall(require, "lua_ingress") +if not ok then + error("require failed: " .. tostring(res)) +else + lua_ingress = res + lua_ingress.set_config(configfile) +end +ok, res = pcall(require, "configuration") +if not ok then + error("require failed: " .. tostring(res)) +else + configuration = res + if not configfile.listen_ports.status_port then + error("required status port not found") + end + configuration.prohibited_localhost_port = configfile.listen_ports.status_port +end +ok, res = pcall(require, "balancer") +if not ok then + error("require failed: " .. tostring(res)) +else + balancer = res +end +if configfile.enable_metrics then + ok, res = pcall(require, "monitor") if not ok then - error("require failed: " .. tostring(res)) + error("require failed: " .. tostring(res)) else - balancer = res - end - - if enablemetrics then - ok, res = pcall(require, "monitor") - if not ok then - error("require failed: " .. tostring(res)) - else - monitor = res - end - end - - ok, res = pcall(require, "certificate") - if not ok then - error("require failed: " .. tostring(res)) - else - certificate = res - certificate.is_ocsp_stapling_enabled = ocsp + monitor = res end end - -return { initialize_ingress = initialize_ingress } \ No newline at end of file +ok, res = pcall(require, "certificate") +if not ok then + error("require failed: " .. tostring(res)) +else + certificate = res + if configfile.enable_ocsp then + certificate.is_ocsp_stapling_enabled = configfile.enable_ocsp + end +end \ No newline at end of file diff --git a/rootfs/etc/nginx/lua/ngx_conf_init_stream.lua b/rootfs/etc/nginx/lua/ngx_conf_init_stream.lua index d5f304834..a78062d0a 100644 --- a/rootfs/etc/nginx/lua/ngx_conf_init_stream.lua +++ b/rootfs/etc/nginx/lua/ngx_conf_init_stream.lua @@ -1,31 +1,30 @@ -local function initialize_stream(statusport) - collectgarbage("collect") - - -- init modules - local ok, res - - ok, res = pcall(require, "configuration") - if not ok then - error("require failed: " .. tostring(res)) - else - configuration = res - end - - ok, res = pcall(require, "tcp_udp_configuration") - if not ok then - error("require failed: " .. tostring(res)) - else - tcp_udp_configuration = res - tcp_udp_configuration.prohibited_localhost_port = statusport - - end - - ok, res = pcall(require, "tcp_udp_balancer") - if not ok then - error("require failed: " .. tostring(res)) - else - tcp_udp_balancer = res - end +local cjson = require("cjson.safe") +collectgarbage("collect") +local f = io.open("/etc/nginx/lua/cfg.json", "r") +local content = f:read("*a") +f:close() +local configfile = cjson.decode(content) +-- init modules +local ok, res +ok, res = pcall(require, "configuration") +if not ok then + error("require failed: " .. tostring(res)) +else + configuration = res +end +ok, res = pcall(require, "tcp_udp_configuration") +if not ok then + error("require failed: " .. tostring(res)) +else + tcp_udp_configuration = res + if not configfile.listen_ports.status_port then + error("required status port not found") + end + tcp_udp_configuration.prohibited_localhost_port = configfile.listen_ports.status_port +end +ok, res = pcall(require, "tcp_udp_balancer") +if not ok then + error("require failed: " .. tostring(res)) +else + tcp_udp_balancer = res end - -return { initialize_stream = initialize_stream } \ No newline at end of file diff --git a/rootfs/etc/nginx/lua/ngx_conf_init_worker.lua b/rootfs/etc/nginx/lua/ngx_conf_init_worker.lua index 68531537a..cba866136 100644 --- a/rootfs/etc/nginx/lua/ngx_conf_init_worker.lua +++ b/rootfs/etc/nginx/lua/ngx_conf_init_worker.lua @@ -1,12 +1,15 @@ -local function initialize_worker(enablemetrics, monitorbatchsize) - local lua_ingress = require("lua_ingress") - local balancer = require("balancer") - local monitor = require("monitor") - lua_ingress.init_worker() - balancer.init_worker() - if enablemetrics then - monitor.init_worker(monitorbatchsize) - end -end +local cjson = require("cjson.safe") -return { initialize_worker = initialize_worker } \ No newline at end of file +local f = io.open("/etc/nginx/lua/cfg.json", "r") +local content = f:read("*a") +f:close() +local configfile = cjson.decode(content) + +local lua_ingress = require("lua_ingress") +local balancer = require("balancer") +local monitor = require("monitor") +lua_ingress.init_worker() +balancer.init_worker() +if configfile.enable_metrics and configfile.monitor_batch_max_size then + monitor.init_worker(configfile.monitor_batch_max_size) +end \ No newline at end of file diff --git a/rootfs/etc/nginx/lua/util.lua b/rootfs/etc/nginx/lua/util.lua index 7389f3226..1469f4dde 100644 --- a/rootfs/etc/nginx/lua/util.lua +++ b/rootfs/etc/nginx/lua/util.lua @@ -146,6 +146,11 @@ function _M.is_blank(str) return str == nil or string_len(str) == 0 end +function _M.string_to_bool(str) + if str == "true" then return true end + return false +end + -- this implementation is taken from: -- https://github.com/luafun/luafun/blob/master/fun.lua#L33 -- SHA: 04c99f9c393e54a604adde4b25b794f48104e0d0 diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 44e595739..2f6d2f2c9 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -70,21 +70,9 @@ http { lua_shared_dict luaconfig 5m; - {{/* We need to keep this lua block inline, because init_worker_by_lua_file does not support using arguments */}} - init_by_lua_block { - local luaconfig = ngx.shared.luaconfig - local ingresscfg = {{ configForLua $all }} - luaconfig:set("enablemetrics", {{ $all.EnableMetrics }}) - luaconfig:set("listen_https_ports", '{{ $all.ListenPorts.HTTPS }}') - luaconfig:set("use_forwarded_headers", {{ $cfg.UseForwardedHeaders }}) - local ngx_conf_init = require('ngx_conf_init') - ngx_conf_init.initialize_ingress('{{ .StatusPort }}', {{ $all.EnableMetrics }}, {{ $cfg.EnableOCSP }}, ingresscfg) - } + init_by_lua_file /etc/nginx/lua/ngx_conf_init.lua; - init_worker_by_lua_block { - local ngx_conf_init_worker = require('ngx_conf_init_worker') - ngx_conf_init_worker.initialize_worker({{ $all.EnableMetrics }}, {{ $all.MonitorMaxBatchSize }}) - } + init_worker_by_lua_file /etc/nginx/lua/ngx_conf_init_worker.lua; {{/* Enable the real_ip module only if we use either X-Forwarded headers or Proxy Protocol. */}} {{/* we use the value of the real IP for the geo_ip module */}} @@ -703,10 +691,7 @@ stream { {{ buildResolvers $cfg.Resolver $cfg.DisableIpv6DNS }} - init_by_lua_block { - local ngx_conf_init_stream = require('ngx_conf_init_stream') - ngx_conf_init_stream.initialize_stream('{{ .StatusPort }}') - } + init_by_lua_file /etc/nginx/lua/ngx_conf_init_stream.lua; init_worker_by_lua_file /etc/nginx/lua/nginx/ngx_conf_init_tcp_udp.lua; @@ -1134,10 +1119,9 @@ stream { mirror_request_body {{ $location.Mirror.RequestBody }}; {{ end }} - rewrite_by_lua_block { - lua_ingress.rewrite({{ locationConfigForLua $location $all }}) - balancer.rewrite() - } + {{ locationConfigForLua $location $all }} + + rewrite_by_lua_file /etc/nginx/lua/nginx/ngx_rewrite.lua; header_filter_by_lua_file /etc/nginx/lua/nginx/ngx_conf_srv_hdr_filter.lua; diff --git a/test/e2e/lua/dynamic_configuration.go b/test/e2e/lua/dynamic_configuration.go index 63c514ca7..a5e2196ce 100644 --- a/test/e2e/lua/dynamic_configuration.go +++ b/test/e2e/lua/dynamic_configuration.go @@ -48,12 +48,7 @@ var _ = framework.IngressNginxDescribe("[Lua] dynamic configuration", func() { ginkgo.It("configures balancer Lua middleware correctly", func() { f.WaitForNginxConfiguration(func(cfg string) bool { - return strings.Contains(cfg, "balancer.init_worker()") && strings.Contains(cfg, "balancer_by_lua_file /etc/nginx/lua/nginx/ngx_conf_balancer.lua") - }) - - host := "foo.com" - f.WaitForNginxServer(host, func(server string) bool { - return strings.Contains(server, "balancer.rewrite()") && strings.Contains(server, "balancer.log()") + return strings.Contains(cfg, "balancer_by_lua_file /etc/nginx/lua/nginx/ngx_conf_balancer.lua") }) }) diff --git a/test/e2e/settings/no_tls_redirect_locations.go b/test/e2e/settings/no_tls_redirect_locations.go index 8339eb23e..18fd09e26 100644 --- a/test/e2e/settings/no_tls_redirect_locations.go +++ b/test/e2e/settings/no_tls_redirect_locations.go @@ -33,7 +33,7 @@ var _ = framework.DescribeSetting("Add no tls redirect locations", func() { f.EnsureIngress(ing) f.WaitForNginxConfiguration(func(server string) bool { - return !strings.Contains(server, "force_no_ssl_redirect = true,") + return strings.Contains(server, "set $force_no_ssl_redirect \"false\"") }) wlKey := "no-tls-redirect-locations" @@ -42,7 +42,7 @@ var _ = framework.DescribeSetting("Add no tls redirect locations", func() { f.UpdateNginxConfigMapData(wlKey, wlValue) f.WaitForNginxConfiguration(func(server string) bool { - return strings.Contains(server, "force_no_ssl_redirect = true,") + return strings.Contains(server, "set $force_no_ssl_redirect \"true\"") }) }) }) diff --git a/test/e2e/settings/tls.go b/test/e2e/settings/tls.go index 51f760df8..f5be40d20 100644 --- a/test/e2e/settings/tls.go +++ b/test/e2e/settings/tls.go @@ -109,10 +109,6 @@ var _ = framework.DescribeSetting("[SSL] TLS protocols, ciphers and headers)", f ginkgo.It("setting max-age parameter", func() { f.UpdateNginxConfigMapData(hstsMaxAge, "86400") - f.WaitForNginxConfiguration(func(server string) bool { - return strings.Contains(server, `hsts_max_age = 86400,`) - }) - f.HTTPTestClientWithTLSConfig(tlsConfig). GET("/"). WithURL(f.GetURL(framework.HTTPS)). @@ -128,10 +124,6 @@ var _ = framework.DescribeSetting("[SSL] TLS protocols, ciphers and headers)", f hstsIncludeSubdomains: "false", }) - f.WaitForNginxConfiguration(func(server string) bool { - return strings.Contains(server, `hsts_include_subdomains = false,`) - }) - f.HTTPTestClientWithTLSConfig(tlsConfig). GET("/"). WithURL(f.GetURL(framework.HTTPS)). @@ -148,10 +140,6 @@ var _ = framework.DescribeSetting("[SSL] TLS protocols, ciphers and headers)", f hstsIncludeSubdomains: "false", }) - f.WaitForNginxConfiguration(func(server string) bool { - return strings.Contains(server, `hsts_preload = true,`) - }) - f.HTTPTestClientWithTLSConfig(tlsConfig). GET("/"). WithURL(f.GetURL(framework.HTTPS)).