diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go index ae5434516..a8e929e06 100644 --- a/internal/ingress/controller/config/config.go +++ b/internal/ingress/controller/config/config.go @@ -607,6 +607,9 @@ type Configuration struct { // Block all requests with given Referer headers BlockReferers []string `json:"block-referers"` + + // Lua shared dict configuration data / certificate data + LuaSharedDicts map[string]int `json:"lua-shared-dicts"` } // NewDefault returns the default nginx configuration diff --git a/internal/ingress/controller/template/configmap.go b/internal/ingress/controller/template/configmap.go index cd493e2f0..76492180b 100644 --- a/internal/ingress/controller/template/configmap.go +++ b/internal/ingress/controller/template/configmap.go @@ -59,6 +59,7 @@ const ( globalAuthSnippet = "global-auth-snippet" globalAuthCacheKey = "global-auth-cache-key" globalAuthCacheDuration = "global-auth-cache-duration" + luaSharedDicts = "lua-shared-dicts" ) var ( @@ -87,7 +88,22 @@ func ReadConfig(src map[string]string) config.Configuration { blockUserAgentList := make([]string, 0) blockRefererList := make([]string, 0) responseHeaders := make([]string, 0) + luaSharedDict := make(map[string]int) + //parse lua shared dict values + if val, ok := conf[luaSharedDicts]; ok { + delete(conf, luaSharedDicts) + lsd := strings.Split(val, ",") + for _, v := range lsd { + v = strings.Replace(v, " ", "", -1) + results := strings.SplitN(v, ":", 2) + val, err := strconv.Atoi(results[1]) + if err != nil { + klog.Warningf("%v is not a valid lua entry: %v", v, err) + } + luaSharedDict[results[0]] = val + } + } if val, ok := conf[customHTTPErrors]; ok { delete(conf, customHTTPErrors) for _, i := range strings.Split(val, ",") { @@ -305,6 +321,7 @@ func ReadConfig(src map[string]string) config.Configuration { to.HideHeaders = hideHeadersList to.ProxyStreamResponses = streamResponses to.DisableIpv6DNS = !ing_net.IsIPv6Enabled() + to.LuaSharedDicts = luaSharedDict config := &mapstructure.DecoderConfig{ Metadata: nil, diff --git a/internal/ingress/controller/template/configmap_test.go b/internal/ingress/controller/template/configmap_test.go index 765bca260..24ef0bdbd 100644 --- a/internal/ingress/controller/template/configmap_test.go +++ b/internal/ingress/controller/template/configmap_test.go @@ -74,6 +74,7 @@ func TestMergeConfigMapToStruct(t *testing.T) { "nginx-status-ipv6-whitelist": "::1,2001::/16", "proxy-add-original-uri-header": "false", "disable-ipv6-dns": "true", + "lua-shared-dicts": "configuration_data:5,certificate_data:5", } def := config.NewDefault() def.CustomHTTPErrors = []int{300, 400} @@ -95,7 +96,7 @@ func TestMergeConfigMapToStruct(t *testing.T) { def.NginxStatusIpv4Whitelist = []string{"127.0.0.1", "10.0.0.0/24"} def.NginxStatusIpv6Whitelist = []string{"::1", "2001::/16"} def.ProxyAddOriginalURIHeader = false - + def.LuaSharedDicts = map[string]int{"configuration_data": 5, "certificate_data": 5} def.DisableIpv6DNS = true hash, err := hashstructure.Hash(def, &hashstructure.HashOptions{ @@ -303,3 +304,51 @@ func TestGlobalExternalAuthCacheDurationParsing(t *testing.T) { } } } + +func TestLuaSharedDict(t *testing.T) { + + testsCases := []struct { + name string + entry map[string]string + expect map[string]int + }{ + { + name: "lua valid entry", + entry: map[string]string{"lua-shared-dicts": "configuration_data:5,certificate_data:5"}, + expect: map[string]int{"configuration_data": 5, "certificate_data": 5}, + }, + + { + name: "lua invalid entry", + entry: map[string]string{"lua-shared-dict": "configuration_data:5,certificate_data:5"}, + expect: map[string]int{}, + }, + { + name: "lua mixed entry", + entry: map[string]string{"lua-shared-dicts": "configuration_data:10,certificate_data:5"}, + expect: map[string]int{"configuration_data": 10, "certificate_data": 5}, + }, + { + name: "lua valid entry - configuration_data only", + entry: map[string]string{"lua-shared-dicts": "configuration_data:5"}, + expect: map[string]int{"configuration_data": 5}, + }, + { + name: "lua valid entry certificate_data only", + entry: map[string]string{"lua-shared-dicts": "certificate_data:5"}, + expect: map[string]int{"certificate_data": 5}, + }, + { + name: "lua valid entry certificate_data only", + entry: map[string]string{"lua-shared-dicts": "configuration_data:10, my_random_dict:15,another_example:2"}, + expect: map[string]int{"configuration_data": 10, "my_random_dict": 15, "another_example": 2}, + }, + } + + for n, tc := range testsCases { + cfg := ReadConfig(tc.entry) + if !reflect.DeepEqual(cfg.LuaSharedDicts, tc.expect) { + t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", n, tc.expect, cfg.LuaSharedDicts) + } + } +} diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index 146b7d4aa..e5e0672c4 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -216,18 +216,33 @@ func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool { return false } -func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string { +func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF bool) string { + + var out []string + // Load config + cfg, ok := c.(config.Configuration) + if !ok { + klog.Errorf("expected a 'config.Configuration' type but %T was returned", c) + return "" + } servers, ok := s.([]*ingress.Server) if !ok { klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s) return "" } - - out := []string{ - "lua_shared_dict configuration_data 15M", - "lua_shared_dict certificate_data 16M", + // check if config contains lua "lua_configuration_data" value otherwise, use default + cfgData, ok := cfg.LuaSharedDicts["configuration_data"] + if !ok { + cfgData = 15 } + out = append(out, fmt.Sprintf("lua_shared_dict configuration_data %dM", cfgData)) + // check if config contains "lua_certificate_data" value otherwise, use default + certData, ok := cfg.LuaSharedDicts["certificate_data"] + if !ok { + certData = 16 + } + out = append(out, fmt.Sprintf("lua_shared_dict certificate_data %dM", certData)) if !disableLuaRestyWAF { luaRestyWAFEnabled := func() bool { for _, server := range servers { @@ -243,7 +258,6 @@ func buildLuaSharedDictionaries(s interface{}, disableLuaRestyWAF bool) string { out = append(out, "lua_shared_dict waf_storage 64M") } } - return strings.Join(out, ";\n\r") + ";" } diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go index ab13efdd3..be846983c 100644 --- a/internal/ingress/controller/template/template_test.go +++ b/internal/ingress/controller/template/template_test.go @@ -168,7 +168,14 @@ proxy_pass http://upstream_balancer;`, func TestBuildLuaSharedDictionaries(t *testing.T) { invalidType := &ingress.Ingress{} expected := "" - actual := buildLuaSharedDictionaries(invalidType, true) + + // config lua dict + cfg := config.Configuration{ + LuaSharedDicts: map[string]int{ + "configuration_data": 10, "certificate_data": 20, + }, + } + actual := buildLuaSharedDictionaries(cfg, invalidType, true) if !reflect.DeepEqual(expected, actual) { t.Errorf("Expected '%v' but returned '%v'", expected, actual) @@ -184,9 +191,9 @@ func TestBuildLuaSharedDictionaries(t *testing.T) { Locations: []*ingress.Location{{Path: "/", LuaRestyWAF: luarestywaf.Config{}}}, }, } - - configuration := buildLuaSharedDictionaries(servers, false) - if !strings.Contains(configuration, "lua_shared_dict configuration_data") { + // returns value from config + configuration := buildLuaSharedDictionaries(cfg, servers, false) + if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n\rlua_shared_dict certificate_data 20M;") { t.Errorf("expected to include 'configuration_data' but got %s", configuration) } if strings.Contains(configuration, "waf_storage") { @@ -194,10 +201,15 @@ func TestBuildLuaSharedDictionaries(t *testing.T) { } servers[1].Locations[0].LuaRestyWAF = luarestywaf.Config{Mode: "ACTIVE"} - configuration = buildLuaSharedDictionaries(servers, false) + configuration = buildLuaSharedDictionaries(cfg, servers, false) if !strings.Contains(configuration, "lua_shared_dict waf_storage") { t.Errorf("expected to configure 'waf_storage', but got %s", configuration) } + // test invalid config + configuration = buildLuaSharedDictionaries(invalidType, servers, false) + if expected != actual { + t.Errorf("Expected '%v' but returned '%v' ", expected, actual) + } } func TestFormatIP(t *testing.T) { diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index a00f6eaf0..1319f8b1a 100755 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -51,7 +51,7 @@ http { lua_package_path "/usr/local/openresty/site/lualib/?.ljbc;/usr/local/openresty/site/lualib/?/init.ljbc;/usr/local/openresty/lualib/?.ljbc;/usr/local/openresty/lualib/?/init.ljbc;/usr/local/openresty/site/lualib/?.lua;/usr/local/openresty/site/lualib/?/init.lua;/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/?/init.lua;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;/usr/local/lib/lua/?.lua;;"; lua_package_cpath "/usr/local/openresty/site/lualib/?.so;/usr/local/openresty/lualib/?.so;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;;"; - {{ buildLuaSharedDictionaries $servers $all.Cfg.DisableLuaRestyWAF }} + {{ buildLuaSharedDictionaries $cfg $servers $all.Cfg.DisableLuaRestyWAF }} init_by_lua_block { collectgarbage("collect") diff --git a/test/e2e/lua/dynamic_configuration.go b/test/e2e/lua/dynamic_configuration.go index a5eb34d53..c687f0ae4 100644 --- a/test/e2e/lua/dynamic_configuration.go +++ b/test/e2e/lua/dynamic_configuration.go @@ -70,6 +70,30 @@ var _ = framework.IngressNginxDescribe("Dynamic Configuration", func() { }) }) + Context("Lua shared dict", func() { + It("update config", func() { + + host := "foo.com" + ingress := framework.NewSingleIngress(host, "/", host, f.Namespace, "http-svc", 80, nil) + f.EnsureIngress(ingress) + + lkey := "lua-shared-dicts" + lval := "configuration_data:100,certificate_data:200" + + By("update shared dict") + + f.UpdateNginxConfigMapData(lkey, lval) + + var nginxConfig string + f.WaitForNginxConfiguration(func(cfg string) bool { + nginxConfig = cfg + return true + }) + + Expect(strings.ContainsAny(nginxConfig, "configuration_data:100M,certificate_data:200M"), true) + }) + }) + Context("when only backends change", func() { It("handles endpoints only changes", func() { var nginxConfig string diff --git a/test/e2e/settings/lua_shared_dicts.go b/test/e2e/settings/lua_shared_dicts.go new file mode 100644 index 000000000..5ebe05041 --- /dev/null +++ b/test/e2e/settings/lua_shared_dicts.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package settings + +import ( + "strings" + + . "github.com/onsi/ginkgo" + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.IngressNginxDescribe("LuaSharedDict", func() { + + f := framework.NewDefaultFramework("lua-shared-dicts") + host := "lua-shared-dicts" + + BeforeEach(func() { + f.NewEchoDeployment() + }) + + AfterEach(func() { + }) + + It("update lua shared dict", func() { + ingress := framework.NewSingleIngress(host, "/", host, f.Namespace, "http-svc", 80, nil) + f.EnsureIngress(ingress) + By("update shared dict") + f.UpdateNginxConfigMapData("lua-shared-dicts", "configuration_data:123,certificate_data:456") + f.WaitForNginxConfiguration(func(cfg string) bool { + return strings.Contains(cfg, "lua_shared_dict configuration_data 123M; lua_shared_dict certificate_data 456M;") + }) + }) +})