Merge remote-tracking branch 'base/master' into fix/collect-metrics-if-metrics-per-host-false
This commit is contained in:
commit
19770f5b41
2674 changed files with 371760 additions and 171868 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
23
internal/ingress/annotations/annotations.go
Executable file → Normal 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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
152
internal/ingress/annotations/authreq/main.go
Executable file → Normal 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
180
internal/ingress/annotations/authreq/main_test.go
Executable file → Normal 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
4
internal/ingress/annotations/authreqglobal/main.go
Executable file → Normal 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
20
internal/ingress/annotations/authreqglobal/main_test.go
Executable file → Normal 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,
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
107
internal/ingress/annotations/fastcgi/main.go
Normal file
107
internal/ingress/annotations/fastcgi/main.go
Normal 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
|
||||
}
|
||||
263
internal/ingress/annotations/fastcgi/main_test.go
Normal file
263
internal/ingress/annotations/fastcgi/main_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
89
internal/ingress/annotations/mirror/main.go
Normal file
89
internal/ingress/annotations/mirror/main.go
Normal 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
|
||||
}
|
||||
72
internal/ingress/annotations/mirror/main_test.go
Normal file
72
internal/ingress/annotations/mirror/main_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
61
internal/ingress/annotations/opentracing/main.go
Normal file
61
internal/ingress/annotations/opentracing/main.go
Normal 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
|
||||
}
|
||||
114
internal/ingress/annotations/opentracing/main_test.go
Normal file
114
internal/ingress/annotations/opentracing/main_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
163
internal/ingress/annotations/proxyssl/main.go
Normal file
163
internal/ingress/annotations/proxyssl/main.go
Normal 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
|
||||
}
|
||||
269
internal/ingress/annotations/proxyssl/main_test.go
Normal file
269
internal/ingress/annotations/proxyssl/main_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
175
internal/ingress/controller/config/config.go
Executable file → Normal 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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
395
internal/ingress/controller/controller.go
Executable file → Normal 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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
406
internal/ingress/controller/nginx.go
Executable file → Normal 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, ©OfServer)
|
||||
}
|
||||
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(©OfRunningConfig)
|
||||
clearCertificates(©OfPcfg)
|
||||
}
|
||||
clearCertificates(©OfRunningConfig)
|
||||
clearCertificates(©OfPcfg)
|
||||
|
||||
return copyOfRunningConfig.Equal(©OfPcfg)
|
||||
}
|
||||
|
||||
// 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
Loading…
Add table
Add a link
Reference in a new issue