diff --git a/examples/customization/README.md b/examples/customization/README.md index a714669fe6..3d0da10b01 100644 --- a/examples/customization/README.md +++ b/examples/customization/README.md @@ -31,6 +31,9 @@ The table below summarizes some of the options. More options (extensions) are av | N/A | `real-ip-header` | Sets the value of the [real_ip_header](http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header) directive. | `X-Real-IP`| | N/A | `real-ip-recursive` | Enables or disables the [real_ip_recursive](http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_recursive) directive. | `False`| | `nginx.org/server-tokens` | `server-tokens` | Enables or disables the [server_tokens](http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens) directive. Additionally, with the NGINX Plus controller, you can specify a custom string value. The empty string value disables the emission of the “Server” field. | `True`| +| N/A | `http-snippets` | Sets a custom snippet in http context. | N/A | +| `nginx.org/location-snippets` | `location-snippets` | Sets a custom snippet in location context. | N/A | +| `nginx.org/server-snippets` | `server-snippets` | Sets a custom snippet in server context. | N/A | ## Using ConfigMaps diff --git a/examples/customization/cafe-ingress-with-annotations.yaml b/examples/customization/cafe-ingress-with-annotations.yaml index a7b59b12ff..fec0ecd2e0 100644 --- a/examples/customization/cafe-ingress-with-annotations.yaml +++ b/examples/customization/cafe-ingress-with-annotations.yaml @@ -6,6 +6,17 @@ metadata: nginx.org/proxy-connect-timeout: "30s" nginx.org/proxy-read-timeout: "20s" nginx.org/client-max-body-size: "4m" + nginx.org/location-snippets: | + if ($ssl_client_verify = SUCCESS) { + set $auth_basic off; + } + if ($ssl_client_verify != SUCCESS) { + set $auth_basic "Restricted"; + } + auth_basic $auth_basic; + auth_basic_user_file "/var/run/secrets/nginx.org/auth-basic-file"; + nginx.org/server-snippets: | + ssl_verify_client optional; spec: rules: - host: cafe.example.com diff --git a/examples/customization/nginx-config.yaml b/examples/customization/nginx-config.yaml index 8e4f1d948f..000cccc072 100644 --- a/examples/customization/nginx-config.yaml +++ b/examples/customization/nginx-config.yaml @@ -32,3 +32,15 @@ data: real-ip-header: "proxy_protocol" # default is X-Real-IP. Sets the value of the real_ip_header directive. http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header real-ip-recursive: "True" # default is "False". Enables or disables the real_ip_recursive directive. See http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_recursive server-tokens: "False" # default is "True". Enables or disables the server_tokens directive. See http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens + http-snippets: | # Pipe is used for multiple line snippets. Make sure the snippet is not a default value, in order to avoid duplication. + map $uri $new_uri { + /old.html /index.html; + } + server-snippets: | # No default. Pipe is used for multiple line snippets. Make sure the snippet is not a default value, in order to avoid duplication. + # Old website redirect + if ($new_uri) { + rewrite ^ $new_uri permanent; + } + location-snippets: | # No default. Pipe is used for multiple line snippets. Make sure the snippet is not a default value, in order to avoid duplication. + proxy_temp_path /var/nginx/proxy_temp; + charset koi8-r; \ No newline at end of file diff --git a/nginx-controller/controller/controller.go b/nginx-controller/controller/controller.go index 10b7c296f2..26c9999e6c 100644 --- a/nginx-controller/controller/controller.go +++ b/nginx-controller/controller/controller.go @@ -375,14 +375,14 @@ func (lbc *LoadBalancerController) syncCfgm(key string) { if proxyReadTimeout, exists := cfgm.Data["proxy-read-timeout"]; exists { cfg.ProxyReadTimeout = proxyReadTimeout } - if proxyHideHeaders, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "proxy-hide-headers", cfgm); exists { + if proxyHideHeaders, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "proxy-hide-headers", cfgm, ","); exists { if err != nil { glog.Error(err) } else { cfg.ProxyHideHeaders = proxyHideHeaders } } - if proxyPassHeaders, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "proxy-pass-headers", cfgm); exists { + if proxyPassHeaders, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "proxy-pass-headers", cfgm, ","); exists { if err != nil { glog.Error(err) } else { @@ -405,7 +405,7 @@ func (lbc *LoadBalancerController) syncCfgm(key string) { cfg.HTTP2 = HTTP2 } } - if redirectToHTTPS, exists,err := nginx.GetMapKeyAsBool(cfgm.Data, "redirect-to-https", cfgm); exists { + if redirectToHTTPS, exists, err := nginx.GetMapKeyAsBool(cfgm.Data, "redirect-to-https", cfgm); exists { if err != nil { glog.Error(err) } else { @@ -457,7 +457,7 @@ func (lbc *LoadBalancerController) syncCfgm(key string) { if realIPHeader, exists := cfgm.Data["real-ip-header"]; exists { cfg.RealIPHeader = realIPHeader } - if setRealIPFrom, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "set-real-ip-from", cfgm); exists { + if setRealIPFrom, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "set-real-ip-from", cfgm, ","); exists { if err != nil { glog.Error(err) } else { @@ -515,6 +515,29 @@ func (lbc *LoadBalancerController) syncCfgm(key string) { if proxyMaxTempFileSize, exists := cfgm.Data["proxy-max-temp-file-size"]; exists { cfg.ProxyMaxTempFileSize = proxyMaxTempFileSize } + + if mainHTTPSnippets, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "http-snippets", cfgm, "\n"); exists { + if err != nil { + glog.Error(err) + } else { + cfg.MainHTTPSnippets = mainHTTPSnippets + } + } + if locationSnippets, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "location-snippets", cfgm, "\n"); exists { + if err != nil { + glog.Error(err) + } else { + cfg.LocationSnippets = locationSnippets + } + } + if serverSnippets, exists, err := nginx.GetMapKeyAsStringSlice(cfgm.Data, "server-snippets", cfgm, "\n"); exists { + if err != nil { + glog.Error(err) + } else { + cfg.ServerSnippets = serverSnippets + } + } + } lbc.cnf.UpdateConfig(cfg) diff --git a/nginx-controller/nginx/config.go b/nginx-controller/nginx/config.go index e25daed8bd..74b4927003 100644 --- a/nginx-controller/nginx/config.go +++ b/nginx-controller/nginx/config.go @@ -2,12 +2,15 @@ package nginx // Config holds NGINX configuration parameters type Config struct { + LocationSnippets []string + ServerSnippets []string ServerTokens bool ProxyConnectTimeout string ProxyReadTimeout string ClientMaxBodySize string HTTP2 bool RedirectToHTTPS bool + MainHTTPSnippets []string MainServerNamesHashBucketSize string MainServerNamesHashMaxSize string MainLogFormat string diff --git a/nginx-controller/nginx/configurator.go b/nginx-controller/nginx/configurator.go index e0f20222ba..3279c90614 100644 --- a/nginx-controller/nginx/configurator.go +++ b/nginx-controller/nginx/configurator.go @@ -121,6 +121,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri RealIPRecursive: ingCfg.RealIPRecursive, ProxyHideHeaders: ingCfg.ProxyHideHeaders, ProxyPassHeaders: ingCfg.ProxyPassHeaders, + ServerSnippets: ingCfg.ServerSnippets, } if pemFile, ok := pems[serverName]; ok { @@ -173,6 +174,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri RealIPRecursive: ingCfg.RealIPRecursive, ProxyHideHeaders: ingCfg.ProxyHideHeaders, ProxyPassHeaders: ingCfg.ProxyPassHeaders, + ServerSnippets: ingCfg.ServerSnippets, } if pemFile, ok := pems[emptyHost]; ok { @@ -204,20 +206,36 @@ func (cnf *Configurator) createConfig(ingEx *IngressEx) Config { ingCfg.ServerTokens = serverTokens } } + + if serverSnippets, exists, err := GetMapKeyAsStringSlice(ingEx.Ingress.Annotations, "nginx.org/server-snippets", ingEx.Ingress, "\n"); exists { + if err != nil { + glog.Error(err) + } else { + ingCfg.ServerSnippets = serverSnippets + } + } + if locationSnippets, exists, err := GetMapKeyAsStringSlice(ingEx.Ingress.Annotations, "nginx.org/location-snippets", ingEx.Ingress, "\n"); exists { + if err != nil { + glog.Error(err) + } else { + ingCfg.LocationSnippets = locationSnippets + } + } + if proxyConnectTimeout, exists := ingEx.Ingress.Annotations["nginx.org/proxy-connect-timeout"]; exists { ingCfg.ProxyConnectTimeout = proxyConnectTimeout } if proxyReadTimeout, exists := ingEx.Ingress.Annotations["nginx.org/proxy-read-timeout"]; exists { ingCfg.ProxyReadTimeout = proxyReadTimeout } - if proxyHideHeaders, exists, err := GetMapKeyAsStringSlice(ingEx.Ingress.Annotations, "nginx.org/proxy-hide-headers", ingEx.Ingress); exists { + if proxyHideHeaders, exists, err := GetMapKeyAsStringSlice(ingEx.Ingress.Annotations, "nginx.org/proxy-hide-headers", ingEx.Ingress, ","); exists { if err != nil { glog.Error(err) } else { ingCfg.ProxyHideHeaders = proxyHideHeaders } } - if proxyPassHeaders, exists, err := GetMapKeyAsStringSlice(ingEx.Ingress.Annotations, "nginx.org/proxy-pass-headers", ingEx.Ingress); exists { + if proxyPassHeaders, exists, err := GetMapKeyAsStringSlice(ingEx.Ingress.Annotations, "nginx.org/proxy-pass-headers", ingEx.Ingress, ","); exists { if err != nil { glog.Error(err) } else { @@ -366,6 +384,7 @@ func createLocation(path string, upstream Upstream, cfg *Config, websocket bool, ProxyBuffers: cfg.ProxyBuffers, ProxyBufferSize: cfg.ProxyBufferSize, ProxyMaxTempFileSize: cfg.ProxyMaxTempFileSize, + LocationSnippets: cfg.LocationSnippets, } return loc @@ -433,6 +452,7 @@ func (cnf *Configurator) UpdateConfig(config *Config) { cnf.config = config mainCfg := &NginxMainConfig{ + HTTPSnippets: config.MainHTTPSnippets, ServerNamesHashBucketSize: config.MainServerNamesHashBucketSize, ServerNamesHashMaxSize: config.MainServerNamesHashMaxSize, LogFormat: config.MainLogFormat, diff --git a/nginx-controller/nginx/convert.go b/nginx-controller/nginx/convert.go index adbede444e..20bb49884f 100644 --- a/nginx-controller/nginx/convert.go +++ b/nginx-controller/nginx/convert.go @@ -40,10 +40,10 @@ func GetMapKeyAsInt(m map[string]string, key string, context apiObject) (int64, return 0, false, nil } -// GetMapKeyAsStringSlice tries to find and parse a key in the map as string slice splitting it on ',' -func GetMapKeyAsStringSlice(m map[string]string, key string, context apiObject) ([]string, bool, error) { +// GetMapKeyAsStringSlice tries to find and parse a key in the map as string slice splitting it on delimiter +func GetMapKeyAsStringSlice(m map[string]string, key string, context apiObject, delimiter string) ([]string, bool, error) { if str, exists := m[key]; exists { - slice := strings.Split(str, ",") + slice := strings.Split(str, delimiter) return slice, exists, nil } return nil, false, nil diff --git a/nginx-controller/nginx/convert_test.go b/nginx-controller/nginx/convert_test.go index 01719efb20..58bb09393c 100644 --- a/nginx-controller/nginx/convert_test.go +++ b/nginx-controller/nginx/convert_test.go @@ -164,7 +164,7 @@ func TestGetMapKeyAsStringSlice(t *testing.T) { "key": "1.String,2.String,3.String", } - slice, exists, err := GetMapKeyAsStringSlice(configMap.Data, "key", &configMap) + slice, exists, err := GetMapKeyAsStringSlice(configMap.Data, "key", &configMap, ",") if err != nil { t.Errorf("Unexpected error: %v", err) } @@ -176,13 +176,36 @@ func TestGetMapKeyAsStringSlice(t *testing.T) { if !reflect.DeepEqual(expected, slice) { t.Errorf("Unexpected return value:\nGot: %#v\nExpected: %#v", slice, expected) } + +} + +func TestGetMapKeyAsStringSliceMultilineSnippets(t *testing.T) { + configMap := configMap + configMap.Data = map[string]string{ + "server-snippets": ` + if ($new_uri) { + rewrite ^ $new_uri permanent; + }`, + } + slice, exists, err := GetMapKeyAsStringSlice(configMap.Data, "server-snippets", &configMap, "\n") + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !exists { + t.Errorf("The key 'server-snippets' must exist in the configMap") + } + expected := []string{"", "\t\t\tif ($new_uri) {", "\t\t\t\trewrite ^ $new_uri permanent;", "\t\t\t}"} + t.Log(expected) + if !reflect.DeepEqual(expected, slice) { + t.Errorf("Unexpected return value:\nGot: %#v\nExpected: %#v", slice, expected) + } } func TestGetMapKeyAsStringSliceNotFound(t *testing.T) { configMap := configMap configMap.Data = map[string]string{} - _, exists, _ := GetMapKeyAsStringSlice(configMap.Data, "key", &configMap) + _, exists, _ := GetMapKeyAsStringSlice(configMap.Data, "key", &configMap, ",") if exists { t.Errorf("The key 'key' must not exist in the configMap") } diff --git a/nginx-controller/nginx/ingress.tmpl b/nginx-controller/nginx/ingress.tmpl index 8e5846d707..84de46208c 100644 --- a/nginx-controller/nginx/ingress.tmpl +++ b/nginx-controller/nginx/ingress.tmpl @@ -40,6 +40,11 @@ server { } {{- end}} + {{- if $server.ServerSnippets}} + {{range $value := $server.ServerSnippets}} + {{$value}}{{end}} + {{- end}} + {{range $location := $server.Locations}} location {{$location.Path}} { proxy_http_version 1.1; @@ -47,6 +52,12 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; {{end}} + + {{- if $location.LocationSnippets}} + {{range $value := $location.LocationSnippets}} + {{$value}}{{end}} + {{- end}} + proxy_connect_timeout {{$location.ProxyConnectTimeout}}; proxy_read_timeout {{$location.ProxyReadTimeout}}; client_max_body_size {{$location.ClientMaxBodySize}}; diff --git a/nginx-controller/nginx/nginx.conf.tmpl b/nginx-controller/nginx/nginx.conf.tmpl index 3a81665597..67a9f86be0 100644 --- a/nginx-controller/nginx/nginx.conf.tmpl +++ b/nginx-controller/nginx/nginx.conf.tmpl @@ -15,6 +15,11 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; + {{- if .HTTPSnippets}} + {{range $value := .HTTPSnippets}} + {{$value}}{{end}} + {{- end}} + {{if .LogFormat -}} log_format main '{{.LogFormat}}'; {{- else -}} diff --git a/nginx-controller/nginx/nginx.go b/nginx-controller/nginx/nginx.go index aa7a2cd61b..57f5e28d74 100644 --- a/nginx-controller/nginx/nginx.go +++ b/nginx-controller/nginx/nginx.go @@ -40,6 +40,7 @@ type UpstreamServer struct { // Server describes an NGINX server type Server struct { + ServerSnippets []string Name string ServerTokens bool Locations []Location @@ -63,6 +64,7 @@ type Server struct { // Location describes an NGINX location type Location struct { + LocationSnippets []string Path string Upstream Upstream ProxyConnectTimeout string @@ -83,6 +85,7 @@ type NginxMainConfig struct { ServerNamesHashMaxSize string LogFormat string HealthStatus bool + HTTPSnippets []string // http://nginx.org/en/docs/http/ngx_http_ssl_module.html SSLProtocols string SSLPreferServerCiphers bool