Add option to force enabling snippet directives (#7665)

Signed-off-by: Ricardo Pchevuzinske Katz <ricardo.katz@gmail.com>
This commit is contained in:
Ricardo Katz 2021-09-19 16:40:08 -03:00 committed by GitHub
parent 314cc6c2dc
commit 5e6ab651ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 459 additions and 24 deletions

View file

@ -4,6 +4,7 @@ controller:
tag: 1.0.0-dev tag: 1.0.0-dev
digest: null digest: null
kind: DaemonSet kind: DaemonSet
enableSnippetDirectives: false
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -5,6 +5,7 @@ controller:
digest: null digest: null
config: config:
use-proxy-protocol: "true" use-proxy-protocol: "true"
enableSnippetDirectives: false
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -10,6 +10,7 @@ metadata:
name: {{ include "ingress-nginx.controller.fullname" . }} name: {{ include "ingress-nginx.controller.fullname" . }}
namespace: {{ .Release.Namespace }} namespace: {{ .Release.Namespace }}
data: data:
enable-snippet-directives: "{{ .Values.controller.enableSnippetDirectives }}"
{{- if .Values.controller.addHeaders }} {{- if .Values.controller.addHeaders }}
add-headers: {{ .Release.Namespace }}/{{ include "ingress-nginx.fullname" . }}-custom-add-headers add-headers: {{ .Release.Namespace }}/{{ include "ingress-nginx.fullname" . }}-custom-add-headers
{{- end }} {{- end }}

View file

@ -69,6 +69,12 @@ controller:
# Process IngressClass per name (additionally as per spec.controller) # Process IngressClass per name (additionally as per spec.controller)
ingressClassByName: false ingressClassByName: false
# This configuration defines if Ingress Controller should allow users to set
# their own *-snippet directives/annotations, otherwise this is forbidden / dropped
# when users add those annotations.
# Global snippets in ConfigMap are still respected
enableSnippetDirectives: true
# Required for use with CNI based kubernetes installations (such as ones set up by kubeadm), # Required for use with CNI based kubernetes installations (such as ones set up by kubeadm),
# since CNI and hostport don't mix yet. Can be deprecated once https://github.com/kubernetes/kubernetes/issues/23920 # since CNI and hostport don't mix yet. Can be deprecated once https://github.com/kubernetes/kubernetes/issues/23920
# is merged # is merged

View file

@ -46,6 +46,7 @@ The following table shows a configuration option's name, type, and the default v
|[disable-access-log](#disable-access-log)|bool|false| |[disable-access-log](#disable-access-log)|bool|false|
|[disable-ipv6](#disable-ipv6)|bool|false| |[disable-ipv6](#disable-ipv6)|bool|false|
|[disable-ipv6-dns](#disable-ipv6-dns)|bool|false| |[disable-ipv6-dns](#disable-ipv6-dns)|bool|false|
|[enable-snippet-directives](#enable-snippet-directives)|bool|true|
|[enable-underscores-in-headers](#enable-underscores-in-headers)|bool|false| |[enable-underscores-in-headers](#enable-underscores-in-headers)|bool|false|
|[enable-ocsp](#enable-ocsp)|bool|false| |[enable-ocsp](#enable-ocsp)|bool|false|
|[ignore-invalid-headers](#ignore-invalid-headers)|bool|true| |[ignore-invalid-headers](#ignore-invalid-headers)|bool|true|
@ -316,6 +317,12 @@ Disable listening on IPV6. _**default:**_ `false`; IPv6 listening is enabled
Disable IPV6 for nginx DNS resolver. _**default:**_ `false`; IPv6 resolving enabled. Disable IPV6 for nginx DNS resolver. _**default:**_ `false`; IPv6 resolving enabled.
## enable-snippet-directives
Enables Ingress to parse and add *-snippet annotations/directives created by the user. _**default:**_ `true`;
Obs.: We recommend enabling this option only if you TRUST users with permission to create Ingress objects, as this
may allow a user to add restricted configurations to the final nginx.conf file
## enable-underscores-in-headers ## enable-underscores-in-headers
Enables underscores in header names. _**default:**_ is disabled Enables underscores in header names. _**default:**_ is disabled

View file

@ -93,6 +93,10 @@ const (
type Configuration struct { type Configuration struct {
defaults.Backend `json:",squash"` defaults.Backend `json:",squash"`
// EnableSnippetDirectives enable users to add their own snippets via ingress annotation.
// If disabled, only snippets added via ConfigMap are added to ingress.
EnableSnippetDirectives bool `json:"enable-snippet-directives"`
// Sets the name of the configmap that contains the headers to pass to the client // Sets the name of the configmap that contains the headers to pass to the client
AddHeaders string `json:"add-headers,omitempty"` AddHeaders string `json:"add-headers,omitempty"`
@ -757,6 +761,7 @@ func NewDefault() Configuration {
defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", "", append(defResponseHeaders, ""), "", "", "", []string{}, map[string]string{}} defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", "", append(defResponseHeaders, ""), "", "", "", []string{}, map[string]string{}}
cfg := Configuration{ cfg := Configuration{
EnableSnippetDirectives: true,
AllowBackendServerHeader: false, AllowBackendServerHeader: false,
AccessLogPath: "/var/log/nginx/access.log", AccessLogPath: "/var/log/nginx/access.log",
AccessLogParams: "", AccessLogParams: "",

View file

@ -234,27 +234,28 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
return fmt.Errorf("This deployment is trying to create a catch-all ingress while DisableCatchAll flag is set to true. Remove '.spec.backend' or set DisableCatchAll flag to false.") return fmt.Errorf("This deployment is trying to create a catch-all ingress while DisableCatchAll flag is set to true. Remove '.spec.backend' or set DisableCatchAll flag to false.")
} }
if parser.AnnotationsPrefix != parser.DefaultAnnotationsPrefix { cfg := n.store.GetBackendConfiguration()
for key := range ing.ObjectMeta.GetAnnotations() { cfg.Resolver = n.resolver
for key := range ing.ObjectMeta.GetAnnotations() {
if parser.AnnotationsPrefix != parser.DefaultAnnotationsPrefix {
if strings.HasPrefix(key, fmt.Sprintf("%s/", parser.DefaultAnnotationsPrefix)) { if strings.HasPrefix(key, fmt.Sprintf("%s/", parser.DefaultAnnotationsPrefix)) {
return fmt.Errorf("This deployment has a custom annotation prefix defined. Use '%s' instead of '%s'", parser.AnnotationsPrefix, parser.DefaultAnnotationsPrefix) return fmt.Errorf("This deployment has a custom annotation prefix defined. Use '%s' instead of '%s'", parser.AnnotationsPrefix, parser.DefaultAnnotationsPrefix)
} }
} }
if !cfg.EnableSnippetDirectives && strings.HasSuffix(key, "-snippet") {
return fmt.Errorf("%s annotation cannot be used. Snippet directives are disabled by the Ingress administrator", key)
}
if len(cfg.GlobalRateLimitMemcachedHost) == 0 && strings.HasPrefix(key, fmt.Sprintf("%s/%s", parser.AnnotationsPrefix, "global-rate-limit")) {
return fmt.Errorf("'global-rate-limit*' annotations require 'global-rate-limit-memcached-host' settings configured in the global configmap")
}
} }
k8s.SetDefaultNGINXPathType(ing) k8s.SetDefaultNGINXPathType(ing)
cfg := n.store.GetBackendConfiguration()
cfg.Resolver = n.resolver
if len(cfg.GlobalRateLimitMemcachedHost) == 0 {
for key := range ing.ObjectMeta.GetAnnotations() {
if strings.HasPrefix(key, fmt.Sprintf("%s/%s", parser.AnnotationsPrefix, "global-rate-limit")) {
return fmt.Errorf("'global-rate-limit*' annotations require 'global-rate-limit-memcached-host' settings configured in the global configmap")
}
}
}
allIngresses := n.store.ListIngresses() allIngresses := n.store.ListIngresses()
filter := func(toCheck *ingress.Ingress) bool { filter := func(toCheck *ingress.Ingress) bool {
@ -511,6 +512,30 @@ func (n *NGINXController) getConfiguration(ingresses []*ingress.Ingress) (sets.S
} }
} }
func dropSnippetDirectives(anns *annotations.Ingress, ingKey string) {
if anns != nil {
if anns.ConfigurationSnippet != "" {
klog.V(3).Infof("Ingress %q tried to use configuration-snippet and the annotation is disabled by the admin. Removing the annotation", ingKey)
anns.ConfigurationSnippet = ""
}
if anns.ServerSnippet != "" {
klog.V(3).Infof("Ingress %q tried to use server-snippet and the annotation is disabled by the admin. Removing the annotation", ingKey)
anns.ServerSnippet = ""
}
if anns.ModSecurity.Snippet != "" {
klog.V(3).Infof("Ingress %q tried to use modsecurity-snippet and the annotation is disabled by the admin. Removing the annotation", ingKey)
anns.ModSecurity.Snippet = ""
}
if anns.ExternalAuth.AuthSnippet != "" {
klog.V(3).Infof("Ingress %q tried to use auth-snippet and the annotation is disabled by the admin. Removing the annotation", ingKey)
anns.ExternalAuth.AuthSnippet = ""
}
}
}
// getBackendServers returns a list of Upstream and Server to be used by the // getBackendServers returns a list of Upstream and Server to be used by the
// backend. An upstream can be used in multiple servers if the namespace, // backend. An upstream can be used in multiple servers if the namespace,
// service name and port are the same. // service name and port are the same.
@ -525,6 +550,10 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
ingKey := k8s.MetaNamespaceKey(ing) ingKey := k8s.MetaNamespaceKey(ing)
anns := ing.ParsedAnnotations anns := ing.ParsedAnnotations
if !n.store.GetBackendConfiguration().EnableSnippetDirectives {
dropSnippetDirectives(anns, ingKey)
}
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
host := rule.Host host := rule.Host
if host == "" { if host == "" {
@ -801,6 +830,10 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
ingKey := k8s.MetaNamespaceKey(ing) ingKey := k8s.MetaNamespaceKey(ing)
anns := ing.ParsedAnnotations anns := ing.ParsedAnnotations
if !n.store.GetBackendConfiguration().EnableSnippetDirectives {
dropSnippetDirectives(anns, ingKey)
}
var defBackend string var defBackend string
if ing.Spec.DefaultBackend != nil && ing.Spec.DefaultBackend.Service != nil { if ing.Spec.DefaultBackend != nil && ing.Spec.DefaultBackend.Service != nil {
defBackend = upstreamName(ing.Namespace, ing.Spec.DefaultBackend.Service) defBackend = upstreamName(ing.Namespace, ing.Spec.DefaultBackend.Service)
@ -1091,6 +1124,10 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
ingKey := k8s.MetaNamespaceKey(ing) ingKey := k8s.MetaNamespaceKey(ing)
anns := ing.ParsedAnnotations anns := ing.ParsedAnnotations
if !n.store.GetBackendConfiguration().EnableSnippetDirectives {
dropSnippetDirectives(anns, ingKey)
}
// default upstream name // default upstream name
un := du.Name un := du.Name
@ -1167,6 +1204,10 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
ingKey := k8s.MetaNamespaceKey(ing) ingKey := k8s.MetaNamespaceKey(ing)
anns := ing.ParsedAnnotations anns := ing.ParsedAnnotations
if !n.store.GetBackendConfiguration().EnableSnippetDirectives {
dropSnippetDirectives(anns, ingKey)
}
if anns.Canary.Enabled { if anns.Canary.Enabled {
klog.V(2).Infof("Ingress %v is marked as Canary, ignoring", ingKey) klog.V(2).Infof("Ingress %v is marked as Canary, ignoring", ingKey)
continue continue

View file

@ -42,6 +42,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations" "k8s.io/ingress-nginx/internal/ingress/annotations"
"k8s.io/ingress-nginx/internal/ingress/annotations/canary" "k8s.io/ingress-nginx/internal/ingress/annotations/canary"
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl" "k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
"k8s.io/ingress-nginx/internal/ingress/annotations/sessionaffinity" "k8s.io/ingress-nginx/internal/ingress/annotations/sessionaffinity"
@ -57,11 +58,12 @@ import (
) )
type fakeIngressStore struct { type fakeIngressStore struct {
ingresses []*ingress.Ingress ingresses []*ingress.Ingress
configuration ngx_config.Configuration
} }
func (fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration { func (fis fakeIngressStore) GetBackendConfiguration() ngx_config.Configuration {
return ngx_config.Configuration{} return fis.configuration
} }
func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) { func (fakeIngressStore) GetConfigMap(key string) (*corev1.ConfigMap, error) {
@ -235,6 +237,9 @@ func TestCheckIngress(t *testing.T) {
}) })
t.Run("When the default annotation prefix is used despite an override", func(t *testing.T) { t.Run("When the default annotation prefix is used despite an override", func(t *testing.T) {
defer func() {
parser.AnnotationsPrefix = "nginx.ingress.kubernetes.io"
}()
parser.AnnotationsPrefix = "ingress.kubernetes.io" parser.AnnotationsPrefix = "ingress.kubernetes.io"
ing.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/backend-protocol"] = "GRPC" ing.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/backend-protocol"] = "GRPC"
nginx.command = testNginxTestCommand{ nginx.command = testNginxTestCommand{
@ -246,6 +251,23 @@ func TestCheckIngress(t *testing.T) {
} }
}) })
t.Run("When snippets are disabled and user tries to use snippet annotation", func(t *testing.T) {
nginx.store = fakeIngressStore{
ingresses: []*ingress.Ingress{},
configuration: ngx_config.Configuration{
EnableSnippetDirectives: false,
},
}
nginx.command = testNginxTestCommand{
t: t,
err: nil,
}
ing.ObjectMeta.Annotations["nginx.ingress.kubernetes.io/server-snippet"] = "bla"
if err := nginx.CheckIngress(ing); err == nil {
t.Errorf("with a snippet annotation, ingresses using the default should be rejected")
}
})
t.Run("When a new catch-all ingress is being created despite catch-alls being disabled ", func(t *testing.T) { t.Run("When a new catch-all ingress is being created despite catch-alls being disabled ", func(t *testing.T) {
backendBefore := ing.Spec.DefaultBackend backendBefore := ing.Spec.DefaultBackend
disableCatchAllBefore := nginx.cfg.DisableCatchAll disableCatchAllBefore := nginx.cfg.DisableCatchAll
@ -275,6 +297,9 @@ func TestCheckIngress(t *testing.T) {
}) })
t.Run("When the ingress is in a different namespace than the watched one", func(t *testing.T) { t.Run("When the ingress is in a different namespace than the watched one", func(t *testing.T) {
defer func() {
nginx.cfg.Namespace = "test-namespace"
}()
nginx.command = testNginxTestCommand{ nginx.command = testNginxTestCommand{
t: t, t: t,
err: fmt.Errorf("test error"), err: fmt.Errorf("test error"),
@ -2211,6 +2236,84 @@ func TestGetBackendServers(t *testing.T) {
} }
}, },
}, },
{
Ingresses: []*ingress.Ingress{
{
Ingress: networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "not-allowed-snippet",
Namespace: "default",
Annotations: map[string]string{
"nginx.ingress.kubernetes.io/server-snippet": "bla",
"nginx.ingress.kubernetes.io/configuration-snippet": "blo",
"nginx.ingress.kubernetes.io/whitelist-source-range": "10.0.0.0/24",
},
},
Spec: networking.IngressSpec{
Rules: []networking.IngressRule{
{
Host: "example.com",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/path1",
PathType: &pathTypePrefix,
Backend: networking.IngressBackend{
Service: &networking.IngressServiceBackend{
Name: "path1-svc",
Port: networking.ServiceBackendPort{
Number: 80,
},
},
},
},
},
},
},
},
},
},
},
ParsedAnnotations: &annotations.Ingress{
Whitelist: ipwhitelist.SourceRange{CIDR: []string{"10.0.0.0/24"}},
ServerSnippet: "bla",
ConfigurationSnippet: "blo",
},
},
},
Validate: func(ingresses []*ingress.Ingress, upstreams []*ingress.Backend, servers []*ingress.Server) {
if len(servers) != 2 {
t.Errorf("servers count should be 2, got %d", len(servers))
return
}
s := servers[1]
if s.ServerSnippet != "" {
t.Errorf("server snippet should be empty, got '%s'", s.ServerSnippet)
}
if s.Locations[0].ConfigurationSnippet != "" {
t.Errorf("config snippet should be empty, got '%s'", s.Locations[0].ConfigurationSnippet)
}
if len(s.Locations[0].Whitelist.CIDR) != 1 || s.Locations[0].Whitelist.CIDR[0] != "10.0.0.0/24" {
t.Errorf("allow list was incorrectly dropped, len should be 1 and contain 10.0.0.0/24")
}
},
SetConfigMap: func(ns string) *v1.ConfigMap {
return &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "config",
SelfLink: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/config", ns),
},
Data: map[string]string{
"enable-snippet-directives": "false",
},
}
},
},
} }
for _, testCase := range testCases { for _, testCase := range testCases {

View file

@ -282,7 +282,7 @@ var _ = framework.DescribeAnnotation("modsecurity owasp", func() {
f.WaitForNginxServer(host, f.WaitForNginxServer(host,
func(server string) bool { func(server string) bool {
return true return strings.Contains(server, "SecRequestBodyAccess On")
}) })
f.HTTPTestClient(). f.HTTPTestClient().
@ -292,4 +292,44 @@ var _ = framework.DescribeAnnotation("modsecurity owasp", func() {
Expect(). Expect().
Status(http.StatusForbidden) Status(http.StatusForbidden)
}) })
ginkgo.It("should enable modsecurity through the config map but ignore snippet as disabled by admin", func() {
host := "modsecurity.foo.com"
nameSpace := f.Namespace
snippet := `SecRequestBodyAccess On
SecAuditEngine RelevantOnly
SecAuditLogParts ABIJDEFHZ
SecAuditLog /dev/stdout
SecAuditLogType Serial
SecRule REQUEST_HEADERS:User-Agent \"block-ua\" \"log,deny,id:107,status:403,msg:\'UA blocked\'\"`
annotations := map[string]string{
"nginx.ingress.kubernetes.io/modsecurity-snippet": snippet,
}
ing := framework.NewSingleIngress(host, "/", host, nameSpace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing)
expectedComment := "SecRuleEngine On"
f.SetNginxConfigMapData(map[string]string{
"enable-modsecurity": "true",
"enable-owasp-modsecurity-crs": "true",
"enable-snippet-directives": "false",
"modsecurity-snippet": expectedComment,
})
f.WaitForNginxServer(host,
func(server string) bool {
return !strings.Contains(server, "block-ua")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
WithHeader("User-Agent", "block-ua").
Expect().
Status(http.StatusOK)
})
}) })

View file

@ -17,6 +17,7 @@ limitations under the License.
package annotations package annotations
import ( import (
"net/http"
"strings" "strings"
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
@ -35,8 +36,8 @@ var _ = framework.DescribeAnnotation("server-snippet", func() {
host := "serversnippet.foo.com" host := "serversnippet.foo.com"
annotations := map[string]string{ annotations := map[string]string{
"nginx.ingress.kubernetes.io/server-snippet": ` "nginx.ingress.kubernetes.io/server-snippet": `
more_set_headers "Content-Length: $content_length"; more_set_headers "Foo: Bar";
more_set_headers "Content-Type: $content_type";`, more_set_headers "Xpto: Lalala";`,
} }
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
@ -44,8 +45,50 @@ var _ = framework.DescribeAnnotation("server-snippet", func() {
f.WaitForNginxServer(host, f.WaitForNginxServer(host,
func(server string) bool { func(server string) bool {
return strings.Contains(server, `more_set_headers "Content-Length: $content_length`) && return strings.Contains(server, `more_set_headers "Foo: Bar`) &&
strings.Contains(server, `more_set_headers "Content-Type: $content_type";`) strings.Contains(server, `more_set_headers "Xpto: Lalala";`)
}) })
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).Headers().
ValueEqual("Foo", []string{"Bar"}).
ValueEqual("Xpto", []string{"Lalala"})
})
ginkgo.It(`drops server snippet if disabled by the administrator`, func() {
host := "noserversnippet.foo.com"
annotations := map[string]string{
"nginx.ingress.kubernetes.io/server-snippet": `
more_set_headers "Foo: Bar";
more_set_headers "Xpto: Lalala";`,
}
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
f.UpdateNginxConfigMapData("enable-snippet-directives", "false")
defer func() {
// Return to the original value
f.UpdateNginxConfigMapData("enable-snippet-directives", "true")
}()
// Sleep a while just to guarantee that the configmap is applied
framework.Sleep()
f.EnsureIngress(ing)
f.WaitForNginxServer(host,
func(server string) bool {
return !strings.Contains(server, `more_set_headers "Foo: Bar`) &&
!strings.Contains(server, `more_set_headers "Xpto: Lalala";`)
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).Headers().
NotContainsKey("Foo").
NotContainsKey("Xpto")
}) })
}) })

View file

@ -17,6 +17,7 @@ limitations under the License.
package annotations package annotations
import ( import (
"net/http"
"strings" "strings"
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
@ -31,11 +32,11 @@ var _ = framework.DescribeAnnotation("configuration-snippet", func() {
f.NewEchoDeployment() f.NewEchoDeployment()
}) })
ginkgo.It(`set snippet "more_set_headers "Request-Id: $req_id";" in all locations"`, func() { ginkgo.It(`set snippet "more_set_headers "Foo1: Bar1";" in all locations"`, func() {
host := "configurationsnippet.foo.com" host := "configurationsnippet.foo.com"
annotations := map[string]string{ annotations := map[string]string{
"nginx.ingress.kubernetes.io/configuration-snippet": ` "nginx.ingress.kubernetes.io/configuration-snippet": `
more_set_headers "Request-Id: $req_id";`, more_set_headers "Foo1: Bar1";`,
} }
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
@ -43,7 +44,44 @@ var _ = framework.DescribeAnnotation("configuration-snippet", func() {
f.WaitForNginxServer(host, f.WaitForNginxServer(host,
func(server string) bool { func(server string) bool {
return strings.Contains(server, `more_set_headers "Request-Id: $req_id";`) return strings.Contains(server, `more_set_headers "Foo1: Bar1";`)
}) })
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).Headers().
ValueEqual("Foo1", []string{"Bar1"})
})
ginkgo.It(`drops snippet "more_set_headers "Foo1: Bar1";" in all locations if disabled by admin"`, func() {
host := "noconfigurationsnippet.foo.com"
annotations := map[string]string{
"nginx.ingress.kubernetes.io/configuration-snippet": `
more_set_headers "Foo1: Bar1";`,
}
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
f.UpdateNginxConfigMapData("enable-snippet-directives", "false")
defer func() {
// Return to the original value
f.UpdateNginxConfigMapData("enable-snippet-directives", "true")
}()
// Sleep a while just to guarantee that the configmap is applied
framework.Sleep()
f.EnsureIngress(ing)
f.WaitForNginxServer(host,
func(server string) bool {
return !strings.Contains(server, `more_set_headers "Foo1: Bar1";`)
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).Headers().
NotContainsKey("Foo1")
}) })
}) })

View file

@ -0,0 +1,149 @@
/*
Copyright 2021 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 (
"net/http"
"strings"
"github.com/onsi/ginkgo"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.DescribeSetting("configmap server-snippet", func() {
f := framework.NewDefaultFramework("cm-server-snippet")
ginkgo.BeforeEach(func() {
f.NewEchoDeployment()
})
ginkgo.It("should add value of server-snippet setting to all ingress config", func() {
host := "serverglobalsnippet1.foo.com"
hostAnnots := "serverannotssnippet1.foo.com"
f.SetNginxConfigMapData(map[string]string{
"server-snippet": `
more_set_headers "Globalfoo: Foooo";`,
})
annotations := map[string]string{
"nginx.ingress.kubernetes.io/server-snippet": `
more_set_headers "Foo: Bar";
more_set_headers "Xpto: Lalala";`,
}
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
ing1 := framework.NewSingleIngress(hostAnnots, "/", hostAnnots, f.Namespace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing1)
// Sleep a while just to guarantee that the configmap is applied
framework.Sleep()
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, `more_set_headers "Globalfoo: Foooo`) &&
!strings.Contains(server, `more_set_headers "Foo: Bar";`) &&
!strings.Contains(server, `more_set_headers "Xpto: Lalala";`)
})
f.WaitForNginxServer(hostAnnots,
func(server string) bool {
return strings.Contains(server, `more_set_headers "Globalfoo: Foooo`) &&
strings.Contains(server, `more_set_headers "Foo: Bar";`) &&
strings.Contains(server, `more_set_headers "Xpto: Lalala";`)
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).Headers().
ValueEqual("Globalfoo", []string{"Foooo"}).
NotContainsKey("Foo").
NotContainsKey("Xpto")
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostAnnots).
Expect().
Status(http.StatusOK).Headers().
ValueEqual("Foo", []string{"Bar"}).
ValueEqual("Xpto", []string{"Lalala"}).
ValueEqual("Globalfoo", []string{"Foooo"})
})
ginkgo.It("should add global server-snippet and drop annotations per admin config", func() {
host := "serverglobalsnippet2.foo.com"
hostAnnots := "serverannotssnippet2.foo.com"
f.SetNginxConfigMapData(map[string]string{
"enable-snippet-directives": "false",
"server-snippet": `
more_set_headers "Globalfoo: Foooo";`,
})
annotations := map[string]string{
"nginx.ingress.kubernetes.io/server-snippet": `
more_set_headers "Foo: Bar";
more_set_headers "Xpto: Lalala";`,
}
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
ing1 := framework.NewSingleIngress(hostAnnots, "/", hostAnnots, f.Namespace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing1)
// Sleep a while just to guarantee that the configmap is applied
framework.Sleep()
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, `more_set_headers "Globalfoo: Foooo`) &&
!strings.Contains(server, `more_set_headers "Foo: Bar";`) &&
!strings.Contains(server, `more_set_headers "Xpto: Lalala";`)
})
f.WaitForNginxServer(hostAnnots,
func(server string) bool {
return strings.Contains(server, `more_set_headers "Globalfoo: Foooo`) &&
!strings.Contains(server, `more_set_headers "Foo: Bar";`) &&
!strings.Contains(server, `more_set_headers "Xpto: Lalala";`)
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK).Headers().
ValueEqual("Globalfoo", []string{"Foooo"}).
NotContainsKey("Foo").
NotContainsKey("Xpto")
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostAnnots).
Expect().
Status(http.StatusOK).Headers().
ValueEqual("Globalfoo", []string{"Foooo"}).
NotContainsKey("Foo").
NotContainsKey("Xpto")
})
})