Merge remote-tracking branch 'base/master' into fix/collect-metrics-if-metrics-per-host-false

This commit is contained in:
Christian Hoffmeister 2020-03-13 07:17:49 +01:00
commit 19770f5b41
2674 changed files with 371760 additions and 171868 deletions

View file

@ -17,11 +17,10 @@ limitations under the License.
package controller
import (
"github.com/google/uuid"
"k8s.io/api/admission/v1beta1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
networking "k8s.io/api/networking/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/klog"
)
@ -29,7 +28,7 @@ import (
// Checker must return an error if the ingress provided as argument
// contains invalid instructions
type Checker interface {
CheckIngress(ing *extensions.Ingress) error
CheckIngress(ing *networking.Ingress) error
}
// IngressAdmission implements the AdmissionController interface
@ -45,21 +44,22 @@ func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) error {
if ar.Request == nil {
klog.Infof("rejecting nil request")
ar.Response = &v1beta1.AdmissionResponse{
UID: types.UID(uuid.New().String()),
Allowed: false,
}
return nil
}
klog.V(3).Infof("handling ingress admission webhook request for {%s} %s in namespace %s", ar.Request.Resource.String(), ar.Request.Name, ar.Request.Namespace)
ingressResource := v1.GroupVersionResource{Group: extensions.SchemeGroupVersion.Group, Version: extensions.SchemeGroupVersion.Version, Resource: "ingresses"}
ingressResource := v1.GroupVersionResource{Group: networking.SchemeGroupVersion.Group, Version: networking.SchemeGroupVersion.Version, Resource: "ingresses"}
if ar.Request.Resource == ingressResource {
oldIngressResource := v1.GroupVersionResource{Group: extensions.SchemeGroupVersion.Group, Version: extensions.SchemeGroupVersion.Version, Resource: "ingresses"}
if ar.Request.Resource == ingressResource || ar.Request.Resource == oldIngressResource {
ar.Response = &v1beta1.AdmissionResponse{
UID: types.UID(uuid.New().String()),
UID: ar.Request.UID,
Allowed: false,
}
ingress := extensions.Ingress{}
ingress := networking.Ingress{}
deserializer := codecs.UniversalDeserializer()
if _, _, err := deserializer.Decode(ar.Request.Object.Raw, nil, &ingress); err != nil {
ar.Response.Result = &v1.Status{Message: err.Error()}
@ -86,7 +86,7 @@ func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) error {
klog.Infof("accepting non ingress %s in namespace %s %s", ar.Request.Name, ar.Request.Namespace, ar.Request.Resource.String())
ar.Response = &v1beta1.AdmissionResponse{
UID: types.UID(uuid.New().String()),
UID: ar.Request.UID,
Allowed: true,
}
return nil

View file

@ -21,8 +21,8 @@ import (
"testing"
"k8s.io/api/admission/v1beta1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
networking "k8s.io/api/networking/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/json"
)
@ -32,7 +32,7 @@ type failTestChecker struct {
t *testing.T
}
func (ftc failTestChecker) CheckIngress(ing *extensions.Ingress) error {
func (ftc failTestChecker) CheckIngress(ing *networking.Ingress) error {
ftc.t.Error("checker should not be called")
return nil
}
@ -42,7 +42,7 @@ type testChecker struct {
err error
}
func (tc testChecker) CheckIngress(ing *extensions.Ingress) error {
func (tc testChecker) CheckIngress(ing *networking.Ingress) error {
if ing.ObjectMeta.Name != testIngressName {
tc.t.Errorf("CheckIngress should be called with %v ingress, but got %v", testIngressName, ing.ObjectMeta.Name)
}
@ -66,7 +66,7 @@ func TestHandleAdmission(t *testing.T) {
t.Errorf("with a non ingress resource, no error should be returned")
}
review.Request.Resource = v1.GroupVersionResource{Group: extensions.SchemeGroupVersion.Group, Version: extensions.SchemeGroupVersion.Version, Resource: "ingresses"}
review.Request.Resource = v1.GroupVersionResource{Group: networking.SchemeGroupVersion.Group, Version: networking.SchemeGroupVersion.Version, Resource: "ingresses"}
review.Request.Object.Raw = []byte{0xff}
err = adm.HandleAdmission(review)
@ -77,7 +77,7 @@ func TestHandleAdmission(t *testing.T) {
t.Errorf("when the request object is not decodable, an error should be returned")
}
raw, err := json.Marshal(extensions.Ingress{ObjectMeta: v1.ObjectMeta{Name: testIngressName}})
raw, err := json.Marshal(networking.Ingress{ObjectMeta: v1.ObjectMeta{Name: testIngressName}})
if err != nil {
t.Errorf("failed to prepare test ingress data: %v", err.Error())
}

View file

@ -24,18 +24,16 @@ import (
"strings"
"testing"
"github.com/google/uuid"
"k8s.io/api/admission/v1beta1"
"k8s.io/apimachinery/pkg/types"
)
type testAdmissionHandler struct{}
func (testAdmissionHandler) HandleAdmission(ar *v1beta1.AdmissionReview) error {
ar.Response = &v1beta1.AdmissionResponse{
UID: types.UID(uuid.New().String()),
Allowed: true,
}
return nil
}

View file

@ -20,6 +20,7 @@ import (
"crypto/sha1"
"encoding/hex"
"io/ioutil"
"k8s.io/klog"
)

View file

@ -16,92 +16,5 @@ limitations under the License.
package file
import (
"fmt"
"os"
"path/filepath"
"strings"
"k8s.io/kubernetes/pkg/util/filesystem"
)
// ReadWriteByUser defines linux permission to read and write files for the owner user
const ReadWriteByUser = 0660
// ReadByUserGroup defines linux permission to read files by the user and group owner/s
const ReadByUserGroup = 0640
// Filesystem is an interface that we can use to mock various filesystem operations
type Filesystem interface {
filesystem.Filesystem
}
// NewLocalFS implements Filesystem using same-named functions from "os" and "io/ioutil".
func NewLocalFS() (Filesystem, error) {
fs := filesystem.DefaultFs{}
for _, directory := range directories {
err := fs.MkdirAll(directory, ReadWriteByUser)
if err != nil {
return nil, err
}
}
return fs, nil
}
// NewFakeFS creates an in-memory filesystem with all the required
// paths used by the ingress controller.
// This allows running test without polluting the local machine.
func NewFakeFS() (Filesystem, error) {
osFs := filesystem.DefaultFs{}
fakeFs := filesystem.NewFakeFs()
//TODO: find another way to do this
rootFS := filepath.Clean(fmt.Sprintf("%v/%v", os.Getenv("GOPATH"), "src/k8s.io/ingress-nginx/rootfs"))
var fileList []string
err := filepath.Walk(rootFS, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
file := strings.TrimPrefix(path, rootFS)
if file == "" {
return nil
}
fileList = append(fileList, file)
return nil
})
if err != nil {
return nil, err
}
for _, file := range fileList {
realPath := fmt.Sprintf("%v%v", rootFS, file)
data, err := osFs.ReadFile(realPath)
if err != nil {
return nil, err
}
fakeFile, err := fakeFs.Create(file)
if err != nil {
return nil, err
}
_, err = fakeFile.Write(data)
if err != nil {
return nil, err
}
}
return fakeFs, nil
}
const ReadWriteByUser = 0700

View file

@ -1,37 +0,0 @@
/*
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.
*/
package file
import (
"testing"
)
func TestNewFakeFS(t *testing.T) {
fs, err := NewFakeFS()
if err != nil {
t.Fatalf("unexpected error creating filesystem abstraction: %v", err)
}
if fs == nil {
t.Fatal("expected a filesystem but none returned")
}
_, err = fs.Stat("/etc/nginx/nginx.conf")
if err != nil {
t.Fatalf("unexpected error reading default nginx.conf file: %v", err)
}
}

View file

@ -16,6 +16,12 @@ limitations under the License.
package file
import (
"os"
"github.com/pkg/errors"
)
const (
// AuthDirectory default directory used to store files
// to authenticate request
@ -34,3 +40,25 @@ var (
AuthDirectory,
}
)
// CreateRequiredDirectories verifies if the required directories to
// start the ingress controller exist and creates the missing ones.
func CreateRequiredDirectories() error {
for _, directory := range directories {
_, err := os.Stat(directory)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(directory, ReadWriteByUser)
if err != nil {
return errors.Wrapf(err, "creating directory '%v'", directory)
}
continue
}
return errors.Wrapf(err, "checking directory %v", directory)
}
}
return nil
}

View file

@ -17,7 +17,11 @@ limitations under the License.
package alias
import (
extensions "k8s.io/api/extensions/v1beta1"
"sort"
"strings"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,6 +38,26 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to add an alias to the provided hosts
func (a alias) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("server-alias", ing)
func (a alias) Parse(ing *networking.Ingress) (interface{}, error) {
val, err := parser.GetStringAnnotation("server-alias", ing)
if err != nil {
return []string{}, err
}
aliases := sets.NewString()
for _, alias := range strings.Split(val, ",") {
alias = strings.TrimSpace(alias)
if len(alias) == 0 {
continue
}
if !aliases.Has(alias) {
aliases.Insert(alias)
}
}
l := aliases.List()
sort.Strings(l)
return l, nil
}

View file

@ -17,10 +17,11 @@ limitations under the License.
package alias
import (
"reflect"
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -36,28 +37,29 @@ func TestParse(t *testing.T) {
testCases := []struct {
annotations map[string]string
expected string
expected []string
}{
{map[string]string{annotation: "www.example.com"}, "www.example.com"},
{map[string]string{annotation: "*.example.com www.example.*"}, "*.example.com www.example.*"},
{map[string]string{annotation: `~^www\d+\.example\.com$`}, `~^www\d+\.example\.com$`},
{map[string]string{annotation: ""}, ""},
{map[string]string{}, ""},
{nil, ""},
{map[string]string{annotation: "a.com, b.com, , c.com"}, []string{"a.com", "b.com", "c.com"}},
{map[string]string{annotation: "www.example.com"}, []string{"www.example.com"}},
{map[string]string{annotation: "*.example.com,www.example.*"}, []string{"*.example.com", "www.example.*"}},
{map[string]string{annotation: `~^www\d+\.example\.com$`}, []string{`~^www\d+\.example\.com$`}},
{map[string]string{annotation: ""}, []string{}},
{map[string]string{}, []string{}},
{nil, []string{}},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
if !reflect.DeepEqual(result, testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}

23
internal/ingress/annotations/annotations.go Executable file → Normal file
View file

@ -20,11 +20,12 @@ import (
"github.com/imdario/mergo"
"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
"k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
"k8s.io/ingress-nginx/internal/ingress/annotations/sslcipher"
"k8s.io/klog"
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/alias"
@ -38,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/opentracing"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/annotations/portinredirect"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
@ -71,7 +74,7 @@ const DeniedKeyName = "Denied"
type Ingress struct {
metav1.ObjectMeta
BackendProtocol string
Alias string
Aliases []string
BasicDigestAuth auth.Config
Canary canary.Config
CertificateAuth authtls.Config
@ -82,11 +85,14 @@ 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
HTTP2PushPreload bool
Opentracing opentracing.Config
Proxy proxy.Config
ProxySSL proxyssl.Config
RateLimit ratelimit.Config
Redirect redirect.Config
Rewrite rewrite.Config
@ -104,9 +110,9 @@ type Ingress struct {
XForwardedPrefix string
SSLCiphers string
Logs log.Config
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
@ -118,7 +124,7 @@ type Extractor struct {
func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
return Extractor{
map[string]parser.IngressAnnotation{
"Alias": alias.NewParser(cfg),
"Aliases": alias.NewParser(cfg),
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
"Canary": canary.NewParser(cfg),
"CertificateAuth": authtls.NewParser(cfg),
@ -128,10 +134,13 @@ 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),
"Opentracing": opentracing.NewParser(cfg),
"Proxy": proxy.NewParser(cfg),
"ProxySSL": proxyssl.NewParser(cfg),
"RateLimit": ratelimit.NewParser(cfg),
"Redirect": redirect.NewParser(cfg),
"Rewrite": rewrite.NewParser(cfg),
@ -149,16 +158,16 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor {
"XForwardedPrefix": xforwardedprefix.NewParser(cfg),
"SSLCiphers": sslcipher.NewParser(cfg),
"Logs": log.NewParser(cfg),
"LuaRestyWAF": luarestywaf.NewParser(cfg),
"InfluxDB": influxdb.NewParser(cfg),
"BackendProtocol": backendprotocol.NewParser(cfg),
"ModSecurity": modsecurity.NewParser(cfg),
"Mirror": mirror.NewParser(cfg),
},
}
}
// Extract extracts the annotations from an Ingress
func (e Extractor) Extract(ing *extensions.Ingress) *Ingress {
func (e Extractor) Extract(ing *networking.Ingress) *Ingress {
pia := &Ingress{
ObjectMeta: ing.ObjectMeta,
}

View file

@ -20,7 +20,7 @@ import (
"testing"
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -33,6 +33,7 @@ var (
annotationSecureVerifyCACert = parser.GetAnnotationWithPrefix("secure-verify-ca-secret")
annotationPassthrough = parser.GetAnnotationWithPrefix("ssl-passthrough")
annotationAffinityType = parser.GetAnnotationWithPrefix("affinity")
annotationAffinityMode = parser.GetAnnotationWithPrefix("affinity-mode")
annotationCorsEnabled = parser.GetAnnotationWithPrefix("enable-cors")
annotationCorsAllowMethods = parser.GetAnnotationWithPrefix("cors-allow-methods")
annotationCorsAllowHeaders = parser.GetAnnotationWithPrefix("cors-allow-headers")
@ -68,34 +69,34 @@ 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
}
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: apiv1.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -109,41 +110,6 @@ func buildIngress() *extensions.Ingress {
}
}
func TestSecureVerifyCACert(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{
MockSecrets: map[string]*apiv1.Secret{
"default/secure-verify-ca": {
ObjectMeta: metav1.ObjectMeta{
Name: "secure-verify-ca",
},
},
},
})
anns := []struct {
it int
annotations map[string]string
exists bool
}{
{1, map[string]string{backendProtocol: "HTTPS", annotationSecureVerifyCACert: "not"}, false},
{2, map[string]string{backendProtocol: "HTTP", annotationSecureVerifyCACert: "secure-verify-ca"}, false},
{3, map[string]string{backendProtocol: "HTTPS", annotationSecureVerifyCACert: "secure-verify-ca"}, true},
{4, map[string]string{backendProtocol: "HTTPS", annotationSecureVerifyCACert + "_not": "secure-verify-ca"}, false},
{5, map[string]string{backendProtocol: "HTTPS"}, false},
{6, map[string]string{}, false},
{7, nil, false},
}
for _, ann := range anns {
ing := buildIngress()
ing.SetAnnotations(ann.annotations)
su := ec.Extract(ing).SecureUpstream
if (su.CACert.CAFileName != "") != ann.exists {
t.Errorf("Expected exists was %v on iteration %v", ann.exists, ann.it)
}
}
}
func TestSSLPassthrough(t *testing.T) {
ec := NewAnnotationExtractor(mockCfg{})
ing := buildIngress()
@ -199,13 +165,14 @@ func TestAffinitySession(t *testing.T) {
fooAnns := []struct {
annotations map[string]string
affinitytype string
affinitymode string
name string
}{
{map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieName: "route"}, "cookie", "route"},
{map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieName: "route1"}, "cookie", "route1"},
{map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieName: ""}, "cookie", "INGRESSCOOKIE"},
{map[string]string{}, "", ""},
{nil, "", ""},
{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: "route"}, "cookie", "balanced", "route"},
{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "persistent", annotationAffinityCookieName: "route1"}, "cookie", "persistent", "route1"},
{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: ""}, "cookie", "balanced", "INGRESSCOOKIE"},
{map[string]string{}, "", "", ""},
{nil, "", "", ""},
}
for _, foo := range fooAnns {
@ -213,6 +180,10 @@ func TestAffinitySession(t *testing.T) {
r := ec.Extract(ing).SessionAffinity
t.Logf("Testing pass %v %v", foo.affinitytype, foo.name)
if r.Mode != foo.affinitymode {
t.Errorf("Returned %v but expected %v for Name", r.Mode, foo.affinitymode)
}
if r.Cookie.Name != foo.name {
t.Errorf("Returned %v but expected %v for Name", r.Cookie.Name, foo.name)
}

View file

@ -20,10 +20,11 @@ import (
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/pkg/errors"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/internal/file"
@ -39,14 +40,20 @@ var (
AuthDirectory = "/etc/ingress-controller/auth"
)
const (
fileAuth = "auth-file"
mapAuth = "auth-map"
)
// Config returns authentication configuration for an Ingress rule
type Config struct {
Type string `json:"type"`
Realm string `json:"realm"`
File string `json:"file"`
Secured bool `json:"secured"`
FileSHA string `json:"fileSha"`
Secret string `json:"secret"`
Type string `json:"type"`
Realm string `json:"realm"`
File string `json:"file"`
Secured bool `json:"secured"`
FileSHA string `json:"fileSha"`
Secret string `json:"secret"`
SecretType string `json:"secretType"`
}
// Equal tests for equality between two Config types
@ -92,7 +99,7 @@ func NewParser(authDirectory string, r resolver.Resolver) parser.IngressAnnotati
// rule used to add authentication in the paths defined in the rule
// and generated an htpasswd compatible file to be used as source
// during the authentication process
func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
at, err := parser.GetStringAnnotation("auth-type", ing)
if err != nil {
return nil, err
@ -102,6 +109,12 @@ func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, ing_errors.NewLocationDenied("invalid authentication type")
}
var secretType string
secretType, err = parser.GetStringAnnotation("auth-secret-type", ing)
if err != nil {
secretType = fileAuth
}
s, err := parser.GetStringAnnotation("auth-secret", ing)
if err != nil {
return nil, ing_errors.LocationDenied{
@ -130,25 +143,39 @@ func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
realm, _ := parser.GetStringAnnotation("auth-realm", ing)
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
err = dumpSecret(passFile, secret)
if err != nil {
return nil, err
passFilename := fmt.Sprintf("%v/%v-%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.UID, secret.UID)
switch secretType {
case fileAuth:
err = dumpSecretAuthFile(passFilename, secret)
if err != nil {
return nil, err
}
case mapAuth:
err = dumpSecretAuthMap(passFilename, secret)
if err != nil {
return nil, err
}
default:
return nil, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "invalid auth-secret-type in annotation, must be 'auth-file' or 'auth-map'"),
}
}
return &Config{
Type: at,
Realm: realm,
File: passFile,
Secured: true,
FileSHA: file.SHA1(passFile),
Secret: name,
Type: at,
Realm: realm,
File: passFilename,
Secured: true,
FileSHA: file.SHA1(passFilename),
Secret: name,
SecretType: secretType,
}, nil
}
// dumpSecret dumps the content of a secret into a file
// in the expected format for the specified authorization
func dumpSecret(filename string, secret *api.Secret) error {
func dumpSecretAuthFile(filename string, secret *api.Secret) error {
val, ok := secret.Data["auth"]
if !ok {
return ing_errors.LocationDenied{
@ -165,3 +192,22 @@ func dumpSecret(filename string, secret *api.Secret) error {
return nil
}
func dumpSecretAuthMap(filename string, secret *api.Secret) error {
builder := &strings.Builder{}
for user, pass := range secret.Data {
builder.WriteString(user)
builder.WriteString(":")
builder.WriteString(string(pass))
builder.WriteString("\n")
}
err := ioutil.WriteFile(filename, []byte(builder.String()), file.ReadWriteByUser)
if err != nil {
return ing_errors.LocationDenied{
Reason: errors.Wrap(err, "unexpected error creating password file"),
}
}
return nil
}

View file

@ -26,7 +26,7 @@ import (
"github.com/pkg/errors"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -34,28 +34,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -182,6 +182,25 @@ func TestIngressAuthWithoutSecret(t *testing.T) {
}
}
func TestIngressAuthInvalidSecretKey(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("auth-type")] = "basic"
data[parser.GetAnnotationWithPrefix("auth-secret")] = "demo-secret"
data[parser.GetAnnotationWithPrefix("auth-secret-type")] = "invalid-type"
data[parser.GetAnnotationWithPrefix("auth-realm")] = "-realm-"
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
_, err := NewParser(dir, mockSecret{}).Parse(ing)
if err == nil {
t.Errorf("expected an error with invalid secret name")
}
}
func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
dir, err := ioutil.TempDir("", fmt.Sprintf("%v", time.Now().Unix()))
if err != nil {
@ -197,20 +216,30 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
return tmpfile.Name(), dir, s
}
func TestDumpSecret(t *testing.T) {
func TestDumpSecretAuthFile(t *testing.T) {
tmpfile, dir, s := dummySecretContent(t)
defer os.RemoveAll(dir)
sd := s.Data
s.Data = nil
err := dumpSecret(tmpfile, s)
err := dumpSecretAuthFile(tmpfile, s)
if err == nil {
t.Errorf("Expected error with secret without auth")
}
s.Data = sd
err = dumpSecret(tmpfile, s)
err = dumpSecretAuthFile(tmpfile, s)
if err != nil {
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err)
}
}
func TestDumpSecretAuthMap(t *testing.T) {
tmpfile, dir, s := dummySecretContent(t)
defer os.RemoveAll(dir)
err := dumpSecretAuthMap(tmpfile, s)
if err != nil {
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err)
}

152
internal/ingress/annotations/authreq/main.go Executable file → Normal file
View file

@ -18,13 +18,12 @@ package authreq
import (
"fmt"
"net/url"
"regexp"
"strings"
"k8s.io/klog"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
@ -36,14 +35,20 @@ import (
type Config struct {
URL string `json:"url"`
// Host contains the hostname defined in the URL
Host string `json:"host"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"`
ResponseHeaders []string `json:"responseHeaders,omitempty"`
RequestRedirect string `json:"requestRedirect"`
AuthSnippet string `json:"authSnippet"`
Host string `json:"host"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"`
ResponseHeaders []string `json:"responseHeaders,omitempty"`
RequestRedirect string `json:"requestRedirect"`
AuthSnippet string `json:"authSnippet"`
AuthCacheKey string `json:"authCacheKey"`
AuthCacheDuration []string `json:"authCacheDuration"`
ProxySetHeaders map[string]string `json:"proxySetHeaders,omitempty"`
}
// DefaultCacheDuration is the fallback value if no cache duration is provided
const DefaultCacheDuration = "200 202 401 5m"
// Equal tests for equality between two Config types
func (e1 *Config) Equal(e2 *Config) bool {
if e1 == e2 {
@ -77,12 +82,18 @@ func (e1 *Config) Equal(e2 *Config) bool {
return false
}
return true
if e1.AuthCacheKey != e2.AuthCacheKey {
return false
}
return sets.StringElementsMatch(e1.AuthCacheDuration, e2.AuthCacheDuration)
}
var (
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
statusCodeRegex = regexp.MustCompile(`^[\d]{3}$`)
durationRegex = regexp.MustCompile(`^[\d]+(ms|s|m|h|d|w|M|y)$`) // see http://nginx.org/en/docs/syntax.html
)
// ValidMethod checks is the provided string a valid HTTP method
@ -104,6 +115,31 @@ func ValidHeader(header string) bool {
return headerRegexp.Match([]byte(header))
}
// ValidCacheDuration checks if the provided string is a valid cache duration
// spec: [code ...] [time ...];
// with: code is an http status code
// time must match the time regex and may appear multiple times, e.g. `1h 30m`
func ValidCacheDuration(duration string) bool {
elements := strings.Split(duration, " ")
seenDuration := false
for _, element := range elements {
if len(element) == 0 {
continue
}
if statusCodeRegex.Match([]byte(element)) {
if seenDuration {
return false // code after duration
}
continue
}
if durationRegex.Match([]byte(element)) {
seenDuration = true
}
}
return seenDuration
}
type authReq struct {
r resolver.Resolver
}
@ -115,16 +151,16 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to use an Config URL as source for authentication
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a authReq) Parse(ing *networking.Ingress) (interface{}, error) {
// Required Parameters
urlString, err := parser.GetStringAnnotation("auth-url", ing)
if err != nil {
return nil, err
}
authURL, message := ParseStringToURL(urlString)
if authURL == nil {
return nil, ing_errors.NewLocationDenied(message)
authURL, err := parser.StringToURL(urlString)
if err != nil {
return nil, ing_errors.InvalidContent{Name: err.Error()}
}
authMethod, _ := parser.GetStringAnnotation("auth-method", ing)
@ -143,6 +179,17 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
klog.V(3).Infof("auth-snippet annotation is undefined and will not be set")
}
authCacheKey, err := parser.GetStringAnnotation("auth-cache-key", ing)
if err != nil {
klog.V(3).Infof("auth-cache-key annotation is undefined and will not be set")
}
durstr, _ := parser.GetStringAnnotation("auth-cache-duration", ing)
authCacheDuration, err := ParseStringToCacheDurations(durstr)
if err != nil {
return nil, err
}
responseHeaders := []string{}
hstr, _ := parser.GetStringAnnotation("auth-response-headers", ing)
if len(hstr) != 0 {
@ -158,34 +205,65 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
}
}
proxySetHeaderMap, err := parser.GetStringAnnotation("auth-proxy-set-headers", ing)
if err != nil {
klog.V(3).Infof("auth-set-proxy-headers annotation is undefined and will not be set")
}
var proxySetHeaders map[string]string
if proxySetHeaderMap != "" {
proxySetHeadersMapContents, err := a.r.GetConfigMap(proxySetHeaderMap)
if err != nil {
return nil, ing_errors.NewLocationDenied(fmt.Sprintf("unable to find configMap %q", proxySetHeaderMap))
}
for header := range proxySetHeadersMapContents.Data {
if !ValidHeader(header) {
return nil, ing_errors.NewLocationDenied("invalid proxy-set-headers in configmap")
}
}
proxySetHeaders = proxySetHeadersMapContents.Data
}
requestRedirect, _ := parser.GetStringAnnotation("auth-request-redirect", ing)
return &Config{
URL: urlString,
Host: authURL.Hostname(),
SigninURL: signIn,
Method: authMethod,
ResponseHeaders: responseHeaders,
RequestRedirect: requestRedirect,
AuthSnippet: authSnippet,
URL: urlString,
Host: authURL.Hostname(),
SigninURL: signIn,
Method: authMethod,
ResponseHeaders: responseHeaders,
RequestRedirect: requestRedirect,
AuthSnippet: authSnippet,
AuthCacheKey: authCacheKey,
AuthCacheDuration: authCacheDuration,
ProxySetHeaders: proxySetHeaders,
}, nil
}
// ParseStringToURL parses the provided string into URL and returns error
// message in case of failure
func ParseStringToURL(input string) (*url.URL, string) {
parsedURL, err := url.Parse(input)
if err != nil {
return nil, fmt.Sprintf("%v is not a valid URL: %v", input, err)
// ParseStringToCacheDurations parses and validates the provided string
// into a list of cache durations.
// It will always return at least one duration (the default duration)
func ParseStringToCacheDurations(input string) ([]string, error) {
authCacheDuration := []string{}
if len(input) != 0 {
arr := strings.Split(input, ",")
for _, duration := range arr {
duration = strings.TrimSpace(duration)
if len(duration) > 0 {
if !ValidCacheDuration(duration) {
authCacheDuration = []string{DefaultCacheDuration}
return authCacheDuration, ing_errors.NewLocationDenied(fmt.Sprintf("invalid cache duration: %s", duration))
}
authCacheDuration = append(authCacheDuration, duration)
}
}
}
if parsedURL.Scheme == "" {
return nil, "url scheme is empty."
} else if parsedURL.Host == "" {
return nil, "url host is empty."
} else if strings.Contains(parsedURL.Host, "..") {
return nil, "invalid url host."
}
return parsedURL, ""
if len(authCacheDuration) == 0 {
authCacheDuration = append(authCacheDuration, DefaultCacheDuration)
}
return authCacheDuration, nil
}

180
internal/ingress/annotations/authreq/main_test.go Executable file → Normal file
View file

@ -18,12 +18,11 @@ package authreq
import (
"fmt"
"net/url"
"reflect"
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -31,28 +30,28 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -79,17 +78,19 @@ func TestAnnotations(t *testing.T) {
method string
requestRedirect string
authSnippet string
authCacheKey string
expErr bool
}{
{"empty", "", "", "", "", "", true},
{"no scheme", "bar", "bar", "", "", "", true},
{"invalid host", "http://", "http://", "", "", "", true},
{"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", "", "", true},
{"valid URL", "http://bar.foo.com/external-auth", "http://bar.foo.com/external-auth", "", "", "", false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "POST", "", "", false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "", "", false},
{"valid URL - request redirect", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "http://foo.com/redirect-me", "", false},
{"auth snippet", "http://foo.com/external-auth", "http://foo.com/external-auth", "", "", "proxy_set_header My-Custom-Header 42;", false},
{"empty", "", "", "", "", "", "", true},
{"no scheme", "bar", "bar", "", "", "", "", true},
{"invalid host", "http://", "http://", "", "", "", "", true},
{"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", "", "", "", true},
{"valid URL", "http://bar.foo.com/external-auth", "http://bar.foo.com/external-auth", "", "", "", "", false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "POST", "", "", "", false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "", "", "", false},
{"valid URL - request redirect", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", "http://foo.com/redirect-me", "", "", false},
{"auth snippet", "http://foo.com/external-auth", "http://foo.com/external-auth", "", "", "proxy_set_header My-Custom-Header 42;", "", false},
{"auth cache ", "http://foo.com/external-auth", "http://foo.com/external-auth", "", "", "", "$foo$bar", false},
}
for _, test := range tests {
@ -98,6 +99,7 @@ func TestAnnotations(t *testing.T) {
data[parser.GetAnnotationWithPrefix("auth-method")] = fmt.Sprintf("%v", test.method)
data[parser.GetAnnotationWithPrefix("auth-request-redirect")] = test.requestRedirect
data[parser.GetAnnotationWithPrefix("auth-snippet")] = test.authSnippet
data[parser.GetAnnotationWithPrefix("auth-cache-key")] = test.authCacheKey
i, err := NewParser(&resolver.Mock{}).Parse(ing)
if test.expErr {
@ -129,6 +131,9 @@ func TestAnnotations(t *testing.T) {
if u.AuthSnippet != test.authSnippet {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.authSnippet, u.AuthSnippet)
}
if u.AuthCacheKey != test.authCacheKey {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.authCacheKey, u.AuthCacheKey)
}
}
}
@ -162,12 +167,11 @@ 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
}
t.Log(i)
u, ok := i.(*Config)
if !ok {
t.Errorf("%v: expected an External type", test.title)
@ -180,37 +184,131 @@ func TestHeaderAnnotations(t *testing.T) {
}
}
func TestParseStringToURL(t *testing.T) {
validURL := "http://bar.foo.com/external-auth"
validParsedURL, _ := url.Parse(validURL)
func TestCacheDurationAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
ing.SetAnnotations(data)
tests := []struct {
title string
url string
message string
parsed *url.URL
expErr bool
title string
url string
duration string
parsedDuration []string
expErr bool
}{
{"empty", "", "url scheme is empty.", nil, true},
{"no scheme", "bar", "url scheme is empty.", nil, true},
{"invalid host", "http://", "url host is empty.", nil, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "invalid url host.", nil, true},
{"valid URL", validURL, "", validParsedURL, false},
{"nothing", "http://goog.url", "", []string{DefaultCacheDuration}, false},
{"spaces", "http://goog.url", " ", []string{DefaultCacheDuration}, false},
{"one duration", "http://goog.url", "5m", []string{"5m"}, false},
{"two durations", "http://goog.url", "200 202 10m, 401 5m", []string{"200 202 10m", "401 5m"}, false},
{"two durations and empty entries", "http://goog.url", ",5m,,401 10m,", []string{"5m", "401 10m"}, false},
{"only status code provided", "http://goog.url", "200", []string{DefaultCacheDuration}, true},
{"mixed valid/invalid", "http://goog.url", "5m, xaxax", []string{DefaultCacheDuration}, true},
{"code after duration", "http://goog.url", "5m 200", []string{DefaultCacheDuration}, true},
}
for _, test := range tests {
data[parser.GetAnnotationWithPrefix("auth-url")] = test.url
data[parser.GetAnnotationWithPrefix("auth-cache-duration")] = test.duration
i, err := ParseStringToURL(test.url)
i, err := NewParser(&resolver.Mock{}).Parse(ing)
if test.expErr {
if err != test.message {
t.Errorf("%v: expected error \"%v\" but \"%v\" was returned", test.title, test.message, err)
if err == nil {
t.Errorf("expected error but retuned nil")
}
continue
}
if i.String() != test.parsed.String() {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.parsed, i)
u, ok := i.(*Config)
if !ok {
t.Errorf("%v: expected an External type", test.title)
continue
}
if !reflect.DeepEqual(u.AuthCacheDuration, test.parsedDuration) {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.duration, u.AuthCacheDuration)
}
}
}
func TestParseStringToCacheDurations(t *testing.T) {
tests := []struct {
title string
duration string
expectedDurations []string
expErr bool
}{
{"empty", "", []string{DefaultCacheDuration}, false},
{"invalid", ",200,", []string{DefaultCacheDuration}, true},
{"single", ",200 5m,", []string{"200 5m"}, false},
{"multiple with duration", ",5m,,401 10m,", []string{"5m", "401 10m"}, false},
{"multiple durations", "200 202 401 5m, 418 30m", []string{"200 202 401 5m", "418 30m"}, false},
}
for _, test := range tests {
dur, err := ParseStringToCacheDurations(test.duration)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but nil was returned", test.title)
}
continue
}
if !reflect.DeepEqual(dur, test.expectedDurations) {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.expectedDurations, dur)
}
}
}
func TestProxySetHeaders(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
ing.SetAnnotations(data)
tests := []struct {
title string
url string
headers map[string]string
expErr bool
}{
{"single header", "http://goog.url", map[string]string{"header": "h1"}, false},
{"no header map", "http://goog.url", nil, true},
{"header with spaces", "http://goog.url", map[string]string{"header": "bad value"}, false},
{"header with other bad symbols", "http://goog.url", map[string]string{"header": "bad+value"}, false},
}
for _, test := range tests {
data[parser.GetAnnotationWithPrefix("auth-url")] = test.url
data[parser.GetAnnotationWithPrefix("auth-proxy-set-headers")] = "proxy-headers-map"
data[parser.GetAnnotationWithPrefix("auth-method")] = "GET"
configMapResolver := &resolver.Mock{
ConfigMaps: map[string]*api.ConfigMap{},
}
if test.headers != nil {
configMapResolver.ConfigMaps["proxy-headers-map"] = &api.ConfigMap{Data: test.headers}
}
i, err := NewParser(configMapResolver).Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("expected error but retuned nil")
}
continue
}
u, ok := i.(*Config)
if !ok {
t.Errorf("%v: expected an External type", test.title)
continue
}
if !reflect.DeepEqual(u.ProxySetHeaders, test.headers) {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.headers, u.ProxySetHeaders)
}
}
}

4
internal/ingress/annotations/authreqglobal/main.go Executable file → Normal file
View file

@ -17,7 +17,7 @@ limitations under the License.
package authreqglobal
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,7 +34,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to enable or disable global external authentication
func (a authReqGlobal) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a authReqGlobal) Parse(ing *networking.Ingress) (interface{}, error) {
enableGlobalAuth, err := parser.GetBoolAnnotation("enable-global-auth", ing)
if err != nil {

20
internal/ingress/annotations/authreqglobal/main_test.go Executable file → Normal file
View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -28,28 +28,28 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -18,7 +18,7 @@ package authtls
import (
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"regexp"
@ -86,7 +86,7 @@ type authTLS struct {
// Parse parses the annotations contained in the ingress
// rule used to use a Certificate as authentication method
func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a authTLS) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -28,28 +28,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -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
}
@ -126,3 +126,136 @@ func TestAnnotations(t *testing.T) {
t.Errorf("expected %v but got %v", true, u.PassCertToUpstream)
}
}
func TestInvalidAnnotations(t *testing.T) {
ing := buildIngress()
fakeSecret := &mockSecret{}
data := map[string]string{}
// No annotation
_, err := NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
// Invalid NameSpace
data[parser.GetAnnotationWithPrefix("auth-tls-secret")] = "demo-secret"
ing.SetAnnotations(data)
_, err = NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
// Invalid Auth Certificate
data[parser.GetAnnotationWithPrefix("auth-tls-secret")] = "default/invalid-demo-secret"
ing.SetAnnotations(data)
_, err = NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
// Invalid optional Annotations
data[parser.GetAnnotationWithPrefix("auth-tls-secret")] = "default/demo-secret"
data[parser.GetAnnotationWithPrefix("auth-tls-verify-client")] = "w00t"
data[parser.GetAnnotationWithPrefix("auth-tls-verify-depth")] = "abcd"
data[parser.GetAnnotationWithPrefix("auth-tls-pass-certificate-to-upstream")] = "nahh"
ing.SetAnnotations(data)
i, err := NewParser(fakeSecret).Parse(ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
u, ok := i.(*Config)
if !ok {
t.Errorf("expected *Config but got %v", u)
}
if u.VerifyClient != "on" {
t.Errorf("expected %v but got %v", "on", u.VerifyClient)
}
if u.ValidationDepth != 1 {
t.Errorf("expected %v but got %v", 1, u.ValidationDepth)
}
if u.PassCertToUpstream != false {
t.Errorf("expected %v but got %v", false, u.PassCertToUpstream)
}
}
func TestEquals(t *testing.T) {
cfg1 := &Config{}
cfg2 := &Config{}
// Same config
result := cfg1.Equal(cfg1)
if result != true {
t.Errorf("Expected true")
}
// compare nil
result = cfg1.Equal(nil)
if result != false {
t.Errorf("Expected false")
}
// Different Certs
sslCert1 := resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
CASHA: "abc",
}
sslCert2 := resolver.AuthSSLCert{
Secret: "default/other-demo-secret",
CAFileName: "/ssl/ca.crt",
CASHA: "abc",
}
cfg1.AuthSSLCert = sslCert1
cfg2.AuthSSLCert = sslCert2
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.AuthSSLCert = sslCert1
// Different Verify Client
cfg1.VerifyClient = "on"
cfg2.VerifyClient = "off"
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.VerifyClient = "on"
// Different Validation Depth
cfg1.ValidationDepth = 1
cfg2.ValidationDepth = 2
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.ValidationDepth = 1
// Different Error Page
cfg1.ErrorPage = "error-1"
cfg2.ErrorPage = "error-2"
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.ErrorPage = "error-1"
// Different Pass to Upstream
cfg1.PassCertToUpstream = true
cfg2.PassCertToUpstream = false
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.PassCertToUpstream = true
// Equal Configs
result = cfg1.Equal(cfg2)
if result != true {
t.Errorf("Expected true")
}
}

View file

@ -20,7 +20,7 @@ import (
"regexp"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -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 {
@ -45,7 +45,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate the backend protocol.
func (a backendProtocol) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a backendProtocol) Parse(ing *networking.Ingress) (interface{}, error) {
if ing.GetAnnotations() == nil {
return HTTP, nil
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -28,14 +28,14 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
func buildIngress() *networking.Ingress {
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},

View file

@ -17,7 +17,7 @@ limitations under the License.
package canary
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
@ -30,11 +30,12 @@ type canary struct {
// Config returns the configuration rules for setting up the Canary
type Config struct {
Enabled bool
Weight int
Header string
HeaderValue string
Cookie string
Enabled bool
Weight int
Header string
HeaderValue string
HeaderPattern string
Cookie string
}
// NewParser parses the ingress for canary related annotations
@ -44,7 +45,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress
// rule used to indicate if the canary should be enabled and with what config
func (c canary) Parse(ing *extensions.Ingress) (interface{}, error) {
func (c canary) Parse(ing *networking.Ingress) (interface{}, error) {
config := &Config{}
var err error
@ -68,12 +69,18 @@ func (c canary) Parse(ing *extensions.Ingress) (interface{}, error) {
config.HeaderValue = ""
}
config.HeaderPattern, err = parser.GetStringAnnotation("canary-by-header-pattern", ing)
if err != nil {
config.HeaderPattern = ""
}
config.Cookie, err = parser.GetStringAnnotation("canary-by-cookie", ing)
if err != nil {
config.Cookie = ""
}
if !config.Enabled && (config.Weight > 0 || len(config.Header) > 0 || len(config.HeaderValue) > 0 || len(config.Cookie) > 0) {
if !config.Enabled && (config.Weight > 0 || len(config.Header) > 0 || len(config.HeaderValue) > 0 || len(config.Cookie) > 0 ||
len(config.HeaderPattern) > 0) {
return nil, errors.NewInvalidAnnotationConfiguration("canary", "configured but not enabled")
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -30,28 +30,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: metaV1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -17,8 +17,9 @@ limitations under the License.
package class
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/klog"
networking "k8s.io/api/networking/v1beta1"
)
const (
@ -41,7 +42,7 @@ var (
// IsValid returns true if the given Ingress either doesn't specify
// the ingress.class annotation, or it's set to the configured in the
// ingress controller.
func IsValid(ing *extensions.Ingress) bool {
func IsValid(ing *networking.Ingress) bool {
ingress, ok := ing.GetAnnotations()[IngressKey]
if !ok {
klog.V(3).Infof("annotation %v is not present in ingress %v/%v", IngressKey, ing.Namespace, ing.Name)

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -47,7 +47,7 @@ func TestIsValidClass(t *testing.T) {
{"custom", "nginx", "nginx", false},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,

View file

@ -17,7 +17,7 @@ limitations under the License.
package clientbodybuffersize
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,6 +34,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to add an client-body-buffer-size to the provided locations
func (cbbs clientBodyBufferSize) Parse(ing *extensions.Ingress) (interface{}, error) {
func (cbbs clientBodyBufferSize) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("client-body-buffer-size", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package connection
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -40,7 +40,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress
// rule used to indicate if the connection header should be overridden.
func (a connection) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a connection) Parse(ing *networking.Ingress) (interface{}, error) {
cp, err := parser.GetStringAnnotation("connection-proxy-header", ing)
if err != nil {
return &Config{

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -43,12 +43,12 @@ func TestParse(t *testing.T) {
{nil, &Config{Enabled: false}},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -19,7 +19,7 @@ package cors
import (
"regexp"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -96,7 +96,7 @@ func (c1 *Config) Equal(c2 *Config) bool {
// Parse parses the annotations contained in the ingress
// rule used to indicate if the location/s should allows CORS
func (c cors) Parse(ing *extensions.Ingress) (interface{}, error) {
func (c cors) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}

View file

@ -20,35 +20,35 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -20,7 +20,7 @@ import (
"strconv"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -37,7 +37,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress to use
// custom http errors
func (e customhttperrors) Parse(ing *extensions.Ingress) (interface{}, error) {
func (e customhttperrors) Parse(ing *networking.Ingress) (interface{}, error) {
c, err := parser.GetStringAnnotation("custom-http-errors", ing)
if err != nil {
return nil, err

View file

@ -22,7 +22,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -30,14 +30,14 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
func buildIngress() *networking.Ingress {
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},

View file

@ -20,7 +20,7 @@ import (
"fmt"
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -37,7 +37,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress to use
// a custom default backend
func (db backend) Parse(ing *extensions.Ingress) (interface{}, error) {
func (db backend) Parse(ing *networking.Ingress) (interface{}, error) {
s, err := parser.GetStringAnnotation("default-backend", ing)
if err != nil {
return nil, err

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -29,28 +29,28 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

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 len(config.Params) != 0 {
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 len(config.Params) != 0 {
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 len(config.Params) != 0 {
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 len(config.Params) != 2 {
t.Errorf("Params should have a length of 2")
}
if config.Params["REDIRECT_STATUS"] != "200" || config.Params["SERVER_NAME"] != "$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 len(config.Params) != 2 {
t.Errorf("Params should have a length of 2")
}
if config.Params["REDIRECT_STATUS"] != "200" || config.Params["SERVER_NAME"] != "$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

@ -17,7 +17,7 @@ limitations under the License.
package http2pushpreload
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,6 +34,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to add http2 push preload to the server
func (h2pp http2PushPreload) Parse(ing *extensions.Ingress) (interface{}, error) {
func (h2pp http2PushPreload) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation("http2-push-preload", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
{nil, false},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package influxdb
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -42,7 +42,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
}
// Parse parses the annotations to look for InfluxDB configurations
func (c influxdb) Parse(ing *extensions.Ingress) (interface{}, error) {
func (c influxdb) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}

View file

@ -20,35 +20,35 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -22,7 +22,7 @@ import (
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/net"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -45,12 +45,7 @@ func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool {
return false
}
match := sets.StringElementsMatch(sr1.CIDR, sr2.CIDR)
if !match {
return false
}
return true
return sets.StringElementsMatch(sr1.CIDR, sr2.CIDR)
}
type ipwhitelist struct {
@ -66,7 +61,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// rule used to limit access to certain client addresses or networks.
// Multiple ranges can specified using commas as separator
// e.g. `18.0.0.0/8,56.0.0.0/8`
func (a ipwhitelist) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a ipwhitelist) Parse(ing *networking.Ingress) (interface{}, error) {
defBackend := a.r.GetDefaultBackend()
sort.Strings(defBackend.WhitelistSourceRange)

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -28,28 +28,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -17,7 +17,7 @@ limitations under the License.
package loadbalancing
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -35,6 +35,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a loadbalancing) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a loadbalancing) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("load-balance", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -43,12 +43,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package log
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -53,7 +53,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress
// rule used to indicate if the location/s should enable logs
func (l log) Parse(ing *extensions.Ingress) (interface{}, error) {
func (l log) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}

View file

@ -20,35 +20,35 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -1,126 +0,0 @@
/*
Copyright 2016 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 luarestywaf
import (
"strings"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
"k8s.io/ingress-nginx/internal/sets"
)
var luaRestyWAFModes = map[string]bool{"ACTIVE": true, "INACTIVE": true, "SIMULATE": true}
// Config returns lua-resty-waf configuration for an Ingress rule
type Config struct {
Mode string `json:"mode"`
Debug bool `json:"debug"`
IgnoredRuleSets []string `json:"ignored-rulesets"`
ExtraRulesetString string `json:"extra-ruleset-string"`
ScoreThreshold int `json:"score-threshold"`
AllowUnknownContentTypes bool `json:"allow-unknown-content-types"`
ProcessMultipartBody bool `json:"process-multipart-body"`
}
// Equal tests for equality between two Config types
func (e1 *Config) Equal(e2 *Config) bool {
if e1 == e2 {
return true
}
if e1 == nil || e2 == nil {
return false
}
if e1.Mode != e2.Mode {
return false
}
if e1.Debug != e2.Debug {
return false
}
match := sets.StringElementsMatch(e1.IgnoredRuleSets, e2.IgnoredRuleSets)
if !match {
return false
}
if e1.ExtraRulesetString != e2.ExtraRulesetString {
return false
}
if e1.ScoreThreshold != e2.ScoreThreshold {
return false
}
if e1.AllowUnknownContentTypes != e2.AllowUnknownContentTypes {
return false
}
if e1.ProcessMultipartBody != e2.ProcessMultipartBody {
return false
}
return true
}
type luarestywaf struct {
r resolver.Resolver
}
// NewParser creates a new CORS annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return luarestywaf{r}
}
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a luarestywaf) Parse(ing *extensions.Ingress) (interface{}, error) {
var err error
config := &Config{}
mode, err := parser.GetStringAnnotation("lua-resty-waf", ing)
if err != nil {
return &Config{}, err
}
config.Mode = strings.ToUpper(mode)
if _, ok := luaRestyWAFModes[config.Mode]; !ok {
return &Config{}, errors.NewInvalidAnnotationContent("lua-resty-waf", mode)
}
config.Debug, _ = parser.GetBoolAnnotation("lua-resty-waf-debug", ing)
ignoredRuleSetsStr, _ := parser.GetStringAnnotation("lua-resty-waf-ignore-rulesets", ing)
config.IgnoredRuleSets = strings.FieldsFunc(ignoredRuleSetsStr, func(c rune) bool {
strC := string(c)
return strC == "," || strC == " "
})
// TODO(elvinefendi) maybe validate the ruleset string here
config.ExtraRulesetString, _ = parser.GetStringAnnotation("lua-resty-waf-extra-rules", ing)
config.ScoreThreshold, _ = parser.GetIntAnnotation("lua-resty-waf-score-threshold", ing)
config.AllowUnknownContentTypes, _ = parser.GetBoolAnnotation("lua-resty-waf-allow-unknown-content-types", ing)
config.ProcessMultipartBody, err = parser.GetBoolAnnotation("lua-resty-waf-process-multipart-body", ing)
if err != nil {
config.ProcessMultipartBody = true
}
return config, nil
}

View file

@ -1,86 +0,0 @@
/*
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.
*/
package luarestywaf
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/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) {
luaRestyWAFAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf")
luaRestyWAFDebugAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-debug")
luaRestyWAFIgnoredRuleSetsAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-ignore-rulesets")
luaRestyWAFScoreThresholdAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-score-threshold")
luaRestyWAFAllowUnknownContentTypesAnnotation := parser.GetAnnotationWithPrefix("lua-resty-waf-allow-unknown-content-types")
luaRestyWAFProcessMultipartBody := parser.GetAnnotationWithPrefix("lua-resty-waf-process-multipart-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
}{
{nil, &Config{}},
{map[string]string{}, &Config{}},
{map[string]string{luaRestyWAFAnnotation: "active"}, &Config{Mode: "ACTIVE", Debug: false, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFDebugAnnotation: "true"}, &Config{Debug: false}},
{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "ACTIVE", Debug: true, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFDebugAnnotation: "false"}, &Config{Mode: "ACTIVE", Debug: false, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "inactive", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "INACTIVE", Debug: true, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{
luaRestyWAFAnnotation: "active",
luaRestyWAFDebugAnnotation: "true",
luaRestyWAFIgnoredRuleSetsAnnotation: "ruleset1, ruleset2 ruleset3, another.ruleset",
luaRestyWAFScoreThresholdAnnotation: "10",
luaRestyWAFAllowUnknownContentTypesAnnotation: "true"},
&Config{Mode: "ACTIVE", Debug: true, IgnoredRuleSets: []string{"ruleset1", "ruleset2", "ruleset3", "another.ruleset"}, ScoreThreshold: 10, AllowUnknownContentTypes: true, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "siMulate", luaRestyWAFDebugAnnotation: "true"}, &Config{Mode: "SIMULATE", Debug: true, IgnoredRuleSets: []string{}, ProcessMultipartBody: true}},
{map[string]string{luaRestyWAFAnnotation: "siMulateX", luaRestyWAFDebugAnnotation: "true"}, &Config{Debug: false}},
{map[string]string{luaRestyWAFAnnotation: "active", luaRestyWAFProcessMultipartBody: "false"}, &Config{Mode: "ACTIVE", ProcessMultipartBody: false, IgnoredRuleSets: []string{}}},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
config := result.(*Config)
if !config.Equal(testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -0,0 +1,89 @@
/*
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"
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 {
Source string `json:"source"`
RequestBody string `json:"requestBody"`
Target string `json:"target"`
}
// Equal tests for equality between two Configuration types
func (m1 *Config) Equal(m2 *Config) bool {
if m1 == m2 {
return true
}
if m1 == nil || m2 == nil {
return false
}
if m1.Source != m2.Source {
return false
}
if m1.RequestBody != m2.RequestBody {
return false
}
if m1.Target != m2.Target {
return false
}
return true
}
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{
Source: fmt.Sprintf("/_mirror-%v", ing.UID),
}
var err error
config.RequestBody, err = parser.GetStringAnnotation("mirror-request-body", ing)
if err != nil || config.RequestBody != "off" {
config.RequestBody = "on"
}
config.Target, err = parser.GetStringAnnotation("mirror-target", ing)
if err != nil {
config.Target = ""
config.Source = ""
}
return config, nil
}

View file

@ -0,0 +1,72 @@
/*
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 (
"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) {
requestBody := parser.GetAnnotationWithPrefix("mirror-request-body")
backendURL := parser.GetAnnotationWithPrefix("mirror-target")
ap := NewParser(&resolver.Mock{})
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
ngxURI := "/_mirror-c89a5111-b2e9-4af8-be19-c2a4a924c256"
testCases := []struct {
annotations map[string]string
expected *Config
}{
{map[string]string{backendURL: "https://test.env.com/$request_uri"}, &Config{
Source: ngxURI,
RequestBody: "on",
Target: "https://test.env.com/$request_uri",
}},
{map[string]string{requestBody: "off"}, &Config{
Source: "",
RequestBody: "off",
Target: "",
}},
}
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
UID: "c89a5111-b2e9-4af8-be19-c2a4a924c256",
},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if !reflect.DeepEqual(result, testCase.expected) {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
package modsecurity
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
@ -65,7 +65,7 @@ type modSecurity struct {
// Parse parses the annotations contained in the ingress
// rule used to enable ModSecurity in a particular location
func (a modSecurity) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a modSecurity) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -59,12 +59,12 @@ func TestParse(t *testing.T) {
{nil, Config{false, false, "", ""}},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -0,0 +1,61 @@
/*
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 opentracing
import (
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
type opentracing struct {
r resolver.Resolver
}
// Config contains the configuration to be used in the Ingress
type Config struct {
Enabled bool `json:"enabled"`
Set bool `json:"set"`
}
// Equal tests for equality between two Config types
func (bd1 *Config) Equal(bd2 *Config) bool {
if bd1.Set != bd2.Set {
return false
}
if bd1.Enabled != bd2.Enabled {
return false
}
return true
}
// NewParser creates a new serviceUpstream annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return opentracing{r}
}
func (s opentracing) Parse(ing *networking.Ingress) (interface{}, error) {
enabled, err := parser.GetBoolAnnotation("enable-opentracing", ing)
if err != nil {
return &Config{Set: false, Enabled: false}, nil
}
return &Config{Set: true, Enabled: enabled}, nil
}

View file

@ -0,0 +1,114 @@
/*
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 opentracing
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/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestIngressAnnotationOpentracingSetTrue(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("enable-opentracing")] = "true"
ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
openTracing, ok := val.(*Config)
if !ok {
t.Errorf("expected a Config type")
}
if !openTracing.Enabled {
t.Errorf("expected annotation value to be true, got false")
}
}
func TestIngressAnnotationOpentracingSetFalse(t *testing.T) {
ing := buildIngress()
// Test with explicitly set to false
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("enable-opentracing")] = "false"
ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
openTracing, ok := val.(*Config)
if !ok {
t.Errorf("expected a Config type")
}
if openTracing.Enabled {
t.Errorf("expected annotation value to be false, got true")
}
}
func TestIngressAnnotationOpentracingUnset(t *testing.T) {
ing := buildIngress()
// Test with no annotation specified
data := map[string]string{}
ing.SetAnnotations(data)
val, _ := NewParser(&resolver.Mock{}).Parse(ing)
_, ok := val.(*Config)
if !ok {
t.Errorf("expected a Config type")
}
}

View file

@ -18,10 +18,12 @@ package parser
import (
"fmt"
"net/url"
"strconv"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/ingress/errors"
)
@ -33,7 +35,7 @@ var (
// IngressAnnotation has a method to parse annotations located in Ingress
type IngressAnnotation interface {
Parse(ing *extensions.Ingress) (interface{}, error)
Parse(ing *networking.Ingress) (interface{}, error)
}
type ingAnnotations map[string]string
@ -75,7 +77,7 @@ func (a ingAnnotations) parseInt(name string) (int, error) {
return 0, errors.ErrMissingAnnotations
}
func checkAnnotation(name string, ing *extensions.Ingress) error {
func checkAnnotation(name string, ing *networking.Ingress) error {
if ing == nil || len(ing.GetAnnotations()) == 0 {
return errors.ErrMissingAnnotations
}
@ -87,7 +89,7 @@ func checkAnnotation(name string, ing *extensions.Ingress) error {
}
// GetBoolAnnotation extracts a boolean from an Ingress annotation
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
func GetBoolAnnotation(name string, ing *networking.Ingress) (bool, error) {
v := GetAnnotationWithPrefix(name)
err := checkAnnotation(v, ing)
if err != nil {
@ -97,7 +99,7 @@ func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
}
// GetStringAnnotation extracts a string from an Ingress annotation
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
func GetStringAnnotation(name string, ing *networking.Ingress) (string, error) {
v := GetAnnotationWithPrefix(name)
err := checkAnnotation(v, ing)
if err != nil {
@ -108,7 +110,7 @@ func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
}
// GetIntAnnotation extracts an int from an Ingress annotation
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
func GetIntAnnotation(name string, ing *networking.Ingress) (int, error) {
v := GetAnnotationWithPrefix(name)
err := checkAnnotation(v, ing)
if err != nil {
@ -130,3 +132,43 @@ func normalizeString(input string) string {
return strings.Join(trimmedContent, "\n")
}
var configmapAnnotations = sets.NewString(
"auth-proxy-set-header",
"fastcgi-params-configmap",
)
// AnnotationsReferencesConfigmap checks if at least one annotation in the Ingress rule
// references a configmap.
func AnnotationsReferencesConfigmap(ing *networking.Ingress) bool {
if ing == nil || len(ing.GetAnnotations()) == 0 {
return false
}
for name := range ing.GetAnnotations() {
if configmapAnnotations.Has(name) {
return true
}
}
return false
}
// StringToURL parses the provided string into URL and returns error
// message in case of failure
func StringToURL(input string) (*url.URL, error) {
parsedURL, err := url.Parse(input)
if err != nil {
return nil, fmt.Errorf("%v is not a valid URL: %v", input, err)
}
if parsedURL.Scheme == "" {
return nil, fmt.Errorf("url scheme is empty")
} else if parsedURL.Host == "" {
return nil, fmt.Errorf("url host is empty")
} else if strings.Contains(parsedURL.Host, "..") {
return nil, fmt.Errorf("invalid url host")
}
return parsedURL, nil
}

View file

@ -17,20 +17,21 @@ limitations under the License.
package parser
import (
"net/url"
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
func buildIngress() *networking.Ingress {
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
}
@ -93,8 +94,8 @@ func TestGetStringAnnotation(t *testing.T) {
{"valid - B", "string", " B", "B", false},
{"empty", "string", " ", "", true},
{"valid multiline", "string", `
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
`, `
rewrite (?i)/arcgis/rest/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/rest/services/Utilities/Geometry/GeometryServer$1 break;
rewrite (?i)/arcgis/services/Utilities/Geometry/GeometryServer(.*)$ /arcgis/services/Utilities/Geometry/GeometryServer$1 break;
@ -162,3 +163,40 @@ func TestGetIntAnnotation(t *testing.T) {
delete(data, test.field)
}
}
func TestStringToURL(t *testing.T) {
validURL := "http://bar.foo.com/external-auth"
validParsedURL, _ := url.Parse(validURL)
tests := []struct {
title string
url string
message string
parsed *url.URL
expErr bool
}{
{"empty", "", "url scheme is empty", nil, true},
{"no scheme", "bar", "url scheme is empty", nil, true},
{"invalid host", "http://", "url host is empty", nil, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "invalid url host", nil, true},
{"valid URL", validURL, "", validParsedURL, false},
}
for _, test := range tests {
i, err := StringToURL(test.url)
if test.expErr {
if err == nil {
t.Fatalf("%v: expected error but none returned", test.title)
}
if err.Error() != test.message {
t.Errorf("%v: expected error \"%v\" but \"%v\" was returned", test.title, test.message, err)
}
continue
}
if i.String() != test.parsed.String() {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.parsed, i)
}
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
package portinredirect
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,7 +34,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress
// rule used to indicate if the redirects must
func (a portInRedirect) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a portInRedirect) Parse(ing *networking.Ingress) (interface{}, error) {
up, err := parser.GetBoolAnnotation("use-port-in-redirects", ing)
if err != nil {
return a.r.GetDefaultBackend().UsePortInRedirects, nil

View file

@ -21,7 +21,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -30,28 +30,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -17,7 +17,7 @@ limitations under the License.
package proxy
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -25,21 +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"`
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
@ -95,6 +97,13 @@ func (l1 *Config) Equal(l2 *Config) bool {
if l1.ProxyBuffering != l2.ProxyBuffering {
return false
}
if l1.ProxyHTTPVersion != l2.ProxyHTTPVersion {
return false
}
if l1.ProxyMaxTempFileSize != l2.ProxyMaxTempFileSize {
return false
}
return true
}
@ -110,7 +119,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure upstream check parameters
func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a proxy) Parse(ing *networking.Ingress) (interface{}, error) {
defBackend := a.r.GetDefaultBackend()
config := &Config{}
@ -191,5 +200,15 @@ func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
config.ProxyBuffering = defBackend.ProxyBuffering
}
config.ProxyHTTPVersion, err = parser.GetStringAnnotation("proxy-http-version", ing)
if err != nil {
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

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -29,28 +29,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -81,6 +81,8 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
ProxyNextUpstreamTries: 3,
ProxyRequestBuffering: "on",
ProxyBuffering: "off",
ProxyHTTPVersion: "1.1",
ProxyMaxTempFileSize: "1024m",
}
}
@ -99,6 +101,8 @@ func TestProxy(t *testing.T) {
data[parser.GetAnnotationWithPrefix("proxy-next-upstream-tries")] = "3"
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)
@ -142,6 +146,12 @@ func TestProxy(t *testing.T) {
if p.ProxyBuffering != "on" {
t.Errorf("expected on as proxy-buffering but returned %v", p.ProxyBuffering)
}
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) {
@ -188,4 +198,10 @@ func TestProxyWithNoAnnotation(t *testing.T) {
if p.RequestBuffering != "on" {
t.Errorf("expected on as request-buffering but returned %v", p.RequestBuffering)
}
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

@ -0,0 +1,163 @@
/*
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 proxyssl
import (
"regexp"
"sort"
"strings"
"github.com/pkg/errors"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
"k8s.io/ingress-nginx/internal/k8s"
)
const (
defaultProxySSLCiphers = "DEFAULT"
defaultProxySSLProtocols = "TLSv1 TLSv1.1 TLSv1.2"
defaultProxySSLVerify = "off"
defaultProxySSLVerifyDepth = 1
)
var (
proxySSLOnOffRegex = regexp.MustCompile(`^(on|off)$`)
proxySSLProtocolRegex = regexp.MustCompile(`^(SSLv2|SSLv3|TLSv1|TLSv1\.1|TLSv1\.2|TLSv1\.3)$`)
)
// Config contains the AuthSSLCert used for mutual authentication
// and the configured VerifyDepth
type Config struct {
resolver.AuthSSLCert
Ciphers string `json:"ciphers"`
Protocols string `json:"protocols"`
ProxySSLName string `json:"proxySSLName"`
Verify string `json:"verify"`
VerifyDepth int `json:"verifyDepth"`
}
// Equal tests for equality between two Config types
func (pssl1 *Config) Equal(pssl2 *Config) bool {
if pssl1 == pssl2 {
return true
}
if pssl1 == nil || pssl2 == nil {
return false
}
if !(&pssl1.AuthSSLCert).Equal(&pssl2.AuthSSLCert) {
return false
}
if pssl1.Ciphers != pssl2.Ciphers {
return false
}
if pssl1.Protocols != pssl2.Protocols {
return false
}
if pssl1.Verify != pssl2.Verify {
return false
}
if pssl1.VerifyDepth != pssl2.VerifyDepth {
return false
}
return true
}
// NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.Resolver) parser.IngressAnnotation {
return proxySSL{resolver}
}
type proxySSL struct {
r resolver.Resolver
}
func sortProtocols(protocols string) string {
protolist := strings.Split(protocols, " ")
n := 0
for _, proto := range protolist {
proto = strings.TrimSpace(proto)
if proto == "" || !proxySSLProtocolRegex.MatchString(proto) {
continue
}
protolist[n] = proto
n++
}
if n == 0 {
return defaultProxySSLProtocols
}
protolist = protolist[:n]
sort.Strings(protolist)
return strings.Join(protolist, " ")
}
// Parse parses the annotations contained in the ingress
// rule used to use a Certificate as authentication method
func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}
proxysslsecret, err := parser.GetStringAnnotation("proxy-ssl-secret", ing)
if err != nil {
return &Config{}, err
}
_, _, err = k8s.ParseNameNS(proxysslsecret)
if err != nil {
return &Config{}, ing_errors.NewLocationDenied(err.Error())
}
proxyCert, err := p.r.GetAuthCertificate(proxysslsecret)
if err != nil {
e := errors.Wrap(err, "error obtaining certificate")
return &Config{}, ing_errors.LocationDenied{Reason: e}
}
config.AuthSSLCert = *proxyCert
config.Ciphers, err = parser.GetStringAnnotation("proxy-ssl-ciphers", ing)
if err != nil {
config.Ciphers = defaultProxySSLCiphers
}
config.Protocols, err = parser.GetStringAnnotation("proxy-ssl-protocols", ing)
if err != nil {
config.Protocols = defaultProxySSLProtocols
} else {
config.Protocols = sortProtocols(config.Protocols)
}
config.ProxySSLName, err = parser.GetStringAnnotation("proxy-ssl-name", ing)
if err != nil {
config.ProxySSLName = ""
}
config.Verify, err = parser.GetStringAnnotation("proxy-ssl-verify", ing)
if err != nil || !proxySSLOnOffRegex.MatchString(config.Verify) {
config.Verify = defaultProxySSLVerify
}
config.VerifyDepth, err = parser.GetIntAnnotation("proxy-ssl-verify-depth", ing)
if err != nil || config.VerifyDepth == 0 {
config.VerifyDepth = defaultProxySSLVerifyDepth
}
return config, nil
}

View file

@ -0,0 +1,269 @@
/*
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 proxyssl
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/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
// mocks the resolver for proxySSL
type mockSecret struct {
resolver.Mock
}
// GetAuthCertificate from mockSecret mocks the GetAuthCertificate for backend certificate authentication
func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
if name != "default/demo-secret" {
return nil, errors.Errorf("there is no secret with name %v", name)
}
return &resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
CASHA: "abc",
}, nil
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/demo-secret"
data[parser.GetAnnotationWithPrefix("proxy-ssl-ciphers")] = "HIGH:-SHA"
data[parser.GetAnnotationWithPrefix("proxy-ssl-name")] = "$host"
data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv1.3 SSLv2 TLSv1 TLSv1.2"
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "off"
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "off"
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "on"
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "3"
ing.SetAnnotations(data)
fakeSecret := &mockSecret{}
i, err := NewParser(fakeSecret).Parse(ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
u, ok := i.(*Config)
if !ok {
t.Errorf("expected *Config but got %v", u)
}
secret, err := fakeSecret.GetAuthCertificate("default/demo-secret")
if err != nil {
t.Errorf("unexpected error getting secret %v", err)
}
if u.AuthSSLCert.Secret != secret.Secret {
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
}
if u.Ciphers != "HIGH:-SHA" {
t.Errorf("expected %v but got %v", "HIGH:-SHA", u.Ciphers)
}
if u.Protocols != "SSLv2 TLSv1 TLSv1.2 TLSv1.3" {
t.Errorf("expected %v but got %v", "SSLv2 TLSv1 TLSv1.2 TLSv1.3", u.Protocols)
}
if u.Verify != "on" {
t.Errorf("expected %v but got %v", "on", u.Verify)
}
if u.VerifyDepth != 3 {
t.Errorf("expected %v but got %v", 3, u.VerifyDepth)
}
if u.ProxySSLName != "$host" {
t.Errorf("expected %v but got %v", "$host", u.ProxySSLName)
}
}
func TestInvalidAnnotations(t *testing.T) {
ing := buildIngress()
fakeSecret := &mockSecret{}
data := map[string]string{}
// No annotation
_, err := NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
// Invalid NameSpace
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "demo-secret"
ing.SetAnnotations(data)
_, err = NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
// Invalid Proxy Certificate
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/invalid-demo-secret"
ing.SetAnnotations(data)
_, err = NewParser(fakeSecret).Parse(ing)
if err == nil {
t.Errorf("Expected error with ingress but got nil")
}
// Invalid optional Annotations
data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/demo-secret"
data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv111 SSLv1"
data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "w00t"
data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "w00t"
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "w00t"
data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "abcd"
ing.SetAnnotations(data)
i, err := NewParser(fakeSecret).Parse(ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
u, ok := i.(*Config)
if !ok {
t.Errorf("expected *Config but got %v", u)
}
if u.Protocols != defaultProxySSLProtocols {
t.Errorf("expected %v but got %v", defaultProxySSLProtocols, u.Protocols)
}
if u.Verify != defaultProxySSLVerify {
t.Errorf("expected %v but got %v", defaultProxySSLVerify, u.Verify)
}
if u.VerifyDepth != defaultProxySSLVerifyDepth {
t.Errorf("expected %v but got %v", defaultProxySSLVerifyDepth, u.VerifyDepth)
}
}
func TestEquals(t *testing.T) {
cfg1 := &Config{}
cfg2 := &Config{}
// Same config
result := cfg1.Equal(cfg1)
if result != true {
t.Errorf("Expected true")
}
// compare nil
result = cfg1.Equal(nil)
if result != false {
t.Errorf("Expected false")
}
// Different Certs
sslCert1 := resolver.AuthSSLCert{
Secret: "default/demo-secret",
CAFileName: "/ssl/ca.crt",
CASHA: "abc",
}
sslCert2 := resolver.AuthSSLCert{
Secret: "default/other-demo-secret",
CAFileName: "/ssl/ca.crt",
CASHA: "abc",
}
cfg1.AuthSSLCert = sslCert1
cfg2.AuthSSLCert = sslCert2
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.AuthSSLCert = sslCert1
// Different Ciphers
cfg1.Ciphers = "DEFAULT"
cfg2.Ciphers = "HIGH:-SHA"
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.Ciphers = "DEFAULT"
// Different Protocols
cfg1.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3"
cfg2.Protocols = "SSLv3 TLSv1 TLSv1.2 TLSv1.3"
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3"
// Different Verify
cfg1.Verify = "off"
cfg2.Verify = "on"
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.Verify = "off"
// Different VerifyDepth
cfg1.VerifyDepth = 1
cfg2.VerifyDepth = 2
result = cfg1.Equal(cfg2)
if result != false {
t.Errorf("Expected false")
}
cfg2.VerifyDepth = 1
// Equal Configs
result = cfg1.Equal(cfg2)
if result != true {
t.Errorf("Expected true")
}
}

View file

@ -22,7 +22,7 @@ import (
"sort"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -95,12 +95,7 @@ func (rt1 *Config) Equal(rt2 *Config) bool {
return false
}
match := sets.StringElementsMatch(rt1.Whitelist, rt2.Whitelist)
if !match {
return false
}
return true
return sets.StringElementsMatch(rt1.Whitelist, rt2.Whitelist)
}
// Zone returns information about the NGINX rate limit (limit_req_zone)
@ -148,7 +143,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a ratelimit) Parse(ing *networking.Ingress) (interface{}, error) {
defBackend := a.r.GetDefaultBackend()
lr, err := parser.GetIntAnnotation("limit-rate", ing)
if err != nil {
@ -180,7 +175,7 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
}, nil
}
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
zoneName := fmt.Sprintf("%v_%v_%v", ing.GetNamespace(), ing.GetName(), ing.UID)
return &Config{
Connections: Zone{

View file

@ -22,7 +22,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -31,28 +31,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -21,7 +21,7 @@ import (
"net/url"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
@ -50,7 +50,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// rule used to create a redirect in the paths defined in the rule.
// If the Ingress contains both annotations the execution order is
// temporal and then permanent
func (r redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
func (r redirect) Parse(ing *networking.Ingress) (interface{}, error) {
r3w, _ := parser.GetBoolAnnotation("from-to-www-redirect", ing)
tr, err := parser.GetStringAnnotation("temporal-redirect", ing)

View file

@ -23,7 +23,7 @@ import (
"strconv"
"testing"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
@ -40,7 +40,7 @@ func TestPermanentRedirectWithDefaultCode(t *testing.T) {
t.Fatalf("Expected a parser.IngressAnnotation but returned nil")
}
ing := new(extensions.Ingress)
ing := new(networking.Ingress)
data := make(map[string]string, 1)
data[parser.GetAnnotationWithPrefix("permanent-redirect")] = defRedirectURL
@ -78,7 +78,7 @@ func TestPermanentRedirectWithCustomCode(t *testing.T) {
for n, tc := range testCases {
t.Run(n, func(t *testing.T) {
ing := new(extensions.Ingress)
ing := new(networking.Ingress)
data := make(map[string]string, 2)
data[parser.GetAnnotationWithPrefix("permanent-redirect")] = defRedirectURL
@ -109,7 +109,7 @@ func TestTemporalRedirect(t *testing.T) {
t.Fatalf("Expected a parser.IngressAnnotation but returned nil")
}
ing := new(extensions.Ingress)
ing := new(networking.Ingress)
data := make(map[string]string, 1)
data[parser.GetAnnotationWithPrefix("from-to-www-redirect")] = "true"

View file

@ -17,7 +17,7 @@ limitations under the License.
package rewrite
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -75,7 +75,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a rewrite) Parse(ing *networking.Ingress) (interface{}, error) {
var err error
config := &Config{}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -33,28 +33,28 @@ const (
defRoute = "/demo"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -17,7 +17,7 @@ limitations under the License.
package satisfy
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -33,7 +33,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
}
// Parse parses annotation contained in the ingress
func (s satisfy) Parse(ing *extensions.Ingress) (interface{}, error) {
func (s satisfy) Parse(ing *networking.Ingress) (interface{}, error) {
satisfy, err := parser.GetStringAnnotation("satisfy", ing)
if err != nil || (satisfy != "any" && satisfy != "all") {

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -28,28 +28,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "fake",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "fake.host.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/fake",
Backend: defaultBackend,

View file

@ -17,10 +17,8 @@ limitations under the License.
package secureupstream
import (
"fmt"
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -42,28 +40,9 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress
// rule used to indicate if the upstream servers should use SSL
func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
bp, _ := parser.GetStringAnnotation("backend-protocol", ing)
ca, _ := parser.GetStringAnnotation("secure-verify-ca-secret", ing)
secure := &Config{
CACert: resolver.AuthSSLCert{},
func (a su) Parse(ing *networking.Ingress) (secure interface{}, err error) {
if ca, _ := parser.GetStringAnnotation("secure-verify-ca-secret", ing); ca != "" {
klog.Errorf("NOTE! secure-verify-ca-secret is not suppored anymore. Please use proxy-ssl-secret instead")
}
if (bp != "HTTPS" && bp != "GRPCS") && ca != "" {
return secure,
errors.Errorf("trying to use CA from secret %v/%v on a non secure backend", ing.Namespace, ca)
}
if ca == "" {
return secure, nil
}
caCert, err := a.r.GetAuthCertificate(fmt.Sprintf("%v/%v", ing.Namespace, ca))
if err != nil {
return secure, errors.Wrap(err, "error obtaining certificate")
}
if caCert == nil {
return secure, nil
}
return &Config{
CACert: *caCert,
}, nil
return
}

View file

@ -21,7 +21,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
@ -29,28 +29,28 @@ import (
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -116,7 +116,7 @@ func TestSecretNotFound(t *testing.T) {
data[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{}).Parse(ing)
if err == nil {
if err != nil {
t.Error("Expected secret not found error on ingress")
}
}
@ -132,7 +132,24 @@ func TestSecretOnNonSecure(t *testing.T) {
"default/secure-verify-ca": {},
},
}).Parse(ing)
if err == nil {
if err != nil {
t.Error("Expected CA secret on non secure backend error on ingress")
}
}
func TestUnsupportedAnnotation(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[parser.GetAnnotationWithPrefix("backend-protocol")] = "HTTPS"
data[parser.GetAnnotationWithPrefix("secure-verify-ca-secret")] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{
certs: map[string]resolver.AuthSSLCert{
"default/secure-verify-ca": {},
},
}).Parse(ing)
if err != nil {
t.Errorf("Unexpected error on ingress: %v", err)
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
package serversnippet
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -35,6 +35,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a serverSnippet) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a serverSnippet) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("server-snippet", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package serviceupstream
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -32,6 +32,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
return serviceUpstream{r}
}
func (s serviceUpstream) Parse(ing *extensions.Ingress) (interface{}, error) {
func (s serviceUpstream) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation("service-upstream", ing)
}

View file

@ -20,35 +20,35 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,

View file

@ -19,7 +19,7 @@ package sessionaffinity
import (
"regexp"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
@ -28,6 +28,7 @@ import (
const (
annotationAffinityType = "affinity"
annotationAffinityMode = "affinity-mode"
// If a cookie with this name exists,
// its value is used as an index into the list of available backends.
annotationAffinityCookieName = "session-cookie-name"
@ -44,6 +45,15 @@ const (
// This is used to control the cookie path when use-regex is set to true
annotationAffinityCookiePath = "session-cookie-path"
// This is used to control the SameSite attribute of the cookie
annotationAffinityCookieSameSite = "session-cookie-samesite"
// This is used to control whether SameSite=None should be conditionally applied based on the User-Agent
annotationAffinityCookieConditionalSameSiteNone = "session-cookie-conditional-samesite-none"
// This is used to control the cookie change after request failure
annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
)
var (
@ -54,6 +64,8 @@ var (
type Config struct {
// The type of affinity that will be used
Type string `json:"type"`
// The affinity mode, i.e. how sticky a session is
Mode string `json:"mode"`
Cookie
}
@ -67,11 +79,17 @@ type Cookie struct {
MaxAge string `json:"maxage"`
// The path that a cookie will be set on
Path string `json:"path"`
// Flag that allows cookie regeneration on request failure
ChangeOnFailure bool `json:"changeonfailure"`
// SameSite attribute value
SameSite string `json:"samesite"`
// Flag that conditionally applies SameSite=None attribute on cookie if user agent accepts it.
ConditionalSameSiteNone bool `json:"conditional-samesite-none"`
}
// cookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
func (a affinity) cookieAffinityParse(ing *extensions.Ingress) *Cookie {
func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
var err error
cookie := &Cookie{}
@ -99,6 +117,26 @@ func (a affinity) cookieAffinityParse(ing *extensions.Ingress) *Cookie {
klog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieMaxAge)
}
cookie.SameSite, err = parser.GetStringAnnotation(annotationAffinityCookieSameSite, ing)
if err != nil {
klog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieSameSite)
}
cookie.ConditionalSameSiteNone, err = parser.GetBoolAnnotation(annotationAffinityCookieConditionalSameSiteNone, ing)
if err != nil {
klog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieConditionalSameSiteNone)
}
cookie.ChangeOnFailure, err = parser.GetBoolAnnotation(annotationAffinityCookieChangeOnFailure, ing)
if err != nil {
klog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieChangeOnFailure)
}
cookie.ChangeOnFailure, err = parser.GetBoolAnnotation(annotationAffinityCookieChangeOnFailure, ing)
if err != nil {
klog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Ignoring it", ing.Name, annotationAffinityCookieChangeOnFailure)
}
return cookie
}
@ -113,7 +151,7 @@ type affinity struct {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
cookie := &Cookie{}
// Check the type of affinity that will be used
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
@ -121,6 +159,12 @@ func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
at = ""
}
// Check the afinity mode that will be used
am, err := parser.GetStringAnnotation(annotationAffinityMode, ing)
if err != nil {
am = ""
}
switch at {
case "cookie":
cookie = a.cookieAffinityParse(ing)
@ -131,6 +175,7 @@ func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
return &Config{
Type: at,
Mode: am,
Cookie: *cookie,
}, nil
}

View file

@ -20,35 +20,35 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
func buildIngress() *networking.Ingress {
defaultBackend := networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
Rules: []networking.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
@ -67,10 +67,12 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
data := map[string]string{}
data[parser.GetAnnotationWithPrefix(annotationAffinityType)] = "cookie"
data[parser.GetAnnotationWithPrefix(annotationAffinityMode)] = "balanced"
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieName)] = "INGRESSCOOKIE"
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieExpires)] = "4500"
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieMaxAge)] = "3000"
data[parser.GetAnnotationWithPrefix(annotationAffinityCookiePath)] = "/foo"
data[parser.GetAnnotationWithPrefix(annotationAffinityCookieChangeOnFailure)] = "true"
ing.SetAnnotations(data)
affin, _ := NewParser(&resolver.Mock{}).Parse(ing)
@ -83,8 +85,12 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
t.Errorf("expected cookie as affinity but returned %v", nginxAffinity.Type)
}
if nginxAffinity.Mode != "balanced" {
t.Errorf("expected balanced as affinity mode but returned %v", nginxAffinity.Mode)
}
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" {
@ -98,4 +104,12 @@ func TestIngressAffinityCookieConfig(t *testing.T) {
if nginxAffinity.Cookie.Path != "/foo" {
t.Errorf("expected /foo as session-cookie-path but returned %v", nginxAffinity.Cookie.Path)
}
if !nginxAffinity.Cookie.ChangeOnFailure {
t.Errorf("expected change of failure parameter set to true but returned %v", nginxAffinity.Cookie.ChangeOnFailure)
}
if !nginxAffinity.Cookie.ChangeOnFailure {
t.Errorf("expected change of failure parameter set to true but returned %v", nginxAffinity.Cookie.ChangeOnFailure)
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
package snippet
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -35,6 +35,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a snippet) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a snippet) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("configuration-snippet", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package sslcipher
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,6 +34,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to add ssl-ciphers to the server name
func (sc sslCipher) Parse(ing *extensions.Ingress) (interface{}, error) {
func (sc sslCipher) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("ssl-ciphers", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -45,12 +45,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package sslpassthrough
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/internal/ingress/errors"
@ -35,7 +35,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if is required to configure
func (a sslpt) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a sslpt) Parse(ing *networking.Ingress) (interface{}, error) {
if ing.GetAnnotations() == nil {
return false, ing_errors.ErrMissingAnnotations
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -28,14 +28,14 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
func buildIngress() *networking.Ingress {
return &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
Spec: networking.IngressSpec{
Backend: &networking.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
@ -61,7 +61,7 @@ func TestParseAnnotations(t *testing.T) {
}
// test with a valid host
ing.Spec.TLS = []extensions.IngressTLS{
ing.Spec.TLS = []networking.IngressTLS{
{
Hosts: []string{"foo.bar.com"},
},

View file

@ -17,7 +17,7 @@ limitations under the License.
package upstreamhashby
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -40,7 +40,7 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
}
// Parse parses the annotations contained in the ingress rule
func (a upstreamhashby) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a upstreamhashby) Parse(ing *networking.Ingress) (interface{}, error) {
upstreamHashBy, _ := parser.GetStringAnnotation("upstream-hash-by", ing)
upstreamHashBySubset, _ := parser.GetBoolAnnotation("upstream-hash-by-subset", ing)
upstreamHashbySubsetSize, _ := parser.GetIntAnnotation("upstream-hash-by-subset-size", ing)

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -17,7 +17,7 @@ limitations under the License.
package upstreamvhost
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -35,6 +35,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a upstreamVhost) Parse(ing *extensions.Ingress) (interface{}, error) {
func (a upstreamVhost) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("upstream-vhost", ing)
}

View file

@ -20,19 +20,19 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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) {
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
data := map[string]string{}

View file

@ -17,7 +17,7 @@ limitations under the License.
package xforwardedprefix
import (
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/resolver"
@ -34,6 +34,6 @@ func NewParser(r resolver.Resolver) parser.IngressAnnotation {
// Parse parses the annotations contained in the ingress rule
// used to add an x-forwarded-prefix header to the request
func (cbbs xforwardedprefix) Parse(ing *extensions.Ingress) (interface{}, error) {
func (cbbs xforwardedprefix) Parse(ing *networking.Ingress) (interface{}, error) {
return parser.GetStringAnnotation("x-forwarded-prefix", ing)
}

View file

@ -20,7 +20,7 @@ import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
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"
@ -44,12 +44,12 @@ func TestParse(t *testing.T) {
{nil, ""},
}
ing := &extensions.Ingress{
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
Spec: networking.IngressSpec{},
}
for _, testCase := range testCases {

View file

@ -18,13 +18,13 @@ package controller
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"github.com/ncabatoff/process-exporter/proc"
"github.com/pkg/errors"
"k8s.io/klog"
"k8s.io/ingress-nginx/internal/nginx"
)
@ -36,41 +36,48 @@ func (n NGINXController) Name() string {
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
func (n *NGINXController) Check(_ *http.Request) error {
statusCode, _, err := nginx.NewGetStatusRequest(nginx.HealthPath)
if err != nil {
klog.Errorf("healthcheck error: %v", err)
return err
}
if statusCode != 200 {
klog.Errorf("healthcheck error: %v", statusCode)
return fmt.Errorf("ingress controller is not healthy")
}
statusCode, _, err = nginx.NewGetStatusRequest("/is-dynamic-lb-initialized")
if err != nil {
klog.Errorf("healthcheck error: %v", err)
return err
}
if statusCode != 200 {
klog.Errorf("healthcheck error: %v", statusCode)
return fmt.Errorf("dynamic load balancer not started")
if n.isShuttingDown {
return fmt.Errorf("the ingress controller is shutting down")
}
// check the nginx master process is running
fs, err := proc.NewFS("/proc", false)
if err != nil {
return errors.Wrap(err, "unexpected error reading /proc directory")
return errors.Wrap(err, "reading /proc directory")
}
f, err := n.fileSystem.ReadFile(nginx.PID)
f, err := ioutil.ReadFile(nginx.PID)
if err != nil {
return errors.Wrapf(err, "unexpected error reading %v", nginx.PID)
return errors.Wrapf(err, "reading %v", nginx.PID)
}
pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
if err != nil {
return errors.Wrapf(err, "unexpected error reading the nginx PID from %v", nginx.PID)
return errors.Wrapf(err, "reading NGINX PID from file %v", nginx.PID)
}
_, err = fs.NewProc(pid)
return err
if err != nil {
return errors.Wrapf(err, "checking for NGINX process with PID %v", pid)
}
statusCode, _, err := nginx.NewGetStatusRequest(nginx.HealthPath)
if err != nil {
return errors.Wrapf(err, "checking if NGINX is running")
}
if statusCode != 200 {
return fmt.Errorf("ingress controller is not healthy (%v)", statusCode)
}
statusCode, _, err = nginx.NewGetStatusRequest("/is-dynamic-lb-initialized")
if err != nil {
return errors.Wrapf(err, "checking if the dynamic load balancer started")
}
if statusCode != 200 {
return fmt.Errorf("dynamic load balancer not started")
}
return nil
}

View file

@ -26,7 +26,6 @@ import (
"testing"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/kubernetes/pkg/util/filesystem"
"k8s.io/ingress-nginx/internal/file"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
@ -34,89 +33,97 @@ import (
)
func TestNginxCheck(t *testing.T) {
mux := http.NewServeMux()
listener, err := net.Listen("unix", nginx.StatusSocket)
if err != nil {
t.Errorf("crating unix listener: %s", err)
}
defer listener.Close()
defer os.Remove(nginx.StatusSocket)
server := &httptest.Server{
Listener: listener,
Config: &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "ok")
}),
},
}
defer server.Close()
server.Start()
// mock filesystem
fs := filesystem.DefaultFs{}
n := &NGINXController{
cfg: &Configuration{
ListenPorts: &ngx_config.ListenPorts{},
},
fileSystem: fs,
var tests = []struct {
healthzPath string
}{
{"/healthz"},
{"/not-healthz"},
}
t.Run("no pid or process", func(t *testing.T) {
if err := callHealthz(true, mux); err == nil {
t.Error("expected an error but none returned")
}
})
for _, tt := range tests {
testName := fmt.Sprintf("health path: %s", tt.healthzPath)
t.Run(testName, func(t *testing.T) {
// create pid file
fs.MkdirAll("/tmp", file.ReadWriteByUser)
pidFile, err := fs.Create(nginx.PID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
mux := http.NewServeMux()
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", nginx.StatusPort))
if err != nil {
t.Fatalf("creating tcp listener: %s", err)
}
defer listener.Close()
server := &httptest.Server{
Listener: listener,
Config: &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "ok")
}),
},
}
defer server.Close()
server.Start()
n := &NGINXController{
cfg: &Configuration{
ListenPorts: &ngx_config.ListenPorts{},
},
}
t.Run("no pid or process", func(t *testing.T) {
if err := callHealthz(true, tt.healthzPath, mux); err == nil {
t.Error("expected an error but none returned")
}
})
// create pid file
os.MkdirAll("/tmp", file.ReadWriteByUser)
pidFile, err := os.Create(nginx.PID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Run("no process", func(t *testing.T) {
if err := callHealthz(true, tt.healthzPath, mux); err == nil {
t.Error("expected an error but none returned")
}
})
// start dummy process to use the PID
cmd := exec.Command("sleep", "3600")
cmd.Start()
pid := cmd.Process.Pid
defer cmd.Process.Kill()
go func() {
cmd.Wait()
}()
pidFile.Write([]byte(fmt.Sprintf("%v", pid)))
pidFile.Close()
healthz.InstallPathHandler(mux, tt.healthzPath, n)
t.Run("valid request", func(t *testing.T) {
if err := callHealthz(false, tt.healthzPath, mux); err != nil {
t.Error(err)
}
})
// pollute pid file
pidFile.Write([]byte(fmt.Sprint("999999")))
pidFile.Close()
t.Run("bad pid", func(t *testing.T) {
if err := callHealthz(true, tt.healthzPath, mux); err == nil {
t.Error("expected an error but none returned")
}
})
})
}
t.Run("no process", func(t *testing.T) {
if err := callHealthz(true, mux); err == nil {
t.Error("expected an error but none returned")
}
})
// start dummy process to use the PID
cmd := exec.Command("sleep", "3600")
cmd.Start()
pid := cmd.Process.Pid
defer cmd.Process.Kill()
go func() {
cmd.Wait()
}()
pidFile.Write([]byte(fmt.Sprintf("%v", pid)))
pidFile.Close()
healthz.InstallHandler(mux, n)
t.Run("valid request", func(t *testing.T) {
if err := callHealthz(false, mux); err != nil {
t.Error(err)
}
})
// pollute pid file
pidFile.Write([]byte(fmt.Sprint("999999")))
pidFile.Close()
t.Run("bad pid", func(t *testing.T) {
if err := callHealthz(true, mux); err == nil {
t.Error("expected an error but none returned")
}
})
}
func callHealthz(expErr bool, mux *http.ServeMux) error {
req, err := http.NewRequest("GET", "/healthz", nil)
func callHealthz(expErr bool, healthzPath string, mux *http.ServeMux) error {
req, err := http.NewRequest("GET", healthzPath, nil)
if err != nil {
return fmt.Errorf("healthz error: %v", err)
}

175
internal/ingress/controller/config/config.go Executable file → Normal file
View file

@ -17,7 +17,6 @@ limitations under the License.
package config
import (
"fmt"
"strconv"
"time"
@ -30,6 +29,11 @@ import (
"k8s.io/ingress-nginx/internal/runtime"
)
var (
// EnableSSLChainCompletion Autocomplete SSL certificate chains with missing intermediate CA certificates.
EnableSSLChainCompletion = false
)
const (
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
// Sets the maximum allowed size of the client request body
@ -46,13 +50,13 @@ const (
// max-age is the time, in seconds, that the browser should remember that this site is only to be accessed using HTTPS.
hstsMaxAge = "15724800"
gzipTypes = "application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
gzipTypes = "application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/x-component"
brotliTypes = "application/xml+rss application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
brotliTypes = "application/xml+rss application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/x-component"
logFormatUpstream = `%v - [$the_real_ip] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id`
logFormatUpstream = `$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $req_id`
logFormatStream = `[$time_local] $protocol $status $bytes_sent $bytes_received $session_time`
logFormatStream = `[$remote_addr] [$time_local] $protocol $status $bytes_sent $bytes_received $session_time`
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
// Sets the size of the buffer used for sending data.
@ -62,12 +66,16 @@ const (
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by the OpenSSL library
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
sslCiphers = "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
sslCiphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
// SSL enabled protocols to use
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
sslProtocols = "TLSv1.2"
// Disable TLS 1.3 early data
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
sslEarlyData = false
// Time during which a client may reuse the session parameters stored in a cache.
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
sslSessionTimeout = "10m"
@ -117,11 +125,6 @@ type Configuration struct {
// By default error logs go to /var/log/nginx/error.log
ErrorLogPath string `json:"error-log-path,omitempty"`
// EnableDynamicTLSRecords enables dynamic TLS record sizes
// https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency
// By default this is enabled
EnableDynamicTLSRecords bool `json:"enable-dynamic-tls-records"`
// EnableModsecurity enables the modsecurity module for NGINX
// By default this is disabled
EnableModsecurity bool `json:"enable-modsecurity"`
@ -130,6 +133,9 @@ type Configuration struct {
// By default this is disabled
EnableOWASPCoreRules bool `json:"enable-owasp-modsecurity-crs"`
// ModSecuritySnippet adds custom rules to modsecurity section of nginx configuration
ModsecuritySnippet string `json:"modsecurity-snippet"`
// ClientHeaderBufferSize allows to configure a custom buffer
// size for reading client request header
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_buffer_size
@ -190,6 +196,10 @@ type Configuration struct {
// and the need of establishing a new connection.
HTTP2MaxRequests int `json:"http2-max-requests,omitempty"`
// http://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_concurrent_streams
// Sets the maximum number of concurrent HTTP/2 streams in a connection.
HTTP2MaxConcurrentStreams int `json:"http2-max-concurrent-streams,omitempty"`
// Enables or disables the header HSTS in servers running SSL
HSTS bool `json:"hsts,omitempty"`
@ -310,6 +320,10 @@ type Configuration struct {
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
SSLProtocols string `json:"ssl-protocols,omitempty"`
// Enables or disable TLS 1.3 early data.
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
SSLEarlyData bool `json:"ssl-early-data,omitempty"`
// Enables or disables the use of shared SSL cache among worker processes.
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
SSLSessionCache bool `json:"ssl-session-cache,omitempty"`
@ -379,6 +393,10 @@ type Configuration struct {
// gzip Compression Level that will be used
GzipLevel int `json:"gzip-level,omitempty"`
// Minimum length of responses to be sent to the client before it is eligible
// for gzip compression, in bytes.
GzipMinLength int `json:"gzip-min-length,omitempty"`
// MIME types in addition to "text/html" to compress. The special value “*” matches any MIME type.
// Responses with the “text/html” type are always compressed if UseGzip is enabled
GzipTypes string `json:"gzip-types,omitempty"`
@ -430,6 +448,10 @@ type Configuration struct {
// Default: 1
ProxyStreamResponses int `json:"proxy-stream-responses,omitempty"`
// Modifies the HTTP version the proxy uses to interact with the backend.
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_http_version
ProxyHTTPVersion string `json:"proxy-http-version"`
// Sets the ipv4 addresses on which the server will accept requests.
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
@ -503,6 +525,22 @@ type Configuration struct {
// Default: 5778
JaegerSamplerPort int `json:"jaeger-sampler-port"`
// JaegerTraceContextHeaderName specifies the header name used for passing trace context
// Default: uber-trace-id
JaegerTraceContextHeaderName string `json:"jaeger-trace-context-header-name"`
// JaegerDebugHeader specifies the header name used for force sampling
// Default: jaeger-debug-id
JaegerDebugHeader string `json:"jaeger-debug-header"`
// JaegerBaggageHeader specifies the header name used to submit baggage if there is no root span
// Default: jaeger-baggage
JaegerBaggageHeader string `json:"jaeger-baggage-header"`
// TraceBaggageHeaderPrefix specifies the header prefix used to propagate baggage
// Default: uberctx-
JaegerTraceBaggageHeaderPrefix string `json:"jaeger-tracer-baggage-header-prefix"`
// DatadogCollectorHost specifies the datadog agent host to use when uploading traces
DatadogCollectorHost string `json:"datadog-collector-host"`
@ -518,6 +556,17 @@ type Configuration struct {
// Default: nginx.handle
DatadogOperationNameOverride string `json:"datadog-operation-name-override"`
// DatadogPrioritySampling specifies to use client-side sampling
// If true disables client-side sampling (thus ignoring sample_rate) and enables distributed
// priority sampling, where traces are sampled based on a combination of user-assigned
// Default: true
DatadogPrioritySampling bool `json:"datadog-priority-sampling"`
// DatadogSampleRate specifies sample rate for any traces created.
// This is effective only when datadog-priority-sampling is false
// Default: 1.0
DatadogSampleRate float32 `json:"datadog-sample-rate"`
// MainSnippet adds custom configuration to the main section of the nginx configuration
MainSnippet string `json:"main-snippet"`
@ -576,10 +625,6 @@ type Configuration struct {
// +optional
GlobalExternalAuth GlobalExternalAuth `json:"global-external-auth"`
// DisableLuaRestyWAF disables lua-resty-waf globally regardless
// of whether there's an ingress that has enabled the WAF using annotation
DisableLuaRestyWAF bool `json:"disable-lua-resty-waf"`
// EnableInfluxDB enables the nginx InfluxDB extension
// http://github.com/influxdata/nginx-influxdb-module/
// By default this is disabled
@ -596,6 +641,18 @@ type Configuration struct {
// Block all requests with given Referer headers
BlockReferers []string `json:"block-referers"`
// Lua shared dict configuration data / certificate data
LuaSharedDicts map[string]int `json:"lua-shared-dicts"`
// DefaultSSLCertificate holds the default SSL certificate to use in the configuration
// It can be the fake certificate or the one behind the flag --default-ssl-certificate
DefaultSSLCertificate *ingress.SSLCert `json:"-"`
// ProxySSLLocationOnly controls whether the proxy-ssl parameters defined in the
// proxy-ssl-* annotations are applied on on location level only in the nginx.conf file
// Default is that those are applied on server level, too
ProxySSLLocationOnly bool `json:"proxy-ssl-location-only"`
}
// NewDefault returns the default nginx configuration
@ -611,7 +668,7 @@ func NewDefault() Configuration {
defNginxStatusIpv4Whitelist = append(defNginxStatusIpv4Whitelist, "127.0.0.1")
defNginxStatusIpv6Whitelist = append(defNginxStatusIpv6Whitelist, "::1")
defProxyDeadlineDuration := time.Duration(5) * time.Second
defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", append(defResponseHeaders, ""), "", ""}
defGlobalExternalAuth := GlobalExternalAuth{"", "", "", "", append(defResponseHeaders, ""), "", "", "", []string{}, map[string]string{}}
cfg := Configuration{
AllowBackendServerHeader: false,
@ -629,17 +686,17 @@ func NewDefault() Configuration {
ClientHeaderTimeout: 60,
ClientBodyBufferSize: "8k",
ClientBodyTimeout: 60,
EnableDynamicTLSRecords: true,
EnableUnderscoresInHeaders: false,
ErrorLogLevel: errorLevel,
UseForwardedHeaders: false,
ForwardedForHeader: "X-Forwarded-For",
ComputeFullForwardedFor: false,
ProxyAddOriginalURIHeader: true,
ProxyAddOriginalURIHeader: false,
GenerateRequestID: true,
HTTP2MaxFieldSize: "4k",
HTTP2MaxHeaderSize: "16k",
HTTP2MaxRequests: 1000,
HTTP2MaxConcurrentStreams: 128,
HTTPRedirectCode: 308,
HSTS: true,
HSTSIncludeSubdomains: true,
@ -647,6 +704,7 @@ func NewDefault() Configuration {
HSTSPreload: false,
IgnoreInvalidHeaders: true,
GzipLevel: 5,
GzipMinLength: 256,
GzipTypes: gzipTypes,
KeepAlive: 75,
KeepAliveRequests: 100,
@ -672,6 +730,7 @@ func NewDefault() Configuration {
SSLCiphers: sslCiphers,
SSLECDHCurve: "auto",
SSLProtocols: sslProtocols,
SSLEarlyData: sslEarlyData,
SSLSessionCache: true,
SSLSessionCacheSize: sslSessionCacheSize,
SSLSessionTickets: true,
@ -681,8 +740,8 @@ func NewDefault() Configuration {
UseGeoIP: true,
UseGeoIP2: false,
WorkerProcesses: strconv.Itoa(runtime.NumCPU()),
WorkerShutdownTimeout: "10s",
VariablesHashBucketSize: 128,
WorkerShutdownTimeout: "240s",
VariablesHashBucketSize: 256,
VariablesHashMaxSize: 2048,
UseHTTP2: true,
ProxyStreamTimeout: "600s",
@ -708,6 +767,8 @@ func NewDefault() Configuration {
LimitRate: 0,
LimitRateAfter: 0,
ProxyBuffering: "off",
ProxyHTTPVersion: "1.1",
ProxyMaxTempFileSize: "1024m",
},
UpstreamKeepaliveConnections: 32,
UpstreamKeepaliveTimeout: 60,
@ -727,12 +788,15 @@ func NewDefault() Configuration {
DatadogServiceName: "nginx",
DatadogCollectorPort: 8126,
DatadogOperationNameOverride: "nginx.handle",
DatadogSampleRate: 1.0,
DatadogPrioritySampling: true,
LimitReqStatusCode: 503,
LimitConnStatusCode: 503,
SyslogPort: 514,
NoTLSRedirectLocations: "/.well-known/acme-challenge",
NoAuthLocations: "/.well-known/acme-challenge",
GlobalExternalAuth: defGlobalExternalAuth,
ProxySSLLocationOnly: false,
}
if klog.V(5) {
@ -742,43 +806,31 @@ func NewDefault() Configuration {
return cfg
}
// BuildLogFormatUpstream format the log_format upstream using
// proxy_protocol_addr as remote client address if UseProxyProtocol
// is enabled.
func (cfg Configuration) BuildLogFormatUpstream() string {
if cfg.LogFormatUpstream == logFormatUpstream {
return fmt.Sprintf(cfg.LogFormatUpstream, "$the_real_ip")
}
return cfg.LogFormatUpstream
}
// TemplateConfig contains the nginx configuration to render the file nginx.conf
type TemplateConfig struct {
ProxySetHeaders map[string]string
AddHeaders map[string]string
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
Cfg Configuration
IsIPV6Enabled bool
IsSSLPassthroughEnabled bool
NginxStatusIpv4Whitelist []string
NginxStatusIpv6Whitelist []string
RedirectServers interface{}
ListenPorts *ListenPorts
PublishService *apiv1.Service
DynamicCertificatesEnabled bool
EnableMetrics bool
ProxySetHeaders map[string]string
AddHeaders map[string]string
BacklogSize int
Backends []*ingress.Backend
PassthroughBackends []*ingress.SSLPassthroughBackend
Servers []*ingress.Server
TCPBackends []ingress.L4Service
UDPBackends []ingress.L4Service
HealthzURI string
Cfg Configuration
IsIPV6Enabled bool
IsSSLPassthroughEnabled bool
NginxStatusIpv4Whitelist []string
NginxStatusIpv6Whitelist []string
RedirectServers interface{}
ListenPorts *ListenPorts
PublishService *apiv1.Service
EnableMetrics bool
PID string
StatusSocket string
StatusPath string
StreamSocket string
PID string
StatusPath string
StatusPort int
StreamPort int
}
// ListenPorts describe the ports required to run the
@ -796,10 +848,13 @@ type ListenPorts struct {
type GlobalExternalAuth struct {
URL string `json:"url"`
// Host contains the hostname defined in the URL
Host string `json:"host"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"`
ResponseHeaders []string `json:"responseHeaders,omitempty"`
RequestRedirect string `json:"requestRedirect"`
AuthSnippet string `json:"authSnippet"`
Host string `json:"host"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"`
ResponseHeaders []string `json:"responseHeaders,omitempty"`
RequestRedirect string `json:"requestRedirect"`
AuthSnippet string `json:"authSnippet"`
AuthCacheKey string `json:"authCacheKey"`
AuthCacheDuration []string `json:"authCacheDuration"`
ProxySetHeaders map[string]string `json:"proxySetHeaders,omitempty"`
}

View file

@ -1,46 +0,0 @@
/*
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.
*/
package config
import (
"fmt"
"testing"
)
func TestBuildLogFormatUpstream(t *testing.T) {
testCases := []struct {
useProxyProtocol bool // use proxy protocol
curLogFormat string
expected string
}{
{true, logFormatUpstream, fmt.Sprintf(logFormatUpstream, "$the_real_ip")},
{false, logFormatUpstream, fmt.Sprintf(logFormatUpstream, "$the_real_ip")},
{true, "my-log-format", "my-log-format"},
{false, "john-log-format", "john-log-format"},
}
for _, testCase := range testCases {
cfg := NewDefault()
cfg.UseProxyProtocol = testCase.useProxyProtocol
cfg.LogFormatUpstream = testCase.curLogFormat
result := cfg.BuildLogFormatUpstream()
if result != testCase.expected {
t.Errorf(" expected %v but return %v", testCase.expected, result)
}
}
}

395
internal/ingress/controller/controller.go Executable file → Normal file
View file

@ -25,7 +25,7 @@ import (
"github.com/mitchellh/hashstructure"
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
networking "k8s.io/api/networking/v1beta1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
@ -37,6 +37,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/internal/nginx"
"k8s.io/klog"
)
@ -48,9 +49,12 @@ const (
// Configuration contains all the settings required by an Ingress controller
type Configuration struct {
APIServerHost string
APIServerHost string
RootCAFile string
KubeConfigFile string
Client clientset.Interface
Client clientset.Interface
ResyncPeriod time.Duration
@ -64,7 +68,6 @@ type Configuration struct {
// +optional
UDPConfigMapName string
HealthCheckTimeout time.Duration
DefaultSSLCertificate string
// +optional
@ -85,14 +88,10 @@ type Configuration struct {
EnableMetrics bool
MetricsPerHost bool
EnableSSLChainCompletion bool
FakeCertificate *ingress.SSLCert
SyncRateLimit float32
DynamicCertificatesEnabled bool
DisableCatchAll bool
ValidationWebhook string
@ -125,11 +124,15 @@ func (n *NGINXController) syncIngress(interface{}) error {
ings := n.store.ListIngresses(nil)
hosts, servers, pcfg := n.getConfiguration(ings)
n.metricCollector.SetSSLExpireTime(servers)
if n.runningConfig.Equal(pcfg) {
klog.V(3).Infof("No configuration change detected, skipping backend reload.")
return nil
}
n.metricCollector.SetHosts(hosts)
if !n.IsDynamicConfigurationEnough(pcfg) {
klog.Infof("Configuration changes detected, backend reload required.")
@ -147,16 +150,9 @@ func (n *NGINXController) syncIngress(interface{}) error {
return err
}
n.metricCollector.SetHosts(hosts)
klog.Infof("Backend successfully reloaded.")
n.metricCollector.ConfigSuccess(hash, true)
n.metricCollector.IncReloadCount()
if n.isLeader() {
klog.V(2).Infof("Updating ssl expiration metrics.")
n.metricCollector.SetSSLExpireTime(servers)
}
}
isFirstSync := n.runningConfig.Equal(&ingress.Configuration{})
@ -175,7 +171,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
}
err := wait.ExponentialBackoff(retry, func() (bool, error) {
err := configureDynamically(pcfg, n.cfg.DynamicCertificatesEnabled)
err := n.configureDynamically(pcfg)
if err == nil {
klog.V(2).Infof("Dynamic reconfiguration succeeded.")
return true, nil
@ -200,7 +196,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
// CheckIngress returns an error in case the provided ingress, when added
// to the current configuration, generates an invalid configuration
func (n *NGINXController) CheckIngress(ing *extensions.Ingress) error {
func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
//TODO: this is wrong
if n == nil {
return fmt.Errorf("cannot check ingress on a nil ingress controller")
@ -260,23 +256,29 @@ func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Pr
klog.V(3).Infof("Obtaining information about %v stream services from ConfigMap %q", proto, configmapName)
_, _, err := k8s.ParseNameNS(configmapName)
if err != nil {
klog.Errorf("Error parsing ConfigMap reference %q: %v", configmapName, err)
klog.Warningf("Error parsing ConfigMap reference %q: %v", configmapName, err)
return []ingress.L4Service{}
}
configmap, err := n.store.GetConfigMap(configmapName)
if err != nil {
klog.Errorf("Error getting ConfigMap %q: %v", configmapName, err)
klog.Warningf("Error getting ConfigMap %q: %v", configmapName, err)
return []ingress.L4Service{}
}
var svcs []ingress.L4Service
var svcProxyProtocol ingress.ProxyProtocol
rp := []int{
n.cfg.ListenPorts.HTTP,
n.cfg.ListenPorts.HTTPS,
n.cfg.ListenPorts.SSLProxy,
n.cfg.ListenPorts.Health,
n.cfg.ListenPorts.Default,
nginx.ProfilerPort,
nginx.StatusPort,
nginx.StreamPort,
}
reserverdPorts := sets.NewInt(rp...)
// svcRef format: <(str)namespace>/<(str)service>:<(intstr)port>[:<("PROXY")decode>:<("PROXY")encode>]
for port, svcRef := range configmap.Data {
@ -409,8 +411,11 @@ func (n *NGINXController) getConfiguration(ingresses []*ingress.Ingress) (sets.S
if !hosts.Has(server.Hostname) {
hosts.Insert(server.Hostname)
}
if server.Alias != "" && !hosts.Has(server.Alias) {
hosts.Insert(server.Alias)
for _, alias := range server.Aliases {
if !hosts.Has(alias) {
hosts.Insert(alias)
}
}
if !server.SSLPassthrough {
@ -489,6 +494,19 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
server.Hostname, ingKey)
}
if !n.store.GetBackendConfiguration().ProxySSLLocationOnly {
if server.ProxySSL.CAFileName == "" {
server.ProxySSL = anns.ProxySSL
if server.ProxySSL.Secret != "" && server.ProxySSL.CAFileName == "" {
klog.V(3).Infof("Secret %q has no 'ca.crt' key, client cert authentication disabled for Ingress %q",
server.ProxySSL.Secret, ingKey)
}
} else {
klog.V(3).Infof("Server %q is already configured for client cert authentication (Ingress %q)",
server.Hostname, ingKey)
}
}
if rule.HTTP == nil {
klog.V(3).Infof("Ingress %q does not contain any HTTP rule, using default backend", ingKey)
continue
@ -562,6 +580,10 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
ups.SessionAffinity.AffinityType = anns.SessionAffinity.Type
}
if ups.SessionAffinity.AffinityMode == "" {
ups.SessionAffinity.AffinityMode = anns.SessionAffinity.Mode
}
if anns.SessionAffinity.Type == "cookie" {
cookiePath := anns.SessionAffinity.Cookie.Path
if anns.Rewrite.UseRegex && cookiePath == "" {
@ -572,6 +594,9 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
ups.SessionAffinity.CookieSessionAffinity.Expires = anns.SessionAffinity.Cookie.Expires
ups.SessionAffinity.CookieSessionAffinity.MaxAge = anns.SessionAffinity.Cookie.MaxAge
ups.SessionAffinity.CookieSessionAffinity.Path = cookiePath
ups.SessionAffinity.CookieSessionAffinity.SameSite = anns.SessionAffinity.Cookie.SameSite
ups.SessionAffinity.CookieSessionAffinity.ConditionalSameSiteNone = anns.SessionAffinity.Cookie.ConditionalSameSiteNone
ups.SessionAffinity.CookieSessionAffinity.ChangeOnFailure = anns.SessionAffinity.Cookie.ChangeOnFailure
locs := ups.SessionAffinity.CookieSessionAffinity.Locations
if _, ok := locs[host]; !ok {
@ -599,42 +624,47 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
for _, upstream := range upstreams {
aUpstreams = append(aUpstreams, upstream)
if upstream.Name == defUpstreamName {
continue
}
isHTTPSfrom := []*ingress.Server{}
for _, server := range servers {
for _, location := range server.Locations {
if shouldCreateUpstreamForLocationDefaultBackend(upstream, location) {
sp := location.DefaultBackend.Spec.Ports[0]
endps := getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
if len(endps) > 0 {
// use default backend
if !shouldCreateUpstreamForLocationDefaultBackend(upstream, location) {
continue
}
name := fmt.Sprintf("custom-default-backend-%v", location.DefaultBackend.GetName())
klog.V(3).Infof("Creating \"%v\" upstream based on default backend annotation", name)
sp := location.DefaultBackend.Spec.Ports[0]
endps := getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
// custom backend is valid only if contains at least one endpoint
if len(endps) > 0 {
name := fmt.Sprintf("custom-default-backend-%v", location.DefaultBackend.GetName())
klog.V(3).Infof("Creating \"%v\" upstream based on default backend annotation", name)
nb := upstream.DeepCopy()
nb.Name = name
nb.Endpoints = endps
aUpstreams = append(aUpstreams, nb)
location.DefaultBackendUpstreamName = name
nb := upstream.DeepCopy()
nb.Name = name
nb.Endpoints = endps
aUpstreams = append(aUpstreams, nb)
location.DefaultBackendUpstreamName = name
if len(upstream.Endpoints) == 0 {
klog.V(3).Infof("Upstream %q has no active Endpoint, so using custom default backend for location %q in server %q (Service \"%v/%v\")",
upstream.Name, location.Path, server.Hostname, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
if len(upstream.Endpoints) == 0 {
klog.V(3).Infof("Upstream %q has no active Endpoint, so using custom default backend for location %q in server %q (Service \"%v/%v\")",
upstream.Name, location.Path, server.Hostname, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
location.Backend = name
}
location.Backend = name
}
}
if server.SSLPassthrough {
if location.Path == rootLocation {
if location.Backend == defUpstreamName {
klog.Warningf("Server %q has no default backend, ignoring SSL Passthrough.", server.Hostname)
continue
}
isHTTPSfrom = append(isHTTPSfrom, server)
if server.SSLPassthrough {
if location.Path == rootLocation {
if location.Backend == defUpstreamName {
klog.Warningf("Server %q has no default backend, ignoring SSL Passthrough.", server.Hostname)
continue
}
isHTTPSfrom = append(isHTTPSfrom, server)
}
} else {
location.DefaultBackendUpstreamName = "upstream-default-backend"
}
}
}
@ -683,8 +713,6 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
klog.V(3).Infof("Creating upstream %q", defBackend)
upstreams[defBackend] = newUpstream(defBackend)
upstreams[defBackend].SecureCACert = anns.SecureUpstream.CACert
upstreams[defBackend].UpstreamHashBy.UpstreamHashBy = anns.UpstreamHashBy.UpstreamHashBy
upstreams[defBackend].UpstreamHashBy.UpstreamHashBySubset = anns.UpstreamHashBy.UpstreamHashBySubset
upstreams[defBackend].UpstreamHashBy.UpstreamHashBySubsetSize = anns.UpstreamHashBy.UpstreamHashBySubsetSize
@ -710,10 +738,11 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
if anns.Canary.Enabled {
upstreams[defBackend].NoServer = true
upstreams[defBackend].TrafficShapingPolicy = ingress.TrafficShapingPolicy{
Weight: anns.Canary.Weight,
Header: anns.Canary.Header,
HeaderValue: anns.Canary.HeaderValue,
Cookie: anns.Canary.Cookie,
Weight: anns.Canary.Weight,
Header: anns.Canary.Header,
HeaderValue: anns.Canary.HeaderValue,
HeaderPattern: anns.Canary.HeaderPattern,
Cookie: anns.Canary.Cookie,
}
}
@ -748,8 +777,6 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
upstreams[name] = newUpstream(name)
upstreams[name].Port = path.Backend.ServicePort
upstreams[name].SecureCACert = anns.SecureUpstream.CACert
upstreams[name].UpstreamHashBy.UpstreamHashBy = anns.UpstreamHashBy.UpstreamHashBy
upstreams[name].UpstreamHashBy.UpstreamHashBySubset = anns.UpstreamHashBy.UpstreamHashBySubset
upstreams[name].UpstreamHashBy.UpstreamHashBySubsetSize = anns.UpstreamHashBy.UpstreamHashBySubsetSize
@ -775,10 +802,11 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
if anns.Canary.Enabled {
upstreams[name].NoServer = true
upstreams[name].TrafficShapingPolicy = ingress.TrafficShapingPolicy{
Weight: anns.Canary.Weight,
Header: anns.Canary.Header,
HeaderValue: anns.Canary.HeaderValue,
Cookie: anns.Canary.Cookie,
Weight: anns.Canary.Weight,
Header: anns.Canary.Header,
HeaderValue: anns.Canary.HeaderValue,
HeaderPattern: anns.Canary.HeaderPattern,
Cookie: anns.Canary.Cookie,
}
}
@ -807,7 +835,7 @@ func (n *NGINXController) createUpstreams(data []*ingress.Ingress, du *ingress.B
// getServiceClusterEndpoint returns an Endpoint corresponding to the ClusterIP
// field of a Service.
func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *extensions.IngressBackend) (endpoint ingress.Endpoint, err error) {
func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *networking.IngressBackend) (endpoint ingress.Endpoint, err error) {
svc, err := n.store.GetService(svcKey)
if err != nil {
return endpoint, fmt.Errorf("service %q does not exist", svcKey)
@ -842,14 +870,28 @@ func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *exte
// serviceEndpoints returns the upstream servers (Endpoints) associated with a Service.
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingress.Endpoint, error) {
svc, err := n.store.GetService(svcKey)
var upstreams []ingress.Endpoint
svc, err := n.store.GetService(svcKey)
if err != nil {
return upstreams, err
}
klog.V(3).Infof("Obtaining ports information for Service %q", svcKey)
// Ingress with an ExternalName Service and no port defined for that Service
if svc.Spec.Type == apiv1.ServiceTypeExternalName {
servicePort := externalNamePorts(backendPort, svc)
endps := getEndpoints(svc, servicePort, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
if len(endps) == 0 {
klog.Warningf("Service %q does not have any active Endpoint.", svcKey)
return upstreams, nil
}
upstreams = append(upstreams, endps...)
return upstreams, nil
}
for _, servicePort := range svc.Spec.Ports {
// targetPort could be a string, use either the port name or number (int)
if strconv.Itoa(int(servicePort.Port)) == backendPort ||
@ -866,45 +908,21 @@ func (n *NGINXController) serviceEndpoints(svcKey, backendPort string) ([]ingres
}
}
// Ingress with an ExternalName Service and no port defined for that Service
if len(svc.Spec.Ports) == 0 && svc.Spec.Type == apiv1.ServiceTypeExternalName {
externalPort, err := strconv.Atoi(backendPort)
if err != nil {
klog.Warningf("Only numeric ports are allowed in ExternalName Services: %q is not a valid port number.", backendPort)
return upstreams, nil
}
servicePort := apiv1.ServicePort{
Protocol: "TCP",
Port: int32(externalPort),
TargetPort: intstr.FromString(backendPort),
}
endps := getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
if len(endps) == 0 {
klog.Warningf("Service %q does not have any active Endpoint.", svcKey)
return upstreams, nil
}
upstreams = append(upstreams, endps...)
return upstreams, nil
}
return upstreams, nil
}
// overridePemFileNameAndPemSHA should only be called when DynamicCertificatesEnabled
// ideally this function should not exist, the only reason why we use it is that
// we rely on PemFileName in nginx.tmpl to configure SSL directives
// and PemSHA to force reload
func (n *NGINXController) overridePemFileNameAndPemSHA(cert *ingress.SSLCert) {
// TODO(elvinefendi): It is not great but we currently use PemFileName to decide whether SSL needs to be configured
// in nginx configuration or not. The whole thing needs to be refactored, we should rely on a proper
// signal to configure SSL, not PemFileName.
cert.PemFileName = n.cfg.FakeCertificate.PemFileName
func (n *NGINXController) getDefaultSSLCertificate() *ingress.SSLCert {
// read custom default SSL certificate, fall back to generated default certificate
if n.cfg.DefaultSSLCertificate != "" {
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
if err == nil {
return certificate
}
// TODO(elvinefendi): This is again another hacky way of avoiding Nginx reload when certificate
// changes in dynamic SSL mode since FakeCertificate never changes.
cert.PemSHA = n.cfg.FakeCertificate.PemSHA
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
}
return n.cfg.FakeCertificate
}
// createServers builds a map of host name to Server structs from a map of
@ -915,45 +933,32 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
du *ingress.Backend) map[string]*ingress.Server {
servers := make(map[string]*ingress.Server, len(data))
aliases := make(map[string]string, len(data))
allAliases := make(map[string][]string, len(data))
bdef := n.store.GetDefaultBackend()
ngxProxy := proxy.Config{
BodySize: bdef.ProxyBodySize,
ConnectTimeout: bdef.ProxyConnectTimeout,
SendTimeout: bdef.ProxySendTimeout,
ReadTimeout: bdef.ProxyReadTimeout,
BuffersNumber: bdef.ProxyBuffersNumber,
BufferSize: bdef.ProxyBufferSize,
CookieDomain: bdef.ProxyCookieDomain,
CookiePath: bdef.ProxyCookiePath,
NextUpstream: bdef.ProxyNextUpstream,
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
RequestBuffering: bdef.ProxyRequestBuffering,
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
ProxyBuffering: bdef.ProxyBuffering,
}
defaultCertificate := n.cfg.FakeCertificate
// read custom default SSL certificate, fall back to generated default certificate
if n.cfg.DefaultSSLCertificate != "" {
certificate, err := n.store.GetLocalSSLCert(n.cfg.DefaultSSLCertificate)
if err == nil {
defaultCertificate = certificate
if n.cfg.DynamicCertificatesEnabled {
n.overridePemFileNameAndPemSHA(defaultCertificate)
}
} else {
klog.Warningf("Error loading custom default certificate, falling back to generated default:\n%v", err)
}
BodySize: bdef.ProxyBodySize,
ConnectTimeout: bdef.ProxyConnectTimeout,
SendTimeout: bdef.ProxySendTimeout,
ReadTimeout: bdef.ProxyReadTimeout,
BuffersNumber: bdef.ProxyBuffersNumber,
BufferSize: bdef.ProxyBufferSize,
CookieDomain: bdef.ProxyCookieDomain,
CookiePath: bdef.ProxyCookiePath,
NextUpstream: bdef.ProxyNextUpstream,
NextUpstreamTimeout: bdef.ProxyNextUpstreamTimeout,
NextUpstreamTries: bdef.ProxyNextUpstreamTries,
RequestBuffering: bdef.ProxyRequestBuffering,
ProxyRedirectFrom: bdef.ProxyRedirectFrom,
ProxyBuffering: bdef.ProxyBuffering,
ProxyHTTPVersion: bdef.ProxyHTTPVersion,
ProxyMaxTempFileSize: bdef.ProxyMaxTempFileSize,
}
// initialize default server and root location
servers[defServerName] = &ingress.Server{
Hostname: defServerName,
SSLCert: *defaultCertificate,
SSLCert: n.getDefaultSSLCertificate(),
Locations: []*ingress.Location{
{
Path: rootLocation,
@ -1017,6 +1022,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
if host == "" {
host = defServerName
}
if _, ok := servers[host]; ok {
// server already configured
continue
@ -1057,16 +1063,13 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
host = defServerName
}
if anns.Alias != "" {
if servers[host].Alias == "" {
servers[host].Alias = anns.Alias
if _, ok := aliases["Alias"]; !ok {
aliases["Alias"] = host
}
} else {
klog.Warningf("Aliases already configured for server %q, skipping (Ingress %q)",
host, ingKey)
if len(servers[host].Aliases) == 0 {
servers[host].Aliases = anns.Aliases
if _, ok := allAliases[host]; !ok {
allAliases[host] = anns.Aliases
}
} else {
klog.Warningf("Aliases already configured for server %q, skipping (Ingress %q)", host, ingKey)
}
if anns.ServerSnippet != "" {
@ -1084,7 +1087,7 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
}
// only add a certificate if the server does not have one previously configured
if servers[host].SSLCert.PemFileName != "" {
if servers[host].SSLCert != nil {
continue
}
@ -1094,10 +1097,9 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
}
tlsSecretName := extractTLSSecretName(host, ing, n.store.GetLocalSSLCert)
if tlsSecretName == "" {
klog.V(3).Infof("Host %q is listed in the TLS section but secretName is empty. Using default certificate.", host)
servers[host].SSLCert = *defaultCertificate
servers[host].SSLCert = n.getDefaultSSLCertificate()
continue
}
@ -1105,7 +1107,14 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
cert, err := n.store.GetLocalSSLCert(secrKey)
if err != nil {
klog.Warningf("Error getting SSL certificate %q: %v. Using default certificate", secrKey, err)
servers[host].SSLCert = *defaultCertificate
servers[host].SSLCert = n.getDefaultSSLCertificate()
continue
}
if cert.Certificate == nil {
klog.Warningf("SSL certificate %q does not contain a valid SSL certificate for server %q", secrKey, host)
klog.Warningf("Using default certificate")
servers[host].SSLCert = n.getDefaultSSLCertificate()
continue
}
@ -1120,16 +1129,12 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
klog.Warningf("SSL certificate %q does not contain a Common Name or Subject Alternative Name for server %q: %v",
secrKey, host, err)
klog.Warningf("Using default certificate")
servers[host].SSLCert = *defaultCertificate
servers[host].SSLCert = n.getDefaultSSLCertificate()
continue
}
}
if n.cfg.DynamicCertificatesEnabled {
n.overridePemFileNameAndPemSHA(cert)
}
servers[host].SSLCert = *cert
servers[host].SSLCert = cert
if cert.ExpireTime.Before(time.Now().Add(240 * time.Hour)) {
klog.Warningf("SSL certificate for server %q is about to expire (%v)", host, cert.ExpireTime)
@ -1137,11 +1142,29 @@ func (n *NGINXController) createServers(data []*ingress.Ingress,
}
}
for alias, host := range aliases {
if _, ok := servers[alias]; ok {
klog.Warningf("Conflicting hostname (%v) and alias (%v). Removing alias to avoid conflicts.", host, alias)
servers[host].Alias = ""
for host, hostAliases := range allAliases {
if _, ok := servers[host]; !ok {
continue
}
uniqAliases := sets.NewString()
for _, alias := range hostAliases {
if alias == host {
continue
}
if _, ok := servers[alias]; ok {
continue
}
if uniqAliases.Has(alias) {
continue
}
uniqAliases.Insert(alias)
}
servers[host].Aliases = uniqAliases.List()
}
return servers
@ -1155,7 +1178,9 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
loc.ExternalAuth = anns.ExternalAuth
loc.EnableGlobalAuth = anns.EnableGlobalAuth
loc.HTTP2PushPreload = anns.HTTP2PushPreload
loc.Opentracing = anns.Opentracing
loc.Proxy = anns.Proxy
loc.ProxySSL = anns.ProxySSL
loc.RateLimit = anns.RateLimit
loc.Redirect = anns.Redirect
loc.Rewrite = anns.Rewrite
@ -1166,13 +1191,16 @@ func locationApplyAnnotations(loc *ingress.Location, anns *annotations.Ingress)
loc.UsePortInRedirects = anns.UsePortInRedirects
loc.Connection = anns.Connection
loc.Logs = anns.Logs
loc.LuaRestyWAF = anns.LuaRestyWAF
loc.InfluxDB = anns.InfluxDB
loc.DefaultBackend = anns.DefaultBackend
loc.BackendProtocol = anns.BackendProtocol
loc.FastCGI = anns.FastCGI
loc.CustomHTTPErrors = anns.CustomHTTPErrors
loc.ModSecurity = anns.ModSecurity
loc.Satisfy = anns.Satisfy
loc.Mirror = anns.Mirror
loc.DefaultBackendUpstreamName = defUpstreamName
}
// OK to merge canary ingresses iff there exists one or more ingresses to potentially merge into
@ -1227,9 +1255,16 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
} else {
merged := false
altEqualsPri := false
for _, loc := range servers[defServerName].Locations {
priUps := upstreams[loc.Backend]
altEqualsPri = altUps.Name == priUps.Name
if altEqualsPri {
klog.Warningf("alternative upstream %s in Ingress %s/%s is primary upstream in Other Ingress for location %s%s!",
altUps.Name, ing.Namespace, ing.Name, servers[defServerName].Hostname, loc.Path)
break
}
if canMergeBackend(priUps, altUps) {
klog.V(2).Infof("matching backend %v found for alternative backend %v",
@ -1239,7 +1274,7 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
}
}
if !merged {
if !altEqualsPri && !merged {
klog.Warningf("unable to find real backend for alternative backend %v. Deleting.", altUps.Name)
delete(upstreams, altUps.Name)
}
@ -1258,6 +1293,7 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
}
merged := false
altEqualsPri := false
server, ok := servers[rule.Host]
if !ok {
@ -1271,6 +1307,12 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
// find matching paths
for _, loc := range server.Locations {
priUps := upstreams[loc.Backend]
altEqualsPri = altUps.Name == priUps.Name
if altEqualsPri {
klog.Warningf("alternative upstream %s in Ingress %s/%s is primary upstream in Other Ingress for location %s%s!",
altUps.Name, ing.Namespace, ing.Name, server.Hostname, loc.Path)
break
}
if canMergeBackend(priUps, altUps) && loc.Path == path.Path {
klog.V(2).Infof("matching backend %v found for alternative backend %v",
@ -1280,7 +1322,7 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
}
}
if !merged {
if !altEqualsPri && !merged {
klog.Warningf("unable to find real backend for alternative backend %v. Deleting.", altUps.Name)
delete(upstreams, altUps.Name)
}
@ -1395,3 +1437,50 @@ func shouldCreateUpstreamForLocationDefaultBackend(upstream *ingress.Backend, lo
(len(upstream.Endpoints) == 0 || len(location.CustomHTTPErrors) != 0) &&
location.DefaultBackend != nil
}
func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
port, err := strconv.Atoi(name)
if err != nil {
// not a number. check port names.
for _, svcPort := range svc.Spec.Ports {
if svcPort.Name != name {
continue
}
tp := svcPort.TargetPort
if tp.IntValue() == 0 {
tp = intstr.FromInt(int(svcPort.Port))
}
return &apiv1.ServicePort{
Protocol: "TCP",
Port: svcPort.Port,
TargetPort: tp,
}
}
}
for _, svcPort := range svc.Spec.Ports {
if svcPort.Port != int32(port) {
continue
}
tp := svcPort.TargetPort
if tp.IntValue() == 0 {
tp = intstr.FromInt(port)
}
return &apiv1.ServicePort{
Protocol: "TCP",
Port: svcPort.Port,
TargetPort: svcPort.TargetPort,
}
}
// ExternalName without port
return &apiv1.ServicePort{
Protocol: "TCP",
Port: int32(port),
TargetPort: intstr.FromInt(port),
}
}

File diff suppressed because it is too large Load diff

View file

@ -21,9 +21,8 @@ import (
"net"
"reflect"
"strconv"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
@ -51,35 +50,11 @@ func getEndpoints(s *corev1.Service, port *corev1.ServicePort, proto corev1.Prot
// ExternalName services
if s.Spec.Type == corev1.ServiceTypeExternalName {
klog.V(3).Infof("Ingress using Service %q of type ExternalName.", svcKey)
targetPort := port.TargetPort.IntValue()
if targetPort <= 0 {
klog.Errorf("ExternalName Service %q has an invalid port (%v)", svcKey, targetPort)
return upsServers
}
// if the externalName is not an IP address we need to validate is a valid FQDN
if net.ParseIP(s.Spec.ExternalName) == nil {
defaultRetry := wait.Backoff{
Steps: 2,
Duration: 1 * time.Second,
Factor: 1.5,
Jitter: 0.2,
}
var lastErr error
err := wait.ExponentialBackoff(defaultRetry, func() (bool, error) {
_, err := net.LookupHost(s.Spec.ExternalName)
if err == nil {
return true, nil
}
lastErr = err
return false, nil
})
if err != nil {
klog.Errorf("Error resolving host %q: %v", s.Spec.ExternalName, lastErr)
if errs := validation.IsDNS1123Subdomain(s.Spec.ExternalName); len(errs) > 0 {
klog.Errorf("Invalid DNS name %s: %v", s.Spec.ExternalName, errs)
return upsServers
}
}

View file

@ -83,11 +83,11 @@ func TestGetEndpoints(t *testing.T) {
&corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
ExternalName: "www.10.0.0.1.xip.io",
ExternalName: "www.google.com",
Ports: []corev1.ServicePort{
{
Name: "default",
TargetPort: intstr.FromInt(80),
TargetPort: intstr.FromInt(443),
},
},
},
@ -102,17 +102,17 @@ func TestGetEndpoints(t *testing.T) {
},
[]ingress.Endpoint{
{
Address: "www.10.0.0.1.xip.io",
Port: "80",
Address: "www.google.com",
Port: "443",
},
},
},
{
"a service type ServiceTypeExternalName with an invalid ExternalName value should return one endpoint",
"a service type ServiceTypeExternalName with an invalid ExternalName value should no return endpoints",
&corev1.Service{
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
ExternalName: "foo.bar",
ExternalName: "1#invalid.hostname",
Ports: []corev1.ServicePort{
{
Name: "default",

406
internal/ingress/controller/nginx.go Executable file → Normal file
View file

@ -22,16 +22,15 @@ import (
"errors"
"fmt"
"io/ioutil"
"math"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
"syscall"
"text/template"
"time"
@ -46,7 +45,6 @@ import (
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/filesystem"
adm_controler "k8s.io/ingress-nginx/internal/admission/controller"
"k8s.io/ingress-nginx/internal/file"
@ -69,14 +67,11 @@ import (
const (
tempNginxPattern = "nginx-cfg"
)
var (
tmplPath = "/etc/nginx/template/nginx.tmpl"
emptyUID = "-1"
)
// NewNGINXController creates a new NGINX Ingress controller.
func NewNGINXController(config *Configuration, mc metric.Collector, fs file.Filesystem) *NGINXController {
func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXController {
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
@ -102,9 +97,9 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
stopCh: make(chan struct{}),
updateCh: channels.NewRingChannel(1024),
stopLock: &sync.Mutex{},
ngxErrCh: make(chan error),
fileSystem: fs,
stopLock: &sync.Mutex{},
runningConfig: new(ingress.Configuration),
@ -130,7 +125,6 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
n.podInfo = pod
n.store = store.New(
config.EnableSSLChainCompletion,
config.Namespace,
config.ConfigMapName,
config.TCPConfigMapName,
@ -138,9 +132,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
config.DefaultSSLCertificate,
config.ResyncPeriod,
config.Client,
fs,
n.updateCh,
config.DynamicCertificatesEnabled,
pod,
config.DisableCatchAll)
@ -160,7 +152,7 @@ func NewNGINXController(config *Configuration, mc metric.Collector, fs file.File
}
onTemplateChange := func() {
template, err := ngx_template.NewTemplate(tmplPath, fs)
template, err := ngx_template.NewTemplate(nginx.TemplatePath)
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
klog.Errorf(`
@ -176,21 +168,16 @@ Error loading new template: %v
n.syncQueue.EnqueueTask(task.GetDummyObject("template-change"))
}
ngxTpl, err := ngx_template.NewTemplate(tmplPath, fs)
ngxTpl, err := ngx_template.NewTemplate(nginx.TemplatePath)
if err != nil {
klog.Fatalf("Invalid NGINX configuration template: %v", err)
}
n.t = ngxTpl
if _, ok := fs.(filesystem.DefaultFs); !ok {
// do not setup watchers on tests
return n
}
_, err = watch.NewFileWatcher(tmplPath, onTemplateChange)
_, err = watch.NewFileWatcher(nginx.TemplatePath, onTemplateChange)
if err != nil {
klog.Fatalf("Error creating file watcher for %v: %v", tmplPath, err)
klog.Fatalf("Error creating file watcher for %v: %v", nginx.TemplatePath, err)
}
filesToWatch := []string{}
@ -264,12 +251,8 @@ type NGINXController struct {
store store.Storer
fileSystem filesystem.Filesystem
metricCollector metric.Collector
currentLeader uint32
validationWebhookServer *http.Server
command NginxExecTester
@ -296,14 +279,12 @@ func (n *NGINXController) Start() {
go n.syncStatus.Run(stopCh)
}
n.setLeader(true)
n.metricCollector.OnStartedLeading(electionID)
// manually update SSL expiration metrics
// (to not wait for a reload)
n.metricCollector.SetSSLExpireTime(n.runningConfig.Servers)
},
OnStoppedLeading: func() {
n.setLeader(false)
n.metricCollector.OnStoppedLeading(electionID)
},
PodName: n.podInfo.Name,
@ -353,7 +334,7 @@ func (n *NGINXController) Start() {
select {
case err := <-n.ngxErrCh:
if n.isShuttingDown {
break
return
}
// if the nginx master process dies the workers continue to process requests,
@ -377,6 +358,7 @@ func (n *NGINXController) Start() {
if n.isShuttingDown {
break
}
if evt, ok := event.(store.Event); ok {
klog.V(3).Infof("Event %v received - object %v", evt.Type, evt.Obj)
if evt.Type == store.ConfigurationEvent {
@ -390,7 +372,7 @@ func (n *NGINXController) Start() {
klog.Warningf("Unexpected event type received %T", event)
}
case <-n.stopCh:
break
return
}
}
}
@ -434,7 +416,7 @@ func (n *NGINXController) Stop() error {
// wait for the NGINX process to terminate
timer := time.NewTicker(time.Second * 1)
for range timer.C {
if !process.IsNginxRunning() {
if !nginx.IsRunning() {
klog.Info("NGINX process has stopped")
timer.Stop()
break
@ -517,14 +499,25 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
var serverNameBytes int
for _, srv := range ingressCfg.Servers {
if longestName < len(srv.Hostname) {
longestName = len(srv.Hostname)
hostnameLength := len(srv.Hostname)
if srv.RedirectFromToWWW {
hostnameLength += 4
}
serverNameBytes += len(srv.Hostname)
if longestName < hostnameLength {
longestName = hostnameLength
}
for _, alias := range srv.Aliases {
if longestName < len(alias) {
longestName = len(alias)
}
}
serverNameBytes += hostnameLength
}
if cfg.ServerNameHashBucketSize == 0 {
nameHashBucketSize := nginxHashBucketSize(longestName)
nameHashBucketSize := nginxHashBucketSize(longestName)
if cfg.ServerNameHashBucketSize < nameHashBucketSize {
klog.V(3).Infof("Adjusting ServerNameHashBucketSize variable to %d", nameHashBucketSize)
cfg.ServerNameHashBucketSize = nameHashBucketSize
}
@ -554,7 +547,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
}
if cfg.MaxWorkerConnections == 0 {
maxWorkerConnections := int(math.Ceil(float64(cfg.MaxWorkerOpenFiles * 3.0 / 4)))
maxWorkerConnections := int(float64(cfg.MaxWorkerOpenFiles * 3.0 / 4))
klog.V(3).Infof("Adjusting MaxWorkerConnections variable to %d", maxWorkerConnections)
cfg.MaxWorkerConnections = maxWorkerConnections
}
@ -590,7 +583,7 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
nsSecName := strings.Replace(secretName, "/", "-", -1)
dh, ok := secret.Data["dhparam.pem"]
if ok {
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
if err != nil {
klog.Warningf("Error adding or updating dhparam file %v: %v", nsSecName, err)
} else {
@ -602,31 +595,32 @@ func (n NGINXController) generateTemplate(cfg ngx_config.Configuration, ingressC
cfg.SSLDHParam = sslDHParam
tc := ngx_config.TemplateConfig{
ProxySetHeaders: setHeaders,
AddHeaders: addHeaders,
BacklogSize: sysctlSomaxconn(),
Backends: ingressCfg.Backends,
PassthroughBackends: ingressCfg.PassthroughBackends,
Servers: ingressCfg.Servers,
TCPBackends: ingressCfg.TCPEndpoints,
UDPBackends: ingressCfg.UDPEndpoints,
Cfg: cfg,
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
RedirectServers: buildRedirects(ingressCfg.Servers),
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(),
DynamicCertificatesEnabled: n.cfg.DynamicCertificatesEnabled,
EnableMetrics: n.cfg.EnableMetrics,
cfg.DefaultSSLCertificate = n.getDefaultSSLCertificate()
HealthzURI: nginx.HealthPath,
PID: nginx.PID,
StatusSocket: nginx.StatusSocket,
StatusPath: nginx.StatusPath,
StreamSocket: nginx.StreamSocket,
tc := ngx_config.TemplateConfig{
ProxySetHeaders: setHeaders,
AddHeaders: addHeaders,
BacklogSize: sysctlSomaxconn(),
Backends: ingressCfg.Backends,
PassthroughBackends: ingressCfg.PassthroughBackends,
Servers: ingressCfg.Servers,
TCPBackends: ingressCfg.TCPEndpoints,
UDPBackends: ingressCfg.UDPEndpoints,
Cfg: cfg,
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
RedirectServers: buildRedirects(ingressCfg.Servers),
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
ListenPorts: n.cfg.ListenPorts,
PublishService: n.GetPublishService(),
EnableMetrics: n.cfg.EnableMetrics,
HealthzURI: nginx.HealthPath,
PID: nginx.PID,
StatusPath: nginx.StatusPath,
StatusPort: nginx.StatusPort,
StreamPort: nginx.StreamPort,
}
tc.Cfg.Checksum = ingressCfg.ConfigurationChecksum
@ -679,11 +673,9 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
return err
}
if cfg.EnableOpentracing {
err := createOpentracingCfg(cfg)
if err != nil {
return err
}
err = createOpentracingCfg(cfg)
if err != nil {
return err
}
err = n.testTemplate(content)
@ -704,9 +696,14 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
return err
}
diffOutput, err := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
diffOutput, err := exec.Command("diff", "-I", "'# Configuration.*'", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
if err != nil {
klog.Warningf("Failed to executing diff command: %v", err)
if exitError, ok := err.(*exec.ExitError); ok {
ws := exitError.Sys().(syscall.WaitStatus)
if ws.ExitStatus() == 2 {
klog.Warningf("Failed to executing diff command: %v", err)
}
}
}
klog.Infof("NGINX configuration diff:\n%v", string(diffOutput))
@ -808,7 +805,7 @@ func clearCertificates(config *ingress.Configuration) {
var clearedServers []*ingress.Server
for _, server := range config.Servers {
copyOfServer := *server
copyOfServer.SSLCert = ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
copyOfServer.SSLCert = nil
clearedServers = append(clearedServers, &copyOfServer)
}
config.Servers = clearedServers
@ -856,20 +853,112 @@ func (n *NGINXController) IsDynamicConfigurationEnough(pcfg *ingress.Configurati
copyOfRunningConfig.ControllerPodsCount = 0
copyOfPcfg.ControllerPodsCount = 0
if n.cfg.DynamicCertificatesEnabled {
clearCertificates(&copyOfRunningConfig)
clearCertificates(&copyOfPcfg)
}
clearCertificates(&copyOfRunningConfig)
clearCertificates(&copyOfPcfg)
return copyOfRunningConfig.Equal(&copyOfPcfg)
}
// configureDynamically encodes new Backends in JSON format and POSTs the
// payload to an internal HTTP endpoint handled by Lua.
func configureDynamically(pcfg *ingress.Configuration, isDynamicCertificatesEnabled bool) error {
backends := make([]*ingress.Backend, len(pcfg.Backends))
func (n *NGINXController) configureDynamically(pcfg *ingress.Configuration) error {
backendsChanged := !reflect.DeepEqual(n.runningConfig.Backends, pcfg.Backends)
if backendsChanged {
err := configureBackends(pcfg.Backends)
if err != nil {
return err
}
}
for i, backend := range pcfg.Backends {
streamConfigurationChanged := !reflect.DeepEqual(n.runningConfig.TCPEndpoints, pcfg.TCPEndpoints) || !reflect.DeepEqual(n.runningConfig.UDPEndpoints, pcfg.UDPEndpoints)
if streamConfigurationChanged {
err := updateStreamConfiguration(pcfg.TCPEndpoints, pcfg.UDPEndpoints)
if err != nil {
return err
}
}
if n.runningConfig.ControllerPodsCount != pcfg.ControllerPodsCount {
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
ControllerPodsCount: pcfg.ControllerPodsCount,
})
if err != nil {
return err
}
if statusCode != http.StatusCreated {
return fmt.Errorf("unexpected error code: %d", statusCode)
}
}
serversChanged := !reflect.DeepEqual(n.runningConfig.Servers, pcfg.Servers)
if serversChanged {
err := configureCertificates(pcfg.Servers)
if err != nil {
return err
}
}
return nil
}
func updateStreamConfiguration(TCPEndpoints []ingress.L4Service, UDPEndpoints []ingress.L4Service) error {
streams := make([]ingress.Backend, 0)
for _, ep := range TCPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
for _, ep := range UDPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
buf, err := json.Marshal(streams)
if err != nil {
return err
}
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%v", nginx.StreamPort))
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write(buf)
if err != nil {
return err
}
_, err = fmt.Fprintf(conn, "\r\n")
if err != nil {
return err
}
return nil
}
func configureBackends(rawBackends []*ingress.Backend) error {
backends := make([]*ingress.Backend, len(rawBackends))
for i, backend := range rawBackends {
var service *apiv1.Service
if backend.Service != nil {
service = &apiv1.Service{Spec: backend.Service.Spec}
@ -908,121 +997,54 @@ func configureDynamically(pcfg *ingress.Configuration, isDynamicCertificatesEnab
return fmt.Errorf("unexpected error code: %d", statusCode)
}
streams := make([]ingress.Backend, 0)
for _, ep := range pcfg.TCPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("tcp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
for _, ep := range pcfg.UDPEndpoints {
var service *apiv1.Service
if ep.Service != nil {
service = &apiv1.Service{Spec: ep.Service.Spec}
}
key := fmt.Sprintf("udp-%v-%v-%v", ep.Backend.Namespace, ep.Backend.Name, ep.Backend.Port.String())
streams = append(streams, ingress.Backend{
Name: key,
Endpoints: ep.Endpoints,
Port: intstr.FromInt(ep.Port),
Service: service,
})
}
err = updateStreamConfiguration(streams)
if err != nil {
return err
}
statusCode, _, err = nginx.NewPostStatusRequest("/configuration/general", "application/json", ingress.GeneralConfig{
ControllerPodsCount: pcfg.ControllerPodsCount,
})
if err != nil {
return err
}
if statusCode != http.StatusCreated {
return fmt.Errorf("unexpected error code: %d", statusCode)
}
if isDynamicCertificatesEnabled {
err = configureCertificates(pcfg)
if err != nil {
return err
}
}
return nil
}
func updateStreamConfiguration(streams []ingress.Backend) error {
conn, err := net.Dial("unix", nginx.StreamSocket)
if err != nil {
return err
}
defer conn.Close()
buf, err := json.Marshal(streams)
if err != nil {
return err
}
_, err = conn.Write(buf)
if err != nil {
return err
}
_, err = fmt.Fprintf(conn, "\r\n")
if err != nil {
return err
}
return nil
type sslConfiguration struct {
Certificates map[string]string `json:"certificates"`
Servers map[string]string `json:"servers"`
}
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
// that is handled by Lua
func configureCertificates(pcfg *ingress.Configuration) error {
var servers []*ingress.Server
func configureCertificates(rawServers []*ingress.Server) error {
configuration := &sslConfiguration{
Certificates: map[string]string{},
Servers: map[string]string{},
}
for _, server := range pcfg.Servers {
servers = append(servers, &ingress.Server{
Hostname: server.Hostname,
SSLCert: ingress.SSLCert{
PemCertKey: server.SSLCert.PemCertKey,
},
})
configure := func(hostname string, sslCert *ingress.SSLCert) {
uid := emptyUID
if server.Alias != "" && server.SSLCert.PemCertKey != "" &&
ssl.IsValidHostname(server.Alias, server.SSLCert.CN) {
servers = append(servers, &ingress.Server{
Hostname: server.Alias,
SSLCert: ingress.SSLCert{
PemCertKey: server.SSLCert.PemCertKey,
},
})
if sslCert != nil {
uid = sslCert.UID
if _, ok := configuration.Certificates[uid]; !ok {
configuration.Certificates[uid] = sslCert.PemCertKey
}
}
configuration.Servers[hostname] = uid
}
for _, rawServer := range rawServers {
configure(rawServer.Hostname, rawServer.SSLCert)
for _, alias := range rawServer.Aliases {
if rawServer.SSLCert != nil && ssl.IsValidHostname(alias, rawServer.SSLCert.CN) {
configuration.Servers[alias] = rawServer.SSLCert.UID
} else {
configuration.Servers[alias] = emptyUID
}
}
}
redirects := buildRedirects(pcfg.Servers)
redirects := buildRedirects(rawServers)
for _, redirect := range redirects {
servers = append(servers, &ingress.Server{
Hostname: redirect.From,
SSLCert: ingress.SSLCert{
PemCertKey: redirect.SSLCert.PemCertKey,
},
})
configure(redirect.From, redirect.SSLCert)
}
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/servers", "application/json", servers)
statusCode, _, err := nginx.NewPostStatusRequest("/configuration/servers", "application/json", configuration)
if err != nil {
return err
}
@ -1050,14 +1072,22 @@ const jaegerTmpl = `{
},
"reporter": {
"localAgentHostPort": "{{ .JaegerCollectorHost }}:{{ .JaegerCollectorPort }}"
}
},
"headers": {
"TraceContextHeaderName": "{{ .JaegerTraceContextHeaderName }}",
"jaegerDebugHeader": "{{ .JaegerDebugHeader }}",
"jaegerBaggageHeader": "{{ .JaegerBaggageHeader }}",
"traceBaggageHeaderPrefix": "{{ .JaegerTraceBaggageHeaderPrefix }}"
},
}`
const datadogTmpl = `{
"service": "{{ .DatadogServiceName }}",
"agent_host": "{{ .DatadogCollectorHost }}",
"agent_port": {{ .DatadogCollectorPort }},
"operation_name_override": "{{ .DatadogOperationNameOverride }}"
"operation_name_override": "{{ .DatadogOperationNameOverride }}",
"sample_rate": {{ .DatadogSampleRate }},
"dd.priority.sampling": {{ .DatadogPrioritySampling }}
}`
func createOpentracingCfg(cfg ngx_config.Configuration) error {
@ -1090,7 +1120,7 @@ func createOpentracingCfg(cfg ngx_config.Configuration) error {
}
// Expand possible environment variables before writing the configuration to file.
expanded := os.ExpandEnv(string(tmplBuf.Bytes()))
expanded := os.ExpandEnv(tmplBuf.String())
return ioutil.WriteFile("/etc/nginx/opentracing.json", []byte(expanded), file.ReadWriteByUser)
}
@ -1127,7 +1157,7 @@ func cleanTempNginxCfg() error {
type redirect struct {
From string
To string
SSLCert ingress.SSLCert
SSLCert *ingress.SSLCert
}
func buildRedirects(servers []*ingress.Server) []*redirect {
@ -1171,7 +1201,7 @@ func buildRedirects(servers []*ingress.Server) []*redirect {
To: to,
}
if srv.SSLCert.PemSHA != "" {
if srv.SSLCert != nil {
if ssl.IsValidHostname(from, srv.SSLCert.CN) {
r.SSLCert = srv.SSLCert
} else {
@ -1185,15 +1215,3 @@ func buildRedirects(servers []*ingress.Server) []*redirect {
return redirectServers
}
func (n *NGINXController) setLeader(leader bool) {
var i uint32
if leader {
i = 1
}
atomic.StoreUint32(&n.currentLeader, i)
}
func (n *NGINXController) isLeader() bool {
return atomic.LoadUint32(&n.currentLeader) != 0
}

Some files were not shown because too many files have changed in this diff Show more