Merge branch 'master' of https://github.com/kubernetes/ingress-nginx into proxyssl

This commit is contained in:
Gabor Lekeny 2019-08-16 06:21:53 +02:00
commit 65b9e2c574
391 changed files with 23957 additions and 20447 deletions

View file

@ -39,12 +39,14 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/cors"
"k8s.io/ingress-nginx/internal/ingress/annotations/customhttperrors"
"k8s.io/ingress-nginx/internal/ingress/annotations/defaultbackend"
"k8s.io/ingress-nginx/internal/ingress/annotations/fastcgi"
"k8s.io/ingress-nginx/internal/ingress/annotations/http2pushpreload"
"k8s.io/ingress-nginx/internal/ingress/annotations/influxdb"
"k8s.io/ingress-nginx/internal/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/internal/ingress/annotations/loadbalancing"
"k8s.io/ingress-nginx/internal/ingress/annotations/log"
"k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf"
"k8s.io/ingress-nginx/internal/ingress/annotations/mirror"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/annotations/portinredirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
@ -83,6 +85,7 @@ type Ingress struct {
CustomHTTPErrors []int
DefaultBackend *apiv1.Service
//TODO: Change this back into an error when https://github.com/imdario/mergo/issues/100 is resolved
FastCGI fastcgi.Config
Denied *string
ExternalAuth authreq.Config
EnableGlobalAuth bool
@ -109,6 +112,7 @@ type Ingress struct {
LuaRestyWAF luarestywaf.Config
InfluxDB influxdb.Config
ModSecurity modsecurity.Config
Mirror mirror.Config
}
// Extractor defines the annotation parsers to be used in the extraction of annotations
@ -130,6 +134,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"CorsConfig": cors.NewParser(cfg),
"CustomHTTPErrors": customhttperrors.NewParser(cfg),
"DefaultBackend": defaultbackend.NewParser(cfg),
"FastCGI": fastcgi.NewParser(cfg),
"ExternalAuth": authreq.NewParser(cfg),
"EnableGlobalAuth": authreqglobal.NewParser(cfg),
"HTTP2PushPreload": http2pushpreload.NewParser(cfg),
@ -156,6 +161,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"InfluxDB": influxdb.NewParser(cfg),
"BackendProtocol": backendprotocol.NewParser(cfg),
"ModSecurity": modsecurity.NewParser(cfg),
"Mirror": mirror.NewParser(cfg),
},
}
}

View file

@ -68,7 +68,7 @@ func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error)
return &resolver.AuthSSLCert{
Secret: name,
CAFileName: "/opt/ca.pem",
PemSHA: "123",
CASHA: "123",
}, nil
}
return nil, nil

View file

@ -86,12 +86,7 @@ func (e1 *Config) Equal(e2 *Config) bool {
return false
}
match = sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
if !match {
return false
}
return true
return sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
}
var (

View file

@ -168,7 +168,7 @@ func TestHeaderAnnotations(t *testing.T) {
i, err := NewParser(&resolver.Mock{}).Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", err.Error())
t.Error("expected error but retuned nil")
}
continue
}
@ -216,7 +216,7 @@ func TestCacheDurationAnnotations(t *testing.T) {
i, err := NewParser(&resolver.Mock{}).Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", err.Error())
t.Errorf("expected error but retuned nil")
}
continue
}

View file

@ -77,7 +77,7 @@ func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, erro
return &resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
PemSHA: "abc",
CASHA: "abc",
}, nil
}
@ -202,12 +202,12 @@ func TestEquals(t *testing.T) {
sslCert1 := resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
PemSHA: "abc",
CASHA: "abc",
}
sslCert2 := resolver.AuthSSLCert{
Secret: "default/other-demo-secret",
CAFileName: "/ssl/ca.crt",
PemSHA: "abc",
CASHA: "abc",
}
cfg1.AuthSSLCert = sslCert1
cfg2.AuthSSLCert = sslCert2

