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

lua-shared-dicts improvements, fixes and documentation #4446

Merged
merged 3 commits into from
Aug 15, 2019
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
16 changes: 16 additions & 0 deletions docs/user-guide/nginx-configuration/configmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ The following table shows a configuration option's name, type, and the default v
|[skip-access-log-urls](#skip-access-log-urls)|[]string|[]string{}|
|[limit-rate](#limit-rate)|int|0|
|[limit-rate-after](#limit-rate-after)|int|0|
|[lua-shared-dicts](#lua-shared-dicts)|string|""|
|[http-redirect-code](#http-redirect-code)|int|308|
|[proxy-buffering](#proxy-buffering)|string|"off"|
|[limit-req-status-code](#limit-req-status-code)|int|503|
Expand Down Expand Up @@ -847,6 +848,21 @@ _References:_

Sets the initial amount after which the further transmission of a response to a client will be rate limited.

## lua-shared-dicts

Customize default Lua shared dictionaries or define more. You can use the following syntax to do so:

```
lua-shared-dicts: "<my dict name>: <my dict size>, [<my dict name>: <my dict size>], ..."
```

For example following will set default `certificate_data` dictionary to `100M` and will introduce a new dictionary called
`my_custom_plugin`:

```
lua-shared-dicts: "certificate_data: 100, my_custom_plugin: 5"
```

_References:_
[http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after](http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after)

Expand Down
45 changes: 36 additions & 9 deletions internal/ingress/controller/template/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,20 @@ const (
globalAuthSnippet = "global-auth-snippet"
globalAuthCacheKey = "global-auth-cache-key"
globalAuthCacheDuration = "global-auth-cache-duration"
luaSharedDicts = "lua-shared-dicts"
luaSharedDictsKey = "lua-shared-dicts"
)

var (
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
validRedirectCodes = sets.NewInt([]int{301, 302, 307, 308}...)
defaultLuaSharedDicts = map[string]int{
"configuration_data": 20,
"certificate_data": 20,
}
)

const (
maxAllowedLuaDictSize = 200
maxNumberOfLuaDicts = 100
)

// ReadConfig obtains the configuration defined by the user merged with the defaults.
Expand All @@ -88,20 +97,38 @@ 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)
luaSharedDicts := make(map[string]int)

//parse lua shared dict values
if val, ok := conf[luaSharedDicts]; ok {
delete(conf, luaSharedDicts)
if val, ok := conf[luaSharedDictsKey]; ok {
delete(conf, luaSharedDictsKey)
lsd := strings.Split(val, ",")
for _, v := range lsd {
v = strings.Replace(v, " ", "", -1)
results := strings.SplitN(v, ":", 2)
val, err := strconv.Atoi(results[1])
dictName := results[0]
size, err := strconv.Atoi(results[1])
if err != nil {
klog.Warningf("%v is not a valid lua entry: %v", v, err)
klog.Errorf("Ignoring non integer value %v for Lua dictionary %v: %v.", results[1], dictName, err)
continue
}
if size > maxAllowedLuaDictSize {
klog.Errorf("Ignoring %v for Lua dictionary %v: maximum size is %v.", size, dictName, maxAllowedLuaDictSize)
continue
}
luaSharedDict[results[0]] = val
if len(luaSharedDicts)+1 > maxNumberOfLuaDicts {
klog.Errorf("Ignoring %v for Lua dictionary %v: can not configure more than %v dictionaries.",
size, dictName, maxNumberOfLuaDicts)
continue
}

luaSharedDicts[dictName] = size
}
}
// set default Lua shared dicts
for k, v := range defaultLuaSharedDicts {
if _, ok := luaSharedDicts[k]; !ok {
luaSharedDicts[k] = v
}
}
if val, ok := conf[customHTTPErrors]; ok {
Expand Down Expand Up @@ -321,7 +348,7 @@ func ReadConfig(src map[string]string) config.Configuration {
to.HideHeaders = hideHeadersList
to.ProxyStreamResponses = streamResponses
to.DisableIpv6DNS = !ing_net.IsIPv6Enabled()
to.LuaSharedDicts = luaSharedDict
to.LuaSharedDicts = luaSharedDicts

config := &mapstructure.DecoderConfig{
Metadata: nil,
Expand Down
46 changes: 23 additions & 23 deletions internal/ingress/controller/template/configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
}

def = config.NewDefault()
def.LuaSharedDicts = map[string]int{"configuration_data": 20, "certificate_data": 20}
def.DisableIpv6DNS = true

hash, err = hashstructure.Hash(def, &hashstructure.HashOptions{
Expand All @@ -142,6 +143,7 @@ func TestMergeConfigMapToStruct(t *testing.T) {
}

def = config.NewDefault()
def.LuaSharedDicts = map[string]int{"configuration_data": 20, "certificate_data": 20}
def.WhitelistSourceRange = []string{"1.1.1.1/32"}
def.DisableIpv6DNS = true

Expand Down Expand Up @@ -303,50 +305,48 @@ func TestGlobalExternalAuthCacheDurationParsing(t *testing.T) {
}
}

func TestLuaSharedDict(t *testing.T) {

func TestLuaSharedDictsParsing(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: "default dicts configured when lua-shared-dicts is not set",
entry: make(map[string]string),
expect: defaultLuaSharedDicts,
},

{
name: "lua invalid entry",
entry: map[string]string{"lua-shared-dict": "configuration_data:5,certificate_data:5"},
expect: map[string]int{},
name: "configuration_data only",
entry: map[string]string{"lua-shared-dicts": "configuration_data:5"},
expect: map[string]int{"configuration_data": 5, "certificate_data": 20},
},
{
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: "certificate_data only",
entry: map[string]string{"lua-shared-dicts": "certificate_data: 4"},
expect: map[string]int{"configuration_data": 20, "certificate_data": 4},
},
{
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: "custom dicts",
entry: map[string]string{"lua-shared-dicts": "configuration_data: 10, my_random_dict:15 , another_example:2"},
expect: map[string]int{"configuration_data": 10, "certificate_data": 20, "my_random_dict": 15, "another_example": 2},
},
{
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: "invalid size value should be ignored",
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 1a"},
expect: map[string]int{"configuration_data": 20, "certificate_data": 20, "mydict": 10},
},
{
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},
name: "dictionary size can not be larger than 200",
entry: map[string]string{"lua-shared-dicts": "mydict: 10, invalid_dict: 201"},
expect: map[string]int{"configuration_data": 20, "certificate_data": 20, "mydict": 10},
},
}

for n, tc := range testsCases {
for _, 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)
t.Errorf("Testing %v. Expected \"%v\" but \"%v\" was returned", tc.name, tc.expect, cfg.LuaSharedDicts)
}
}
}
99 changes: 55 additions & 44 deletions internal/ingress/controller/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,36 +133,37 @@ var (
}
return true
},
"escapeLiteralDollar": escapeLiteralDollar,
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
"buildAuthResponseHeaders": buildAuthResponseHeaders,
"buildProxyPass": buildProxyPass,
"filterRateLimits": filterRateLimits,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"configForLua": configForLua,
"locationConfigForLua": locationConfigForLua,
"buildResolvers": buildResolvers,
"buildUpstreamName": buildUpstreamName,
"isLocationInLocationList": isLocationInLocationList,
"isLocationAllowed": isLocationAllowed,
"buildLogFormatUpstream": buildLogFormatUpstream,
"buildDenyVariable": buildDenyVariable,
"getenv": os.Getenv,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"trimSpace": strings.TrimSpace,
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"formatIP": formatIP,
"quote": quote,
"buildNextUpstream": buildNextUpstream,
"getIngressInformation": getIngressInformation,
"escapeLiteralDollar": escapeLiteralDollar,
"shouldConfigureLuaRestyWAF": shouldConfigureLuaRestyWAF,
"buildLuaSharedDictionaries": buildLuaSharedDictionaries,
"luaConfigurationRequestBodySize": luaConfigurationRequestBodySize,
"buildLocation": buildLocation,
"buildAuthLocation": buildAuthLocation,
"shouldApplyGlobalAuth": shouldApplyGlobalAuth,
"buildAuthResponseHeaders": buildAuthResponseHeaders,
"buildProxyPass": buildProxyPass,
"filterRateLimits": filterRateLimits,
"buildRateLimitZones": buildRateLimitZones,
"buildRateLimit": buildRateLimit,
"configForLua": configForLua,
"locationConfigForLua": locationConfigForLua,
"buildResolvers": buildResolvers,
"buildUpstreamName": buildUpstreamName,
"isLocationInLocationList": isLocationInLocationList,
"isLocationAllowed": isLocationAllowed,
"buildLogFormatUpstream": buildLogFormatUpstream,
"buildDenyVariable": buildDenyVariable,
"getenv": os.Getenv,
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix,
"trimSpace": strings.TrimSpace,
"toUpper": strings.ToUpper,
"toLower": strings.ToLower,
"formatIP": formatIP,
"quote": quote,
"buildNextUpstream": buildNextUpstream,
"getIngressInformation": getIngressInformation,
"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
return struct{ First, Second interface{} }{all, server}
},
Expand Down Expand Up @@ -236,7 +237,7 @@ func shouldConfigureLuaRestyWAF(disableLuaRestyWAF bool, mode string) bool {

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)
Expand All @@ -247,20 +248,13 @@ func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF
klog.Errorf("expected an '[]*ingress.Server' type but %T was returned", s)
return ""
}
// 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
for name, size := range cfg.LuaSharedDicts {
out = append(out, fmt.Sprintf("lua_shared_dict %s %dM", name, size))
}
out = append(out, fmt.Sprintf("lua_shared_dict certificate_data %dM", certData))
if !disableLuaRestyWAF {

// TODO: there must be a better place for this
if _, ok := cfg.LuaSharedDicts["waf_storage"]; !ok && !disableLuaRestyWAF {
luaRestyWAFEnabled := func() bool {
for _, server := range servers {
for _, location := range server.Locations {
Expand All @@ -275,7 +269,24 @@ func buildLuaSharedDictionaries(c interface{}, s interface{}, disableLuaRestyWAF
out = append(out, "lua_shared_dict waf_storage 64M")
}
}
return strings.Join(out, ";\n\r") + ";"

return strings.Join(out, ";\n") + ";\n"
}

func luaConfigurationRequestBodySize(c interface{}) string {
cfg, ok := c.(config.Configuration)
if !ok {
klog.Errorf("expected a 'config.Configuration' type but %T was returned", c)
return "100" // just a default number
}

size := cfg.LuaSharedDicts["configuration_data"]
if size < cfg.LuaSharedDicts["certificate_data"] {
size = cfg.LuaSharedDicts["certificate_data"]
}
size = size + 1

return fmt.Sprintf("%d", size)
}

// configForLua returns some general configuration as Lua table represented as string
Expand Down
18 changes: 17 additions & 1 deletion internal/ingress/controller/template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,12 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
}
// 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;") {
if !strings.Contains(configuration, "lua_shared_dict configuration_data 10M;\n") {
t.Errorf("expected to include 'configuration_data' but got %s", configuration)
}
if !strings.Contains(configuration, "lua_shared_dict certificate_data 20M;\n") {
t.Errorf("expected to include 'certificate_data' but got %s", configuration)
}
if strings.Contains(configuration, "waf_storage") {
t.Errorf("expected to not include 'waf_storage' but got %s", configuration)
}
Expand All @@ -222,6 +225,19 @@ func TestBuildLuaSharedDictionaries(t *testing.T) {
}
}

func TestLuaConfigurationRequestBodySize(t *testing.T) {
cfg := config.Configuration{
LuaSharedDicts: map[string]int{
"configuration_data": 10, "certificate_data": 20,
},
}

size := luaConfigurationRequestBodySize(cfg)
if "21" != size {
t.Errorf("expected the size to be 20 but got: %v", size)
}
}

func TestFormatIP(t *testing.T) {
cases := map[string]struct {
Input, Output string
Expand Down
5 changes: 2 additions & 3 deletions rootfs/etc/nginx/template/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -580,9 +580,8 @@ http {
}

location /configuration {
# this should be equals to configuration_data dict
client_max_body_size 10m;
client_body_buffer_size 10m;
client_max_body_size {{ luaConfigurationRequestBodySize $cfg }}m;
client_body_buffer_size {{ luaConfigurationRequestBodySize $cfg }}m;
proxy_buffering off;

content_by_lua_block {
Expand Down
Loading