2016-12-29 20:02:06 +00:00
|
|
|
/*
|
|
|
|
Copyright 2017 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.
|
|
|
|
*/
|
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
package annotations
|
2016-12-29 20:02:06 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
|
2017-09-17 18:42:31 +00:00
|
|
|
apiv1 "k8s.io/api/core/v1"
|
2021-08-21 20:42:00 +00:00
|
|
|
networking "k8s.io/api/networking/v1"
|
2017-09-17 18:42:31 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2016-12-29 20:02:06 +00:00
|
|
|
|
2017-11-23 16:46:23 +00:00
|
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
|
2017-11-07 22:02:12 +00:00
|
|
|
"k8s.io/ingress-nginx/internal/ingress/defaults"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/resolver"
|
2016-12-29 20:02:06 +00:00
|
|
|
)
|
|
|
|
|
2017-11-23 16:46:23 +00:00
|
|
|
var (
|
2021-07-29 21:23:19 +00:00
|
|
|
annotationPassthrough = parser.GetAnnotationWithPrefix("ssl-passthrough")
|
|
|
|
annotationAffinityType = parser.GetAnnotationWithPrefix("affinity")
|
|
|
|
annotationAffinityMode = parser.GetAnnotationWithPrefix("affinity-mode")
|
|
|
|
annotationAffinityCanaryBehavior = parser.GetAnnotationWithPrefix("affinity-canary-behavior")
|
|
|
|
annotationCorsEnabled = parser.GetAnnotationWithPrefix("enable-cors")
|
|
|
|
annotationCorsAllowMethods = parser.GetAnnotationWithPrefix("cors-allow-methods")
|
|
|
|
annotationCorsAllowHeaders = parser.GetAnnotationWithPrefix("cors-allow-headers")
|
|
|
|
annotationCorsExposeHeaders = parser.GetAnnotationWithPrefix("cors-expose-headers")
|
|
|
|
annotationCorsAllowCredentials = parser.GetAnnotationWithPrefix("cors-allow-credentials")
|
|
|
|
defaultCorsMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
|
2022-05-04 12:11:51 +00:00
|
|
|
defaultCorsHeaders = "DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
|
2021-07-29 21:23:19 +00:00
|
|
|
annotationAffinityCookieName = parser.GetAnnotationWithPrefix("session-cookie-name")
|
|
|
|
annotationUpstreamHashBy = parser.GetAnnotationWithPrefix("upstream-hash-by")
|
|
|
|
annotationCustomHTTPErrors = parser.GetAnnotationWithPrefix("custom-http-errors")
|
2024-04-09 10:25:22 +00:00
|
|
|
annotationCustomHeaders = parser.GetAnnotationWithPrefix("custom-headers")
|
2017-01-25 02:17:45 +00:00
|
|
|
)
|
|
|
|
|
2016-12-29 20:02:06 +00:00
|
|
|
type mockCfg struct {
|
2017-11-08 20:58:57 +00:00
|
|
|
resolver.Mock
|
2024-04-09 10:25:22 +00:00
|
|
|
MockSecrets map[string]*apiv1.Secret
|
|
|
|
MockServices map[string]*apiv1.Service
|
|
|
|
MockConfigMaps map[string]*apiv1.ConfigMap
|
2016-12-29 20:02:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m mockCfg) GetDefaultBackend() defaults.Backend {
|
2024-04-09 10:25:22 +00:00
|
|
|
return defaults.Backend{
|
|
|
|
AllowedResponseHeaders: []string{"Content-Type"},
|
|
|
|
}
|
2016-12-29 20:02:06 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 18:42:31 +00:00
|
|
|
func (m mockCfg) GetSecret(name string) (*apiv1.Secret, error) {
|
2017-03-27 02:06:07 +00:00
|
|
|
return m.MockSecrets[name], nil
|
2016-12-29 20:02:06 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 18:42:31 +00:00
|
|
|
func (m mockCfg) GetService(name string) (*apiv1.Service, error) {
|
2017-08-25 15:50:08 +00:00
|
|
|
return m.MockServices[name], nil
|
|
|
|
}
|
|
|
|
|
2024-04-09 10:25:22 +00:00
|
|
|
func (m mockCfg) GetConfigMap(name string) (*apiv1.ConfigMap, error) {
|
|
|
|
return m.MockConfigMaps[name], nil
|
|
|
|
}
|
|
|
|
|
2017-05-14 22:14:27 +00:00
|
|
|
func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
|
2023-08-31 07:36:48 +00:00
|
|
|
secret, err := m.GetSecret(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if secret != nil {
|
2017-05-14 22:14:27 +00:00
|
|
|
return &resolver.AuthSSLCert{
|
|
|
|
Secret: name,
|
|
|
|
CAFileName: "/opt/ca.pem",
|
2019-08-13 21:14:55 +00:00
|
|
|
CASHA: "123",
|
2017-05-14 22:14:27 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2016-12-29 20:02:06 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2019-06-09 22:49:59 +00:00
|
|
|
func buildIngress() *networking.Ingress {
|
|
|
|
defaultBackend := networking.IngressBackend{
|
2021-08-21 20:42:00 +00:00
|
|
|
Service: &networking.IngressServiceBackend{
|
|
|
|
Name: "default-backend",
|
|
|
|
Port: networking.ServiceBackendPort{
|
|
|
|
Number: 80,
|
|
|
|
},
|
|
|
|
},
|
2016-12-29 20:02:06 +00:00
|
|
|
}
|
|
|
|
|
2019-06-09 22:49:59 +00:00
|
|
|
return &networking.Ingress{
|
2017-09-17 18:42:31 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
2016-12-29 20:02:06 +00:00
|
|
|
Name: "foo",
|
2017-09-17 18:42:31 +00:00
|
|
|
Namespace: apiv1.NamespaceDefault,
|
2016-12-29 20:02:06 +00:00
|
|
|
},
|
2019-06-09 22:49:59 +00:00
|
|
|
Spec: networking.IngressSpec{
|
2021-08-21 20:42:00 +00:00
|
|
|
DefaultBackend: &networking.IngressBackend{
|
|
|
|
Service: &networking.IngressServiceBackend{
|
|
|
|
Name: "default-backend",
|
|
|
|
Port: networking.ServiceBackendPort{
|
|
|
|
Number: 80,
|
|
|
|
},
|
|
|
|
},
|
2016-12-29 20:02:06 +00:00
|
|
|
},
|
2019-06-09 22:49:59 +00:00
|
|
|
Rules: []networking.IngressRule{
|
2016-12-29 20:02:06 +00:00
|
|
|
{
|
|
|
|
Host: "foo.bar.com",
|
2019-06-09 22:49:59 +00:00
|
|
|
IngressRuleValue: networking.IngressRuleValue{
|
|
|
|
HTTP: &networking.HTTPIngressRuleValue{
|
|
|
|
Paths: []networking.HTTPIngressPath{
|
2016-12-29 20:02:06 +00:00
|
|
|
{
|
|
|
|
Path: "/foo",
|
|
|
|
Backend: defaultBackend,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2017-01-25 02:17:45 +00:00
|
|
|
|
|
|
|
func TestSSLPassthrough(t *testing.T) {
|
2017-11-07 16:36:51 +00:00
|
|
|
ec := NewAnnotationExtractor(mockCfg{})
|
2017-01-25 02:17:45 +00:00
|
|
|
ing := buildIngress()
|
|
|
|
|
2023-12-08 00:52:14 +00:00
|
|
|
//nolint:goconst //already a constant
|
2017-01-25 02:17:45 +00:00
|
|
|
fooAnns := []struct {
|
|
|
|
annotations map[string]string
|
|
|
|
er bool
|
|
|
|
}{
|
2017-02-02 11:11:19 +00:00
|
|
|
{map[string]string{annotationPassthrough: "true"}, true},
|
|
|
|
{map[string]string{annotationPassthrough: "false"}, false},
|
|
|
|
{map[string]string{annotationPassthrough + "_no": "true"}, false},
|
2017-01-25 02:17:45 +00:00
|
|
|
{map[string]string{}, false},
|
|
|
|
{nil, false},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, foo := range fooAnns {
|
|
|
|
ing.SetAnnotations(foo.annotations)
|
2023-07-22 03:32:07 +00:00
|
|
|
r, err := ec.Extract(ing)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Errors should be null: %v", err)
|
|
|
|
}
|
|
|
|
if r.SSLPassthrough != foo.er {
|
2017-01-25 02:17:45 +00:00
|
|
|
t.Errorf("Returned %v but expected %v", r, foo.er)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 03:00:17 +00:00
|
|
|
|
2017-09-30 21:29:16 +00:00
|
|
|
func TestUpstreamHashBy(t *testing.T) {
|
2017-11-07 16:36:51 +00:00
|
|
|
ec := NewAnnotationExtractor(mockCfg{})
|
2017-09-30 21:29:16 +00:00
|
|
|
ing := buildIngress()
|
|
|
|
|
|
|
|
fooAnns := []struct {
|
|
|
|
annotations map[string]string
|
|
|
|
er string
|
|
|
|
}{
|
|
|
|
{map[string]string{annotationUpstreamHashBy: "$request_uri"}, "$request_uri"},
|
|
|
|
{map[string]string{annotationUpstreamHashBy: "false"}, "false"},
|
|
|
|
{map[string]string{annotationUpstreamHashBy + "_no": "true"}, ""},
|
|
|
|
{map[string]string{}, ""},
|
|
|
|
{nil, ""},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, foo := range fooAnns {
|
|
|
|
ing.SetAnnotations(foo.annotations)
|
2023-07-22 03:32:07 +00:00
|
|
|
r, err := ec.Extract(ing)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error should be null: %v", err)
|
|
|
|
}
|
|
|
|
if r.UpstreamHashBy.UpstreamHashBy != foo.er {
|
2017-09-30 21:29:16 +00:00
|
|
|
t.Errorf("Returned %v but expected %v", r, foo.er)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-12 23:13:39 +00:00
|
|
|
func TestAffinitySession(t *testing.T) {
|
2017-11-07 16:36:51 +00:00
|
|
|
ec := NewAnnotationExtractor(mockCfg{})
|
2017-02-10 03:00:17 +00:00
|
|
|
ing := buildIngress()
|
|
|
|
|
|
|
|
fooAnns := []struct {
|
2021-07-29 21:23:19 +00:00
|
|
|
annotations map[string]string
|
|
|
|
affinitytype string
|
|
|
|
affinitymode string
|
|
|
|
cookiename string
|
|
|
|
canarybehavior string
|
2017-02-10 03:00:17 +00:00
|
|
|
}{
|
2021-07-29 21:23:19 +00:00
|
|
|
{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: "route", annotationAffinityCanaryBehavior: ""}, "cookie", "balanced", "route", ""},
|
|
|
|
{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "persistent", annotationAffinityCookieName: "route1", annotationAffinityCanaryBehavior: "sticky"}, "cookie", "persistent", "route1", "sticky"},
|
|
|
|
{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: "", annotationAffinityCanaryBehavior: "legacy"}, "cookie", "balanced", "INGRESSCOOKIE", "legacy"},
|
|
|
|
{map[string]string{}, "", "", "", ""},
|
|
|
|
{nil, "", "", "", ""},
|
2017-02-10 03:00:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, foo := range fooAnns {
|
|
|
|
ing.SetAnnotations(foo.annotations)
|
2023-07-22 03:32:07 +00:00
|
|
|
rann, err := ec.Extract(ing)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error should be null: %v", err)
|
|
|
|
}
|
|
|
|
r := rann.SessionAffinity
|
2021-07-29 21:23:19 +00:00
|
|
|
t.Logf("Testing pass %v %v", foo.affinitytype, foo.cookiename)
|
|
|
|
|
|
|
|
if r.Type != foo.affinitytype {
|
|
|
|
t.Errorf("Returned %v but expected %v for Type", r.Type, foo.affinitytype)
|
|
|
|
}
|
2017-02-10 03:00:17 +00:00
|
|
|
|
2019-08-30 17:08:03 +00:00
|
|
|
if r.Mode != foo.affinitymode {
|
2021-07-29 21:23:19 +00:00
|
|
|
t.Errorf("Returned %v but expected %v for Mode", r.Mode, foo.affinitymode)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.CanaryBehavior != foo.canarybehavior {
|
|
|
|
t.Errorf("Returned %v but expected %v for CanaryBehavior", r.CanaryBehavior, foo.canarybehavior)
|
2019-08-30 09:40:29 +00:00
|
|
|
}
|
|
|
|
|
2021-07-29 21:23:19 +00:00
|
|
|
if r.Cookie.Name != foo.cookiename {
|
|
|
|
t.Errorf("Returned %v but expected %v for Cookie.Name", r.Cookie.Name, foo.cookiename)
|
2017-02-10 03:00:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-19 20:03:02 +00:00
|
|
|
|
|
|
|
func TestCors(t *testing.T) {
|
2017-11-07 16:36:51 +00:00
|
|
|
ec := NewAnnotationExtractor(mockCfg{})
|
2017-10-19 20:03:02 +00:00
|
|
|
ing := buildIngress()
|
|
|
|
|
|
|
|
fooAnns := []struct {
|
|
|
|
annotations map[string]string
|
|
|
|
corsenabled bool
|
|
|
|
methods string
|
|
|
|
headers string
|
2021-11-02 19:31:42 +00:00
|
|
|
origin []string
|
2017-10-19 20:03:02 +00:00
|
|
|
credentials bool
|
2020-09-23 15:41:52 +00:00
|
|
|
expose string
|
2017-10-19 20:03:02 +00:00
|
|
|
}{
|
2021-11-02 19:31:42 +00:00
|
|
|
{map[string]string{annotationCorsEnabled: "true"}, true, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
|
|
|
|
{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowMethods: "POST, GET, OPTIONS", annotationCorsAllowHeaders: "$nginx_version", annotationCorsAllowCredentials: "false", annotationCorsExposeHeaders: "X-CustomResponseHeader"}, true, "POST, GET, OPTIONS", defaultCorsHeaders, []string{"*"}, false, "X-CustomResponseHeader"},
|
|
|
|
{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowCredentials: "false"}, true, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, false, ""},
|
|
|
|
{map[string]string{}, false, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
|
|
|
|
{nil, false, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
|
2017-10-19 20:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, foo := range fooAnns {
|
|
|
|
ing.SetAnnotations(foo.annotations)
|
2023-07-22 03:32:07 +00:00
|
|
|
rann, err := ec.Extract(ing)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error should be null: %v", err)
|
|
|
|
}
|
|
|
|
r := rann.CorsConfig
|
2017-10-19 20:03:02 +00:00
|
|
|
t.Logf("Testing pass %v %v %v %v %v", foo.corsenabled, foo.methods, foo.headers, foo.origin, foo.credentials)
|
|
|
|
|
|
|
|
if r.CorsEnabled != foo.corsenabled {
|
|
|
|
t.Errorf("Returned %v but expected %v for Cors Enabled", r.CorsEnabled, foo.corsenabled)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.CorsAllowHeaders != foo.headers {
|
|
|
|
t.Errorf("Returned %v but expected %v for Cors Headers", r.CorsAllowHeaders, foo.headers)
|
|
|
|
}
|
|
|
|
|
|
|
|
if r.CorsAllowMethods != foo.methods {
|
|
|
|
t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowMethods, foo.methods)
|
|
|
|
}
|
|
|
|
|
2021-11-02 19:31:42 +00:00
|
|
|
if len(r.CorsAllowOrigin) != len(foo.origin) {
|
|
|
|
t.Errorf("Lengths of Cors Origins are not equal. Expected %v - Actual %v", r.CorsAllowOrigin, foo.origin)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, v := range r.CorsAllowOrigin {
|
|
|
|
if v != foo.origin[i] {
|
|
|
|
t.Errorf("Values of Cors Origins are not equal. Expected %v - Actual %v", r.CorsAllowOrigin, foo.origin)
|
|
|
|
}
|
2017-10-19 20:03:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if r.CorsAllowCredentials != foo.credentials {
|
2021-11-02 19:31:42 +00:00
|
|
|
t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials)
|
2017-10-19 20:03:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-31 07:36:48 +00:00
|
|
|
|
2018-10-25 16:35:48 +00:00
|
|
|
func TestCustomHTTPErrors(t *testing.T) {
|
|
|
|
ec := NewAnnotationExtractor(mockCfg{})
|
|
|
|
ing := buildIngress()
|
|
|
|
|
|
|
|
fooAnns := []struct {
|
|
|
|
annotations map[string]string
|
|
|
|
er []int
|
|
|
|
}{
|
|
|
|
{map[string]string{annotationCustomHTTPErrors: "404,415"}, []int{404, 415}},
|
|
|
|
{map[string]string{annotationCustomHTTPErrors: "404"}, []int{404}},
|
|
|
|
{map[string]string{annotationCustomHTTPErrors: ""}, []int{}},
|
|
|
|
{map[string]string{annotationCustomHTTPErrors + "_no": "404"}, []int{}},
|
|
|
|
{map[string]string{}, []int{}},
|
|
|
|
{nil, []int{}},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, foo := range fooAnns {
|
|
|
|
ing.SetAnnotations(foo.annotations)
|
2023-07-22 03:32:07 +00:00
|
|
|
rann, err := ec.Extract(ing)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error should be null: %v", err)
|
|
|
|
}
|
|
|
|
r := rann.CustomHTTPErrors
|
2018-10-25 16:35:48 +00:00
|
|
|
|
|
|
|
// Check that expected codes were created
|
|
|
|
for i := range foo.er {
|
|
|
|
if r[i] != foo.er[i] {
|
|
|
|
t.Errorf("Returned %v but expected %v", r, foo.er)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that no unexpected codes were also created
|
|
|
|
for i := range r {
|
|
|
|
if r[i] != foo.er[i] {
|
|
|
|
t.Errorf("Returned %v but expected %v", r, foo.er)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-04-09 10:25:22 +00:00
|
|
|
|
|
|
|
func TestCustomResponseHeaders(t *testing.T) {
|
|
|
|
mockObj := mockCfg{}
|
|
|
|
mockObj.MockConfigMaps = map[string]*apiv1.ConfigMap{}
|
|
|
|
mockObj.MockConfigMaps["custom-headers"] = &apiv1.ConfigMap{Data: map[string]string{"Content-Type": "application/json"}}
|
|
|
|
mockObj.MockConfigMaps["empty-custom-headers"] = &apiv1.ConfigMap{Data: map[string]string{}}
|
|
|
|
|
|
|
|
ec := NewAnnotationExtractor(mockObj)
|
|
|
|
ing := buildIngress()
|
|
|
|
fooAnns := []struct {
|
|
|
|
annotations map[string]string
|
|
|
|
headers map[string]string
|
|
|
|
}{
|
|
|
|
{map[string]string{annotationCustomHeaders: "custom-headers"}, map[string]string{"Content-Type": "application/json"}},
|
|
|
|
{map[string]string{annotationCustomHeaders: "empty-custom-headers"}, map[string]string{}},
|
|
|
|
{nil, map[string]string{}},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, foo := range fooAnns {
|
|
|
|
ing.SetAnnotations(foo.annotations)
|
|
|
|
rann, err := ec.Extract(ing)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("error should be null: %v", err)
|
|
|
|
}
|
|
|
|
r := rann.CustomHeaders.Headers
|
|
|
|
|
|
|
|
// Check that expected headers were created
|
|
|
|
for i := range foo.headers {
|
|
|
|
if r[i] != foo.headers[i] {
|
|
|
|
t.Errorf("Returned %v but expected %v", r, foo.headers)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that no unexpected headers were created
|
|
|
|
for i := range r {
|
|
|
|
if r[i] != foo.headers[i] {
|
|
|
|
t.Errorf("Returned %v but expected %v", r, foo.headers)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|