From 33ca0cbb0ef0af30b8a06aba940298b176df0526 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Fri, 12 Jun 2020 17:29:27 +0100 Subject: [PATCH 1/2] Add vs/vsr snippets support --- cmd/nginx-ingress/main.go | 4 + deployments/common/vs-definition.yaml | 6 ++ deployments/common/vsr-definition.yaml | 2 + deployments/helm-chart/README.md | 1 + .../helm-chart/crds/virtualserver.yaml | 10 +- .../helm-chart/crds/virtualserverroute.yaml | 2 + .../templates/controller-daemonset.yaml | 1 + .../templates/controller-deployment.yaml | 1 + deployments/helm-chart/values.yaml | 3 + .../command-line-arguments.md | 12 ++- ...server-and-virtualserverroute-resources.md | 68 ++++++++++++ .../installation/installation-with-helm.md | 3 + internal/configs/config_params.go | 1 + internal/configs/version2/http.go | 1 + .../version2/nginx-plus.virtualserver.tmpl | 4 + .../configs/version2/nginx.virtualserver.tmpl | 6 +- internal/configs/version2/templates_test.go | 1 + internal/configs/virtualserver.go | 100 ++++++++++++------ internal/configs/virtualserver_test.go | 67 ++++++++++-- pkg/apis/configuration/v1/types.go | 25 +++-- 20 files changed, 260 insertions(+), 58 deletions(-) diff --git a/cmd/nginx-ingress/main.go b/cmd/nginx-ingress/main.go index ab76228686..46d85697cd 100644 --- a/cmd/nginx-ingress/main.go +++ b/cmd/nginx-ingress/main.go @@ -132,6 +132,9 @@ var ( enableCustomResources = flag.Bool("enable-custom-resources", true, "Enable custom resources") + enableSnippets = flag.Bool("enable-snippets", false, + "Enable custom NGINX configuration snippets in VirtualServer and VirtualServerRoute resources.") + globalConfiguration = flag.String("global-configuration", "", `A GlobalConfiguration resource for global configuration of the Ingress Controller. Requires -enable-custom-resources. If the flag is set, but the Ingress controller is not able to fetch the corresponding resource from Kubernetes API, the Ingress Controller @@ -392,6 +395,7 @@ func main() { NginxStatusPort: *nginxStatusPort, StubStatusOverUnixSocketForOSS: *enablePrometheusMetrics, TLSPassthrough: *enableTLSPassthrough, + EnableSnippets: *enableSnippets, SpiffeCerts: *spireAgentAddress != "", } diff --git a/deployments/common/vs-definition.yaml b/deployments/common/vs-definition.yaml index e75f453031..277207ec4f 100644 --- a/deployments/common/vs-definition.yaml +++ b/deployments/common/vs-definition.yaml @@ -59,6 +59,8 @@ spec: properties: host: type: string + http-snippets: + type: string ingressClassName: type: string routes: @@ -186,6 +188,8 @@ spec: type: string type: type: string + location-snippets: + type: string matches: type: array items: @@ -474,6 +478,8 @@ spec: type: string weight: type: integer + server-snippets: + type: string tls: description: TLS defines TLS configuration for a VirtualServer. type: object diff --git a/deployments/common/vsr-definition.yaml b/deployments/common/vsr-definition.yaml index 6132642564..ca3f36debc 100644 --- a/deployments/common/vsr-definition.yaml +++ b/deployments/common/vsr-definition.yaml @@ -184,6 +184,8 @@ spec: type: string type: type: string + location-snippets: + type: string matches: type: array items: diff --git a/deployments/helm-chart/README.md b/deployments/helm-chart/README.md index 906cd9fd63..0749a483d9 100644 --- a/deployments/helm-chart/README.md +++ b/deployments/helm-chart/README.md @@ -210,6 +210,7 @@ Parameter | Description | Default `controller.watchNamespace` | Namespace to watch for Ingress resources. By default the Ingress controller watches all namespaces. | "" `controller.enableCustomResources` | Enable the custom resources. | true `controller.enableTLSPassthrough` | Enable TLS Passthrough on port 443. Requires `controller.enableCustomResources`. | false +`controller.enableSnippets` | Enable custom NGINX configuration snippets in VirtualServer and VirtualServerRoute resources. | false `controller.healthStatus` | Add a location "/nginx-health" to the default server. The location responds with the 200 status code for any request. Useful for external health-checking of the Ingress controller. | false `controller.healthStatusURI` | Sets the URI of health status location in the default server. Requires `contoller.healthStatus`. | "/nginx-health" `controller.nginxStatus.enable` | Enable the NGINX stub_status, or the NGINX Plus API. | true diff --git a/deployments/helm-chart/crds/virtualserver.yaml b/deployments/helm-chart/crds/virtualserver.yaml index 15acf73136..31809e9f1a 100644 --- a/deployments/helm-chart/crds/virtualserver.yaml +++ b/deployments/helm-chart/crds/virtualserver.yaml @@ -61,10 +61,12 @@ spec: description: VirtualServerSpec is the spec of the VirtualServer resource. type: object properties: - ingressClassName: - type: string host: type: string + http-snippets: + type: string + ingressClassName: + type: string routes: type: array items: @@ -190,6 +192,8 @@ spec: type: string type: type: string + location-snippets: + type: string matches: type: array items: @@ -478,6 +482,8 @@ spec: type: string weight: type: integer + server-snippets: + type: string tls: description: TLS defines TLS configuration for a VirtualServer. type: object diff --git a/deployments/helm-chart/crds/virtualserverroute.yaml b/deployments/helm-chart/crds/virtualserverroute.yaml index ddcdcbdc2e..47d237a819 100644 --- a/deployments/helm-chart/crds/virtualserverroute.yaml +++ b/deployments/helm-chart/crds/virtualserverroute.yaml @@ -188,6 +188,8 @@ spec: type: string type: type: string + location-snippets: + type: string matches: type: array items: diff --git a/deployments/helm-chart/templates/controller-daemonset.yaml b/deployments/helm-chart/templates/controller-daemonset.yaml index e710cc9457..bbd11bf7e9 100644 --- a/deployments/helm-chart/templates/controller-daemonset.yaml +++ b/deployments/helm-chart/templates/controller-daemonset.yaml @@ -130,5 +130,6 @@ spec: - -enable-custom-resources={{ .Values.controller.enableCustomResources }} {{- if .Values.controller.enableCustomResources }} - -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} + - -enable-snippets={{ .Values.controller.enableSnippets }} {{- end }} {{- end }} diff --git a/deployments/helm-chart/templates/controller-deployment.yaml b/deployments/helm-chart/templates/controller-deployment.yaml index bbb981e869..753d830564 100644 --- a/deployments/helm-chart/templates/controller-deployment.yaml +++ b/deployments/helm-chart/templates/controller-deployment.yaml @@ -128,5 +128,6 @@ spec: - -enable-custom-resources={{ .Values.controller.enableCustomResources }} {{- if .Values.controller.enableCustomResources }} - -enable-tls-passthrough={{ .Values.controller.enableTLSPassthrough }} + - -enable-snippets={{ .Values.controller.enableSnippets }} {{- end }} {{- end }} diff --git a/deployments/helm-chart/values.yaml b/deployments/helm-chart/values.yaml index ccf432592e..f83b53066d 100644 --- a/deployments/helm-chart/values.yaml +++ b/deployments/helm-chart/values.yaml @@ -124,6 +124,9 @@ controller: ## Enable TLS Passthrough on port 443. Requires controller.enableCustomResources. enableTLSPassthrough: false + ## Enable custom NGINX configuration snippets in VirtualServer and VirtualServerRoute resources. + enableSnippets: false + ## Add a location based on the value of health-status-uri to the default server. The location responds with the 200 status code for any request. ## Useful for external health-checking of the Ingress controller. healthStatus: false diff --git a/docs-web/configuration/global-configuration/command-line-arguments.md b/docs-web/configuration/global-configuration/command-line-arguments.md index 7b9b688c91..0649adfdf2 100644 --- a/docs-web/configuration/global-configuration/command-line-arguments.md +++ b/docs-web/configuration/global-configuration/command-line-arguments.md @@ -6,6 +6,10 @@ The Ingress Controller supports several command-line arguments. Setting the argu Below we describe the available command-line arguments: ```eval_rst +.. option:: -enable-snippets + + Enable custom NGINX configuration snippets in VirtualServer and VirtualServerRoute resources. (default false) + .. option:: -default-server-tls-secret Secret with a TLS certificate and key for TLS termination of the default server. @@ -27,11 +31,11 @@ Below we describe the available command-line arguments: .. option:: -enable-custom-resources - Enables custom resources (default true) + Enables custom resources. (default true) .. option:: -enable-leader-election - Enables Leader election to avoid multiple replicas of the controller reporting the status of Ingress, VirtualServer and VirtualServerRoute resources -- only one replica will report status (default true). + Enables Leader election to avoid multiple replicas of the controller reporting the status of Ingress, VirtualServer and VirtualServerRoute resources -- only one replica will report status. (default true) See :option:`-report-ingress-status` flag. @@ -161,13 +165,13 @@ Below we describe the available command-line arguments: Enables exposing NGINX or NGINX Plus metrics in the Prometheus format. -.. option:: -prometheus-metrics-listen-port +.. option:: -prometheus-metrics-listen-port Sets the port where the Prometheus metrics are exposed. Format: ``[1023 - 65535]`` (default 9113) -.. option:: -spire-agent-address +.. option:: -spire-agent-address Specifies the address of a running Spire agent. **For use with NGINX Service Mesh only**. diff --git a/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md b/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md index 2fdd0c5d4c..9bf15cbf01 100644 --- a/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md +++ b/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md @@ -33,6 +33,7 @@ This document is the reference documentation for the resources. To see additiona - [ErrorPage.Redirect](#errorpage-redirect) - [ErrorPage.Return](#errorpage-return) - [Using VirtualServer and VirtualServerRoute](#using-virtualserver-and-virtualserverroute) + - [Using Snippets](#using-snippets) - [Validation](#validation) - [Structural Validation](#structural-validation) - [Comprehensive Validation](#comprehensive-validation) @@ -98,6 +99,12 @@ spec: - No * - ``ingressClassName`` - Specifies which Ingress controller must handle the VirtualServer resource. + * - ``http-snippets`` + - Sets a custom snippet in the http context. + - ``string`` + - No + * - ``server-snippets`` + - Sets a custom snippet in server context. Overrides the ``server-snippets`` ConfigMap key. - ``string`` - No ``` @@ -200,6 +207,10 @@ The route defines rules for matching client requests to actions like passing a r - The custom responses for error codes. NGINX will use those responses instead of returning the error responses from the upstream servers or the default responses generated by NGINX. A custom response can be a redirect or a canned response. For example, a redirect to another URL if an upstream server responded with a 404 status code. - `[]errorPage <#errorpage>`_ - No + * - ``location-snippets`` + - Sets a custom snippet in the location context. Overrides the ``location-snippets`` ConfigMap key. + - ``string`` + - No ``` \* -- a route must include exactly one of the following: `action`, `splits`, or `route`. @@ -321,6 +332,10 @@ action: - The custom responses for error codes. NGINX will use those responses instead of returning the error responses from the upstream servers or the default responses generated by NGINX. A custom response can be a redirect or a canned response. For example, a redirect to another URL if an upstream server responded with a 404 status code. - `[]errorPage <#errorpage>`_ - No + * - ``location-snippets`` + - Sets a custom snippet in the location context. Overrides the ``location-snippets`` of the VirtualServer (if set) or the ``location-snippets`` ConfigMap key. + - ``string`` + - No ``` \* -- a subroute must include exactly one of the following: `action` or `splits`. @@ -1263,6 +1278,59 @@ In the kubectl get and similar commands, you can also use the short name `vs` in Working with VirtualServerRoute resources is analogous. In the kubectl commands, use `virtualserverroute` or the short name `vsr`. +### Using Snippets + +Snippets allow you to insert raw NGINX config into different contexts of NGINX configuration. In the example below, we use snippets to configure several NGINX features in a VirtualServer: + +```yaml +apiVersion: k8s.nginx.org/v1 +kind: VirtualServer +metadata: + name: cafe + namespace: cafe +spec: + http-snippets: | + limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s; + proxy_cache_path /tmp keys_zone=one:10m; + host: cafe.example.com + tls: + secret: cafe-secret + server-snippets: | + limit_req zone=mylimit burst=20; + upstreams: + - name: tea + service: tea-svc + port: 80 + - name: coffee + service: coffee-svc + port: 80 + routes: + - path: /tea + location-snippets: | + proxy_cache one; + proxy_cache_valid 200 10m; + action: + pass: tea + - path: /coffee + action: + pass: coffee +``` + +Snippets are intended to be used by advanced NGINX users who need more control over the generated NGINX configuration. + +However, because of the disadvantages described below, snippets are disabled by default. To use snippets, set the [`enable-snippets`](/nginx-ingress-controller/configuration/global-configuration/command-line-arguments#cmdoption-enable-snippets) command-line argument. + +Disadvantages of using snippets: +* *Complexity*. To use snippets, you will need to: + * Understand NGINX configuration primitives and implement a correct NGINX configuration. + * Understand how the IC generates NGINX configuration so that a snippet doesn't interfere with the other features in the configuration. +* *Decreased robustness*. An incorrect snippet makes the NGINX config invalid which will lead to a failed reload. This will prevent any new configuration updates, including updates for the other VirtualServer and VirtualServerRoute resources until the snippet is fixed. +* *Security implications*. Snippets give access to NGINX configuration primitives and those primitives are not validated by the Ingress Controller. For example, a snippet can configure NGINX to serve the TLS certificates and keys used for TLS termination for Ingress and VirtualServer resources. + +To help catch errors when using snippets, the Ingress Controller reports config reload errors in the logs as well as in the events and status field of VirtualServer and VirtualServerRoute resources. Additionally, a number of Prometheus metrics show the stats about failed reloads – `controller_nginx_last_reload_status` and `controller_nginx_reload_errors_total`. + +> Note that during a period when the NGINX config includes an invalid snippet, NGINX will continue to operate with the latest valid configuration. + ### Validation Two types of validation are available for VirtualServer and VirtualServerRoute resources: diff --git a/docs-web/installation/installation-with-helm.md b/docs-web/installation/installation-with-helm.md index c11bb7fa7a..89337aaa57 100644 --- a/docs-web/installation/installation-with-helm.md +++ b/docs-web/installation/installation-with-helm.md @@ -273,6 +273,9 @@ The following tables lists the configurable parameters of the NGINX Ingress cont * - ``controller.enableTLSPassthrough`` - Enable TLS Passthrough on port 443. Requires ``controller.enableCustomResources``. - false + * - ``controller.enableSnippets`` + - Enable custom NGINX configuration snippets in VirtualServer and VirtualServerRoute resources. + - false * - ``controller.healthStatus`` - Add a location "/nginx-health" to the default server. The location responds with the 200 status code for any request. Useful for external health-checking of the Ingress controller. - false diff --git a/internal/configs/config_params.go b/internal/configs/config_params.go index 87379ad67d..31f30a8e00 100644 --- a/internal/configs/config_params.go +++ b/internal/configs/config_params.go @@ -99,6 +99,7 @@ type StaticConfigParams struct { NginxStatusPort int StubStatusOverUnixSocketForOSS bool TLSPassthrough bool + EnableSnippets bool SpiffeCerts bool } diff --git a/internal/configs/version2/http.go b/internal/configs/version2/http.go index c2173ebc68..15683583d5 100644 --- a/internal/configs/version2/http.go +++ b/internal/configs/version2/http.go @@ -7,6 +7,7 @@ type VirtualServerConfig struct { SplitClients []SplitClient Maps []Map StatusMatches []StatusMatch + HTTPSnippets []string SpiffeCerts bool } diff --git a/internal/configs/version2/nginx-plus.virtualserver.tmpl b/internal/configs/version2/nginx-plus.virtualserver.tmpl index 355d213214..2511e9084e 100644 --- a/internal/configs/version2/nginx-plus.virtualserver.tmpl +++ b/internal/configs/version2/nginx-plus.virtualserver.tmpl @@ -40,6 +40,10 @@ map {{ $m.Source }} {{ $m.Variable }} { } {{ end }} +{{ range $snippet := .HTTPSnippets }} +{{ $snippet }} +{{ end }} + {{ range $m := .StatusMatches }} match {{ $m.Name }} { status {{ $m.Code }}; diff --git a/internal/configs/version2/nginx.virtualserver.tmpl b/internal/configs/version2/nginx.virtualserver.tmpl index 4a5f1282d2..fcbdd2f63c 100644 --- a/internal/configs/version2/nginx.virtualserver.tmpl +++ b/internal/configs/version2/nginx.virtualserver.tmpl @@ -30,6 +30,10 @@ map {{ $m.Source }} {{ $m.Variable }} { } {{ end }} +{{ range $snippet := .HTTPSnippets }} +{{ $snippet }} +{{ end }} + {{ $s := .Server }} server { listen 80{{ if $s.ProxyProtocol }} proxy_protocol{{ end }}; @@ -137,7 +141,7 @@ server { proxy_buffer_size {{ $l.ProxyBufferSize }}; {{ end }} {{ if $l.ProxyInterceptErrors }} - proxy_intercept_errors on; + proxy_intercept_errors on; {{ end }} proxy_http_version 1.1; diff --git a/internal/configs/version2/templates_test.go b/internal/configs/version2/templates_test.go index 1f7dcd04ab..d2080fc904 100644 --- a/internal/configs/version2/templates_test.go +++ b/internal/configs/version2/templates_test.go @@ -99,6 +99,7 @@ var virtualServerCfg = VirtualServerConfig{ }, }, }, + HTTPSnippets: []string{"# HTTP snippet"}, Server: Server{ ServerName: "example.com", StatusZone: "example.com", diff --git a/internal/configs/virtualserver.go b/internal/configs/virtualserver.go index e7e412698d..f239de3382 100644 --- a/internal/configs/virtualserver.go +++ b/internal/configs/virtualserver.go @@ -143,6 +143,7 @@ type virtualServerConfigurator struct { isPlus bool isResolverConfigured bool isTLSPassthrough bool + enableSnippets bool warnings Warnings spiffeCerts bool } @@ -162,6 +163,7 @@ func newVirtualServerConfigurator(cfgParams *ConfigParams, isPlus bool, isResolv isPlus: isPlus, isResolverConfigured: isResolverConfigured, isTLSPassthrough: staticParams.TLSPassthrough, + enableSnippets: staticParams.EnableSnippets, warnings: make(map[runtime.Object][]string), spiffeCerts: staticParams.SpiffeCerts, } @@ -188,8 +190,6 @@ func (vsc *virtualServerConfigurator) generateEndpointsForUpstream(owner runtime // GenerateVirtualServerConfig generates a full configuration for a VirtualServer func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerEx *VirtualServerEx, tlsPemFileName string) (version2.VirtualServerConfig, Warnings) { vsc.clearWarnings() - ssl := generateSSLConfig(virtualServerEx.VirtualServer.Spec.TLS, tlsPemFileName, vsc.cfgParams) - tlsRedirectConfig := generateTLSRedirectConfig(virtualServerEx.VirtualServer.Spec.TLS) // crUpstreams maps an UpstreamName to its conf_v1.Upstream as they are generated // necessary for generateLocation to know what Upstream each Location references @@ -251,6 +251,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE var maps []version2.Map var errorPageLocations []version2.ErrorPageLocation var vsrErrorPagesFromVs = make(map[string][]conf_v1.ErrorPage) + var vsrLocationSnippetsFromVs = make(map[string]string) var vsrErrorPagesRouteIndex = make(map[string]int) matchesRoutes := 0 @@ -260,8 +261,18 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE for _, r := range virtualServerEx.VirtualServer.Spec.Routes { errorPageIndex := len(errorPageLocations) errorPageLocations = append(errorPageLocations, generateErrorPageLocations(errorPageIndex, r.ErrorPages)...) + // ignore routes that reference VirtualServerRoute if r.Route != "" { + // store route location snippet for the referenced VirtualServerRoute in case they don't define their own + if r.LocationSnippets != "" { + name := r.Route + if !strings.Contains(name, "/") { + name = fmt.Sprintf("%v/%v", virtualServerEx.VirtualServer.Namespace, r.Route) + } + vsrLocationSnippetsFromVs[name] = r.LocationSnippets + } + // store route error pages and route index for the referenced VirtualServerRoute in case they don't define their own if len(r.ErrorPages) > 0 { name := r.Route @@ -274,18 +285,17 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE continue } - if len(r.Matches) > 0 { - cfg := generateMatchesConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients), vsc.cfgParams, r.ErrorPages, errorPageIndex) + vsLocSnippets := r.LocationSnippets + if len(r.Matches) > 0 { + cfg := generateMatchesConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients), vsc.cfgParams, r.ErrorPages, errorPageIndex, vsLocSnippets, vsc.enableSnippets) maps = append(maps, cfg.Maps...) locations = append(locations, cfg.Locations...) internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) splitClients = append(splitClients, cfg.SplitClients...) - matchesRoutes++ } else if len(r.Splits) > 0 { - cfg := generateDefaultSplitsConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams, r.ErrorPages, errorPageIndex, r.Path) - + cfg := generateDefaultSplitsConfig(r, virtualServerUpstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams, r.ErrorPages, errorPageIndex, r.Path, vsLocSnippets, vsc.enableSnippets) splitClients = append(splitClients, cfg.SplitClients...) locations = append(locations, cfg.Locations...) internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) @@ -293,7 +303,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE upstreamName := virtualServerUpstreamNamer.GetNameForUpstreamFromAction(r.Action) upstream := crUpstreams[upstreamName] proxySSLName := generateProxySSLName(upstream.Service, virtualServerEx.VirtualServer.Namespace) - loc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, r.ErrorPages, false, errorPageIndex, proxySSLName, r.Path) + loc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, r.ErrorPages, false, errorPageIndex, proxySSLName, r.Path, vsLocSnippets, vsc.enableSnippets) locations = append(locations, loc) } } @@ -305,27 +315,30 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE errorPageIndex := len(errorPageLocations) errorPageLocations = append(errorPageLocations, generateErrorPageLocations(errorPageIndex, r.ErrorPages)...) errorPages := r.ErrorPages + vsrNamespaceName := fmt.Sprintf("%v/%v", vsr.Namespace, vsr.Name) // use referenced VirtualServer error pages if the route does not define any if r.ErrorPages == nil { - vsrNamespaceName := fmt.Sprintf("%v/%v", vsr.Namespace, vsr.Name) if vsErrorPages, ok := vsrErrorPagesFromVs[vsrNamespaceName]; ok { errorPages = vsErrorPages errorPageIndex = vsrErrorPagesRouteIndex[vsrNamespaceName] } } - if len(r.Matches) > 0 { - cfg := generateMatchesConfig(r, upstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients), vsc.cfgParams, errorPages, errorPageIndex) + locSnippets := r.LocationSnippets + // use referenced VirtualServer location snippet if the route does not define any + if r.LocationSnippets == "" { + locSnippets = vsrLocationSnippetsFromVs[r.Route] + } + if len(r.Matches) > 0 { + cfg := generateMatchesConfig(r, upstreamNamer, crUpstreams, variableNamer, matchesRoutes, len(splitClients), vsc.cfgParams, errorPages, errorPageIndex, locSnippets, vsc.enableSnippets) maps = append(maps, cfg.Maps...) locations = append(locations, cfg.Locations...) internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) splitClients = append(splitClients, cfg.SplitClients...) - matchesRoutes++ } else if len(r.Splits) > 0 { - cfg := generateDefaultSplitsConfig(r, upstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams, errorPages, errorPageIndex, r.Path) - + cfg := generateDefaultSplitsConfig(r, upstreamNamer, crUpstreams, variableNamer, len(splitClients), vsc.cfgParams, errorPages, errorPageIndex, r.Path, locSnippets, vsc.enableSnippets) splitClients = append(splitClients, cfg.SplitClients...) locations = append(locations, cfg.Locations...) internalRedirectLocations = append(internalRedirectLocations, cfg.InternalRedirectLocation) @@ -333,17 +346,23 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE upstreamName := upstreamNamer.GetNameForUpstreamFromAction(r.Action) upstream := crUpstreams[upstreamName] proxySSLName := generateProxySSLName(upstream.Service, vsr.Namespace) - loc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, errorPages, false, errorPageIndex, proxySSLName, r.Path) + loc := generateLocation(r.Path, upstreamName, upstream, r.Action, vsc.cfgParams, errorPages, false, errorPageIndex, proxySSLName, r.Path, locSnippets, vsc.enableSnippets) locations = append(locations, loc) } } } - vscfg := version2.VirtualServerConfig{ + ssl := generateSSLConfig(virtualServerEx.VirtualServer.Spec.TLS, tlsPemFileName, vsc.cfgParams) + tlsRedirectConfig := generateTLSRedirectConfig(virtualServerEx.VirtualServer.Spec.TLS) + httpSnippets := generateSnippets(vsc.enableSnippets, virtualServerEx.VirtualServer.Spec.HTTPSnippets, []string{""}) + serverSnippets := generateSnippets(vsc.enableSnippets, virtualServerEx.VirtualServer.Spec.ServerSnippets, vsc.cfgParams.ServerSnippets) + + vsCfg := version2.VirtualServerConfig{ Upstreams: upstreams, SplitClients: splitClients, Maps: maps, StatusMatches: statusMatches, + HTTPSnippets: httpSnippets, Server: version2.Server{ ServerName: virtualServerEx.VirtualServer.Spec.Host, StatusZone: virtualServerEx.VirtualServer.Spec.Host, @@ -353,7 +372,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE SetRealIPFrom: vsc.cfgParams.SetRealIPFrom, RealIPHeader: vsc.cfgParams.RealIPHeader, RealIPRecursive: vsc.cfgParams.RealIPRecursive, - Snippets: vsc.cfgParams.ServerSnippets, + Snippets: serverSnippets, InternalRedirectLocations: internalRedirectLocations, Locations: locations, HealthChecks: healthChecks, @@ -364,7 +383,7 @@ func (vsc *virtualServerConfigurator) GenerateVirtualServerConfig(virtualServerE SpiffeCerts: vsc.spiffeCerts, } - return vscfg, vsc.warnings + return vsCfg, vsc.warnings } func (vsc *virtualServerConfigurator) generateUpstream(owner runtime.Object, upstreamName string, upstream conf_v1.Upstream, isExternalNameSvc bool, endpoints []string) version2.Upstream { @@ -598,6 +617,13 @@ func generateString(s string, defaultS string) string { return s } +func generateSnippets(enableSnippets bool, s string, defaultS []string) []string { + if !enableSnippets || s == "" { + return defaultS + } + return strings.Split(s, "\n") +} + func generateBuffers(s *conf_v1.UpstreamBuffers, defaultS string) string { if s == nil { return defaultS @@ -638,10 +664,14 @@ func generateReturnBlock(text string, code int, defaultCode int) *version2.Retur } func generateLocation(path string, upstreamName string, upstream conf_v1.Upstream, action *conf_v1.Action, - cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int, proxySSLName string, originalPath string) version2.Location { + cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int, proxySSLName string, + originalPath string, locSnippets string, enableSnippets bool) version2.Location { + + locationSnippets := generateSnippets(enableSnippets, locSnippets, cfgParams.LocationSnippets) + if action.Redirect != nil { returnBlock := generateReturnBlock(action.Redirect.URL, action.Redirect.Code, 301) - return generateLocationForReturnBlock(path, cfgParams.LocationSnippets, returnBlock, "") + return generateLocationForReturnBlock(path, locationSnippets, returnBlock, "") } if action.Return != nil { @@ -650,10 +680,10 @@ func generateLocation(path string, upstreamName string, upstream conf_v1.Upstrea defaultType = "text/plain" } returnBlock := generateReturnBlock(action.Return.Body, action.Return.Code, 200) - return generateLocationForReturnBlock(path, cfgParams.LocationSnippets, returnBlock, defaultType) + return generateLocationForReturnBlock(path, locationSnippets, returnBlock, defaultType) } - return generateLocationForProxying(path, upstreamName, upstream, cfgParams, errorPages, internal, errPageIndex, proxySSLName, action.Proxy, originalPath) + return generateLocationForProxying(path, upstreamName, upstream, cfgParams, errorPages, internal, errPageIndex, proxySSLName, action.Proxy, originalPath, locationSnippets) } func generateProxySetHeaders(proxy *conf_v1.ActionProxy) []version2.Header { @@ -728,11 +758,12 @@ func generateProxyAddHeaders(proxy *conf_v1.ActionProxy) []version2.AddHeader { } func generateLocationForProxying(path string, upstreamName string, upstream conf_v1.Upstream, - cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int, proxySSLName string, proxy *conf_v1.ActionProxy, originalPath string) version2.Location { + cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, internal bool, errPageIndex int, + proxySSLName string, proxy *conf_v1.ActionProxy, originalPath string, locationSnippets []string) version2.Location { return version2.Location{ Path: generatePath(path), Internal: internal, - Snippets: cfgParams.LocationSnippets, + Snippets: locationSnippets, ProxyConnectTimeout: generateString(upstream.ProxyConnectTimeout, cfgParams.ProxyConnectTimeout), ProxyReadTimeout: generateString(upstream.ProxyReadTimeout, cfgParams.ProxyReadTimeout), ProxySendTimeout: generateString(upstream.ProxySendTimeout, cfgParams.ProxySendTimeout), @@ -781,7 +812,9 @@ type routingCfg struct { } func generateSplits(splits []conf_v1.Split, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1.Upstream, - variableNamer *variableNamer, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, errPageIndex int, originalPath string) (version2.SplitClient, []version2.Location) { + variableNamer *variableNamer, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, errPageIndex int, + originalPath string, locSnippets string, enableSnippets bool) (version2.SplitClient, []version2.Location) { + var distributions []version2.Distribution for i, s := range splits { @@ -805,7 +838,7 @@ func generateSplits(splits []conf_v1.Split, upstreamNamer *upstreamNamer, crUpst upstreamName := upstreamNamer.GetNameForUpstreamFromAction(s.Action) upstream := crUpstreams[upstreamName] proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace) - loc := generateLocation(path, upstreamName, upstream, s.Action, cfgParams, errorPages, true, errPageIndex, proxySSLName, originalPath) + loc := generateLocation(path, upstreamName, upstream, s.Action, cfgParams, errorPages, true, errPageIndex, proxySSLName, originalPath, locSnippets, enableSnippets) locations = append(locations, loc) } @@ -813,8 +846,8 @@ func generateSplits(splits []conf_v1.Split, upstreamNamer *upstreamNamer, crUpst } func generateDefaultSplitsConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1.Upstream, - variableNamer *variableNamer, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, errPageIndex int, originalPath string) routingCfg { - sc, locs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex, cfgParams, errorPages, errPageIndex, originalPath) + variableNamer *variableNamer, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, errPageIndex int, originalPath string, locSnippets string, enableSnippets bool) routingCfg { + sc, locs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex, cfgParams, errorPages, errPageIndex, originalPath, locSnippets, enableSnippets) splitClientVarName := variableNamer.GetNameForSplitClientVariable(scIndex) @@ -831,7 +864,7 @@ func generateDefaultSplitsConfig(route conf_v1.Route, upstreamNamer *upstreamNam } func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, crUpstreams map[string]conf_v1.Upstream, - variableNamer *variableNamer, index int, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, errPageIndex int) routingCfg { + variableNamer *variableNamer, index int, scIndex int, cfgParams *ConfigParams, errorPages []conf_v1.ErrorPage, errPageIndex int, locSnippets string, enableSnippets bool) routingCfg { // Generate maps var maps []version2.Map @@ -904,9 +937,8 @@ func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, cr for i, m := range route.Matches { if len(m.Splits) > 0 { - sc, locs := generateSplits(m.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex+scLocalIndex, cfgParams, errorPages, errPageIndex, route.Path) + sc, locs := generateSplits(m.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex+scLocalIndex, cfgParams, errorPages, errPageIndex, route.Path, locSnippets, enableSnippets) scLocalIndex++ - splitClients = append(splitClients, sc) locations = append(locations, locs...) } else { @@ -914,14 +946,14 @@ func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, cr upstreamName := upstreamNamer.GetNameForUpstreamFromAction(m.Action) upstream := crUpstreams[upstreamName] proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace) - loc := generateLocation(path, upstreamName, upstream, m.Action, cfgParams, errorPages, true, errPageIndex, proxySSLName, route.Path) + loc := generateLocation(path, upstreamName, upstream, m.Action, cfgParams, errorPages, true, errPageIndex, proxySSLName, route.Path, locSnippets, enableSnippets) locations = append(locations, loc) } } // Generate default splits or default action if len(route.Splits) > 0 { - sc, locs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex+scLocalIndex, cfgParams, errorPages, errPageIndex, route.Path) + sc, locs := generateSplits(route.Splits, upstreamNamer, crUpstreams, variableNamer, scIndex+scLocalIndex, cfgParams, errorPages, errPageIndex, route.Path, locSnippets, enableSnippets) splitClients = append(splitClients, sc) locations = append(locations, locs...) } else { @@ -929,7 +961,7 @@ func generateMatchesConfig(route conf_v1.Route, upstreamNamer *upstreamNamer, cr upstreamName := upstreamNamer.GetNameForUpstreamFromAction(route.Action) upstream := crUpstreams[upstreamName] proxySSLName := generateProxySSLName(upstream.Service, upstreamNamer.namespace) - loc := generateLocation(path, upstreamName, upstream, route.Action, cfgParams, errorPages, true, errPageIndex, proxySSLName, route.Path) + loc := generateLocation(path, upstreamName, upstream, route.Action, cfgParams, errorPages, true, errPageIndex, proxySSLName, route.Path, locSnippets, enableSnippets) locations = append(locations, loc) } diff --git a/internal/configs/virtualserver_test.go b/internal/configs/virtualserver_test.go index 1c9fe74c27..a24484eef0 100644 --- a/internal/configs/virtualserver_test.go +++ b/internal/configs/virtualserver_test.go @@ -433,6 +433,7 @@ func TestGenerateVirtualServerConfig(t *testing.T) { Keepalive: 16, }, }, + HTTPSnippets: []string{""}, Server: version2.Server{ ServerName: "cafe.example.com", StatusZone: "cafe.example.com", @@ -622,6 +623,7 @@ func TestGenerateVirtualServerConfigWithSpiffeCerts(t *testing.T) { Keepalive: 16, }, }, + HTTPSnippets: []string{""}, Server: version2.Server{ ServerName: "cafe.example.com", StatusZone: "cafe.example.com", @@ -833,6 +835,7 @@ func TestGenerateVirtualServerConfigForVirtualServerWithSplits(t *testing.T) { }, }, }, + HTTPSnippets: []string{""}, Server: version2.Server{ ServerName: "cafe.example.com", StatusZone: "cafe.example.com", @@ -1108,6 +1111,7 @@ func TestGenerateVirtualServerConfigForVirtualServerWithMatches(t *testing.T) { }, }, }, + HTTPSnippets: []string{""}, Server: version2.Server{ ServerName: "cafe.example.com", StatusZone: "cafe.example.com", @@ -1409,6 +1413,46 @@ func TestGenerateString(t *testing.T) { } } +func TestGenerateSnippets(t *testing.T) { + tests := []struct { + enableSnippets bool + s string + defaultS []string + expected []string + }{ + { + true, + "test", + []string{""}, + []string{"test"}, + }, + { + true, + "", + []string{"default"}, + []string{"default"}, + }, + { + true, + "test\none\ntwo", + []string{""}, + []string{"test", "one", "two"}, + }, + { + false, + "test", + nil, + nil, + }, + } + for _, test := range tests { + result := generateSnippets(test.enableSnippets, test.s, test.defaultS) + if !reflect.DeepEqual(result, test.expected) { + t.Errorf("generateSnippets() return %v, but expected %v", result, test.expected) + } + } +} + func TestGenerateBuffer(t *testing.T) { tests := []struct { inputS *conf_v1.UpstreamBuffers @@ -1446,10 +1490,11 @@ func TestGenerateLocationForProxying(t *testing.T) { } path := "/" upstreamName := "test-upstream" + vsLocSnippets := []string{"# vs location snippet"} expected := version2.Location{ Path: "/", - Snippets: []string{"# location snippet"}, + Snippets: vsLocSnippets, ProxyConnectTimeout: "30s", ProxyReadTimeout: "31s", ProxySendTimeout: "32s", @@ -1465,7 +1510,7 @@ func TestGenerateLocationForProxying(t *testing.T) { ProxyPassRequestHeaders: true, } - result := generateLocationForProxying(path, upstreamName, conf_v1.Upstream{}, &cfgParams, nil, false, 0, "", nil, "") + result := generateLocationForProxying(path, upstreamName, conf_v1.Upstream{}, &cfgParams, nil, false, 0, "", nil, "", vsLocSnippets) if !reflect.DeepEqual(result, expected) { t.Errorf("generateLocationForProxying() returned \n%v but expected \n%v", result, expected) } @@ -1918,6 +1963,8 @@ func TestGenerateSplits(t *testing.T) { Service: "coffee-v2", }, } + locSnippet := "# location snippet" + enableSnippets := true errorPages := []conf_v1.ErrorPage{ { Codes: []int{400, 500}, @@ -1986,6 +2033,7 @@ func TestGenerateSplits(t *testing.T) { }, ProxySSLName: "coffee-v1.default.svc", ProxyPassRequestHeaders: true, + Snippets: []string{locSnippet}, }, { Path: "/internal_location_splits_1_split_1", @@ -2009,10 +2057,11 @@ func TestGenerateSplits(t *testing.T) { }, ProxySSLName: "coffee-v2.default.svc", ProxyPassRequestHeaders: true, + Snippets: []string{locSnippet}, }, } - resultSplitClient, resultLocations := generateSplits(splits, upstreamNamer, crUpstreams, variableNamer, scIndex, &cfgParams, errorPages, 0, originalPath) + resultSplitClient, resultLocations := generateSplits(splits, upstreamNamer, crUpstreams, variableNamer, scIndex, &cfgParams, errorPages, 0, originalPath, locSnippet, enableSnippets) if !reflect.DeepEqual(resultSplitClient, expectedSplitClient) { t.Errorf("generateSplits() returned \n%+v but expected \n%+v", resultSplitClient, expectedSplitClient) } @@ -2096,6 +2145,8 @@ func TestGenerateDefaultSplitsConfig(t *testing.T) { } cfgParams := ConfigParams{} + locSnippet := "" + enableSnippets := false crUpstreams := map[string]conf_v1.Upstream{ "vs_default_cafe_coffee-v1": { Service: "coffee-v1", @@ -2105,7 +2156,7 @@ func TestGenerateDefaultSplitsConfig(t *testing.T) { }, } - result := generateDefaultSplitsConfig(route, upstreamNamer, crUpstreams, variableNamer, index, &cfgParams, route.ErrorPages, 0, "") + result := generateDefaultSplitsConfig(route, upstreamNamer, crUpstreams, variableNamer, index, &cfgParams, route.ErrorPages, 0, "", locSnippet, enableSnippets) if !reflect.DeepEqual(result, expected) { t.Errorf("generateDefaultSplitsConfig() returned \n%+v but expected \n%+v", result, expected) } @@ -2467,13 +2518,15 @@ func TestGenerateMatchesConfig(t *testing.T) { } cfgParams := ConfigParams{} + enableSnippets := false + locSnippets := "" crUpstreams := map[string]conf_v1.Upstream{ "vs_default_cafe_coffee-v1": {Service: "coffee-v1"}, "vs_default_cafe_coffee-v2": {Service: "coffee-v2"}, "vs_default_cafe_tea": {Service: "tea"}, } - result := generateMatchesConfig(route, upstreamNamer, crUpstreams, variableNamer, index, scIndex, &cfgParams, errorPages, 2) + result := generateMatchesConfig(route, upstreamNamer, crUpstreams, variableNamer, index, scIndex, &cfgParams, errorPages, 2, locSnippets, enableSnippets) if !reflect.DeepEqual(result, expected) { t.Errorf("generateMatchesConfig() returned \n%+v but expected \n%+v", result, expected) } @@ -2823,11 +2876,13 @@ func TestGenerateMatchesConfigWithMultipleSplits(t *testing.T) { } cfgParams := ConfigParams{} + enableSnippets := false + locSnippets := "" crUpstreams := map[string]conf_v1.Upstream{ "vs_default_cafe_coffee-v1": {Service: "coffee-v1"}, "vs_default_cafe_coffee-v2": {Service: "coffee-v2"}, } - result := generateMatchesConfig(route, upstreamNamer, crUpstreams, variableNamer, index, scIndex, &cfgParams, errorPages, 0) + result := generateMatchesConfig(route, upstreamNamer, crUpstreams, variableNamer, index, scIndex, &cfgParams, errorPages, 0, locSnippets, enableSnippets) if !reflect.DeepEqual(result, expected) { t.Errorf("generateMatchesConfig() returned \n%+v but expected \n%+v", result, expected) } diff --git a/pkg/apis/configuration/v1/types.go b/pkg/apis/configuration/v1/types.go index 916defc493..84f506c354 100644 --- a/pkg/apis/configuration/v1/types.go +++ b/pkg/apis/configuration/v1/types.go @@ -28,11 +28,13 @@ type VirtualServer struct { // VirtualServerSpec is the spec of the VirtualServer resource. type VirtualServerSpec struct { - IngressClass string `json:"ingressClassName"` - Host string `json:"host"` - TLS *TLS `json:"tls"` - Upstreams []Upstream `json:"upstreams"` - Routes []Route `json:"routes"` + IngressClass string `json:"ingressClassName"` + Host string `json:"host"` + TLS *TLS `json:"tls"` + Upstreams []Upstream `json:"upstreams"` + Routes []Route `json:"routes"` + HTTPSnippets string `json:"http-snippets"` + ServerSnippets string `json:"server-snippets"` } // Upstream defines an upstream. @@ -110,12 +112,13 @@ type SessionCookie struct { // Route defines a route. type Route struct { - Path string `json:"path"` - Route string `json:"route"` - Action *Action `json:"action"` - Splits []Split `json:"splits"` - Matches []Match `json:"matches"` - ErrorPages []ErrorPage `json:"errorPages"` + Path string `json:"path"` + Route string `json:"route"` + Action *Action `json:"action"` + Splits []Split `json:"splits"` + Matches []Match `json:"matches"` + ErrorPages []ErrorPage `json:"errorPages"` + LocationSnippets string `json:"location-snippets"` } // Action defines an action. From 113db8d4838ecd8e64fb3736ffb591eab5450b05 Mon Sep 17 00:00:00 2001 From: Dean Coakley Date: Wed, 1 Jul 2020 18:30:30 +0100 Subject: [PATCH 2/2] Fix rebase error --- .../virtualserver-and-virtualserverroute-resources.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md b/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md index 9bf15cbf01..e1c2fa07c6 100644 --- a/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md +++ b/docs-web/configuration/virtualserver-and-virtualserverroute-resources.md @@ -99,6 +99,8 @@ spec: - No * - ``ingressClassName`` - Specifies which Ingress controller must handle the VirtualServer resource. + - ``string`` + - No * - ``http-snippets`` - Sets a custom snippet in the http context. - ``string``