View file

@ -31,7 +31,7 @@ import (
const HTTP = "HTTP"
var (
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS)$`)
validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
)
type backendProtocol struct {

View file

@ -0,0 +1,107 @@
/*
Copyright 2018 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 fastcgi
import (
"fmt"
"reflect"
"github.com/pkg/errors"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type fastcgi struct {
r resolver.Resolver
}
// Config describes the per location fastcgi config
type Config struct {
Index string `json:"index"`
Params map[string]string `json:"params"`
}
// Equal tests for equality between two Configuration types
func (l1 *Config) Equal(l2 *Config) bool {
if l1 == l2 {
return true
}
if l1 == nil || l2 == nil {
return false
}
if l1.Index != l2.Index {
return false
}
return reflect.DeepEqual(l1.Params, l2.Params)
}
// NewParser creates a new fastcgiConfig protocol annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return fastcgi{r}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate the fastcgiConfig.
func (a fastcgi) Parse(ing *networking.Ingress) (interface{}, error) {
fcgiConfig := Config{}
if ing.GetAnnotations() == nil {
return fcgiConfig, nil
}
index, err := parser.GetStringAnnotation("fastcgi-index", ing)
if err != nil {
index = ""
}
fcgiConfig.Index = index
cm, err := parser.GetStringAnnotation("fastcgi-params-configmap", ing)
if err != nil {
return fcgiConfig, nil
}
cmns, cmn, err := cache.SplitMetaNamespaceKey(cm)
if err != nil {
return fcgiConfig, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "error reading configmap name from annotation"),
}
}
if cmns == "" {
cmns = ing.Namespace
}
cm = fmt.Sprintf("%v/%v", cmns, cmn)
cmap, err := a.r.GetConfigMap(cm)
if err != nil {
return fcgiConfig, ing_errors.LocationDenied{
Reason: errors.Wrapf(err, "unexpected error reading configmap %v", cm),
}
}
fcgiConfig.Params = cmap.Data
return fcgiConfig, nil
}

View file

@ -0,0 +1,263 @@
/*
Copyright 2018 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 fastcgi
import (
"testing"
api "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *networking.Ingress {
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "fastcgi",
ServicePort: intstr.FromInt(80),
},
},
}
}
type mockConfigMap struct {
resolver.Mock
}
func (m mockConfigMap) GetConfigMap(name string) (*api.ConfigMap, error) {
if name != "default/demo-configmap" {
return nil, errors.Errorf("there is no configmap with name %v", name)
}
return &api.ConfigMap{
ObjectMeta: meta_v1.ObjectMeta{
Namespace: api.NamespaceDefault,
Name: "demo-secret",
},
Data: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
}, nil
}
func TestParseEmptyFastCGIAnnotations(t *testing.T) {
ing := buildIngress()
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if config.Index != "" {
t.Errorf("Index should be an empty string")
}
if 0 != len(config.Params) {
t.Errorf("Params should be an empty slice")
}
}
func TestParseFastCGIIndexAnnotation(t *testing.T) {
ing := buildIngress()
const expectedAnnotation = "index.php"
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-index")] = expectedAnnotation
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if config.Index != "index.php" {
t.Errorf("expected %s but %v returned", expectedAnnotation, config.Index)
}
}
func TestParseEmptyFastCGIParamsConfigMapAnnotation(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = ""
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 0 != len(config.Params) {
t.Errorf("Params should be an empty slice")
}
}
func TestParseFastCGIInvalidParamsConfigMapAnnotation(t *testing.T) {
ing := buildIngress()
invalidConfigMapList := []string{"unknown/configMap", "unknown/config/map"}
for _, configmap := range invalidConfigMapList {
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = configmap
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err == nil {
t.Errorf("Reading an unexisting configmap should return an error")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 0 != len(config.Params) {
t.Errorf("Params should be an empty slice")
}
}
}
func TestParseFastCGIParamsConfigMapAnnotationWithoutNS(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "demo-configmap"
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 2 != len(config.Params) {
t.Errorf("Params should have a length of 2")
}
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
t.Errorf("Params value is not the one expected")
}
}
func TestParseFastCGIParamsConfigMapAnnotationWithNS(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("fastcgi-params-configmap")] = "default/demo-configmap"
ing.SetAnnotations(data)
i, err := NewParser(&mockConfigMap{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress without fastcgi")
}
config, ok := i.(Config)
if !ok {
t.Errorf("Parse do not return a Config object")
}
if 2 != len(config.Params) {
t.Errorf("Params should have a length of 2")
}
if "200" != config.Params["REDIRECT_STATUS"] || "$server_name" != config.Params["SERVER_NAME"] {
t.Errorf("Params value is not the one expected")
}
}
func TestConfigEquality(t *testing.T) {
var nilConfig *Config
config := Config{
Index: "index.php",
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
}
configCopy := Config{
Index: "index.php",
Params: map[string]string{"REDIRECT_STATUS": "200", "SERVER_NAME": "$server_name"},
}
config2 := Config{
Index: "index.php",
Params: map[string]string{"REDIRECT_STATUS": "200"},
}
config3 := Config{
Index: "index.py",
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
}
config4 := Config{
Index: "index.php",
Params: map[string]string{"SERVER_NAME": "$server_name", "REDIRECT_STATUS": "200"},
}
if !config.Equal(&config) {
t.Errorf("config should be equal to itself")
}
if nilConfig.Equal(&config) {
t.Errorf("Foo")
}
if !config.Equal(&configCopy) {
t.Errorf("config should be equal to configCopy")
}
if config.Equal(&config2) {
t.Errorf("config2 should not be equal to config")
}
if config.Equal(&config3) {
t.Errorf("config3 should not be equal to config")
}
if !config.Equal(&config4) {
t.Errorf("config4 should be equal to config")
}
}

View file

@ -0,0 +1,58 @@
/*
Copyright 2019 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 mirror
import (
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
// Config returns the mirror to use in a given location
type Config struct {
URI string `json:"uri"`
RequestBody string `json:"requestBody"`
}
type mirror struct {
r resolver.Resolver
}
// NewParser creates a new mirror configuration annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return mirror{r}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure mirror
func (a mirror) Parse(ing *networking.Ingress) (interface{}, error) {
config := &Config{}
var err error
config.URI, err = parser.GetStringAnnotation("mirror-uri", ing)
if err != nil {
config.URI = ""
}
config.RequestBody, err = parser.GetStringAnnotation("mirror-request-body", ing)
if err != nil || config.RequestBody != "off" {
config.RequestBody = "on"
}
return config, nil
}

View file

@ -0,0 +1,86 @@
/*
Copyright 2019 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 mirror
import (
"fmt"
"reflect"
"testing"
api "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func TestParse(t *testing.T) {
uri := parser.GetAnnotationWithPrefix("mirror-uri")
requestBody := parser.GetAnnotationWithPrefix("mirror-request-body")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected *Config
}{
{map[string]string{uri: "/mirror", requestBody: ""}, &Config{
URI: "/mirror",
RequestBody: "on",
}},
{map[string]string{uri: "/mirror", requestBody: "off"}, &Config{
URI: "/mirror",
RequestBody: "off",
}},
{map[string]string{uri: "", requestBody: "ahh"}, &Config{
URI: "",
RequestBody: "on",
}},
{map[string]string{uri: "", requestBody: ""}, &Config{
URI: "",
RequestBody: "on",
}},
{map[string]string{}, &Config{
URI: "",
RequestBody: "on",
}},
{nil, &Config{
URI: "",
RequestBody: "on",
}},
}
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
fmt.Printf("%t", result)
if !reflect.DeepEqual(result, testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -25,22 +25,23 @@ import (
// Config returns the proxy timeout to use in the upstream server/s
type Config struct {
BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"`
ReadTimeout int `json:"readTimeout"`
BuffersNumber int `json:"buffersNumber"`
BufferSize string `json:"bufferSize"`
CookieDomain string `json:"cookieDomain"`
CookiePath string `json:"cookiePath"`
NextUpstream string `json:"nextUpstream"`
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
NextUpstreamTries int `json:"nextUpstreamTries"`
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
ProxyRedirectTo string `json:"proxyRedirectTo"`
RequestBuffering string `json:"requestBuffering"`
ProxyBuffering string `json:"proxyBuffering"`
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"`
ReadTimeout int `json:"readTimeout"`
BuffersNumber int `json:"buffersNumber"`
BufferSize string `json:"bufferSize"`
CookieDomain string `json:"cookieDomain"`
CookiePath string `json:"cookiePath"`
NextUpstream string `json:"nextUpstream"`
NextUpstreamTimeout int `json:"nextUpstreamTimeout"`
NextUpstreamTries int `json:"nextUpstreamTries"`
ProxyRedirectFrom string `json:"proxyRedirectFrom"`
ProxyRedirectTo string `json:"proxyRedirectTo"`
RequestBuffering string `json:"requestBuffering"`
ProxyBuffering string `json:"proxyBuffering"`
ProxyHTTPVersion string `json:"proxyHTTPVersion"`
ProxyMaxTempFileSize string `json:"proxyMaxTempFileSize"`
}
// Equal tests for equality between two Configuration types
@ -100,6 +101,10 @@ func (l1 *Config) Equal(l2 *Config) bool {
return false
}
if l1.ProxyMaxTempFileSize != l2.ProxyMaxTempFileSize {
return false
}
return true
}
@ -200,5 +205,10 @@ func (a proxy) Parse(ing *networking.Ingress) (interface{}, error) {
config.ProxyHTTPVersion = defBackend.ProxyHTTPVersion
}
config.ProxyMaxTempFileSize, err = parser.GetStringAnnotation("proxy-max-temp-file-size", ing)
if err != nil {
config.ProxyMaxTempFileSize = defBackend.ProxyMaxTempFileSize
}
return config, nil
}

View file

@ -82,6 +82,7 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
ProxyRequestBuffering: "on",
ProxyBuffering: "off",
ProxyHTTPVersion: "1.1",
ProxyMaxTempFileSize: "1024m",
}
}
@ -101,6 +102,7 @@ func TestProxy(t *testing.T) {
data[parser.GetAnnotationWithPrefix("proxy-request-buffering")] = "off"
data[parser.GetAnnotationWithPrefix("proxy-buffering")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-http-version")] = "1.0"
data[parser.GetAnnotationWithPrefix("proxy-max-temp-file-size")] = "128k"
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing)
@ -147,6 +149,9 @@ func TestProxy(t *testing.T) {
if p.ProxyHTTPVersion != "1.0" {
t.Errorf("expected 1.0 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
}
if p.ProxyMaxTempFileSize != "128k" {
t.Errorf("expected 128k as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
}
}
func TestProxyWithNoAnnotation(t *testing.T) {
@ -196,4 +201,7 @@ func TestProxyWithNoAnnotation(t *testing.T) {
if p.ProxyHTTPVersion != "1.1" {
t.Errorf("expected 1.1 as proxy-http-version but returned %v", p.ProxyHTTPVersion)
}
if p.ProxyMaxTempFileSize != "1024m" {
t.Errorf("expected 1024m as proxy-max-temp-file-size but returned %v", p.ProxyMaxTempFileSize)
}
}

View file

@ -85,7 +85,7 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
}
if nginxAffinity.Cookie.Name != "INGRESSCOOKIE" {
t.Errorf("expected route as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
t.Errorf("expected INGRESSCOOKIE as session-cookie-name but returned %v", nginxAffinity.Cookie.Name)
}
if nginxAffinity.Cookie.Expires != "4500" {