Update dependencies
This commit is contained in:
parent
bf5616c65b
commit
d6d374b28d
13962 changed files with 48226 additions and 3618880 deletions
72
Gopkg.lock
generated
72
Gopkg.lock
generated
|
@ -31,6 +31,23 @@
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/dgrijalva/jwt-go"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c"
|
||||||
|
version = "v3.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/docker/distribution"
|
||||||
|
packages = ["digestset","reference"]
|
||||||
|
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/docker/spdystream"
|
||||||
|
packages = [".","spdy"]
|
||||||
|
revision = "bc6354cbbc295e925e4c611ffe90c1f287ee54db"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/emicklei/go-restful"
|
name = "github.com/emicklei/go-restful"
|
||||||
packages = [".","log"]
|
packages = [".","log"]
|
||||||
|
@ -81,7 +98,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/gogo/protobuf"
|
name = "github.com/gogo/protobuf"
|
||||||
packages = ["proto","sortkeys"]
|
packages = ["gogoproto","proto","protoc-gen-gogo/descriptor","sortkeys"]
|
||||||
revision = "100ba4e885062801d56799d78530b73b178a78f3"
|
revision = "100ba4e885062801d56799d78530b73b178a78f3"
|
||||||
version = "v0.4"
|
version = "v0.4"
|
||||||
|
|
||||||
|
@ -157,6 +174,12 @@
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "5b9ff866471762aa2ab2dced63c9fb6f53921342"
|
revision = "5b9ff866471762aa2ab2dced63c9fb6f53921342"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/kr/pty"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "95d05c1eef33a45bd58676b6ce28d105839b8d0b"
|
||||||
|
version = "v1.0.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/kylelemons/godebug"
|
name = "github.com/kylelemons/godebug"
|
||||||
|
@ -188,10 +211,17 @@
|
||||||
revision = "d0303fe809921458f417bcf828397a65db30a7e4"
|
revision = "d0303fe809921458f417bcf828397a65db30a7e4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
name = "github.com/ncabatoff/process-exporter"
|
name = "github.com/ncabatoff/process-exporter"
|
||||||
packages = [".","proc"]
|
packages = [".","proc"]
|
||||||
revision = "ae9193ff5f4d34dd62dc2fa194453ea73d7e64ee"
|
revision = "5917bc766b95a1fa3c2ae85340f4de02a6b7e15e"
|
||||||
version = "0.1.0"
|
source = "github.com/aledbf/process-exporter"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/opencontainers/go-digest"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
|
||||||
|
version = "v1.0.0-rc1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -268,7 +298,7 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/net"
|
name = "golang.org/x/net"
|
||||||
packages = ["context","http2","http2/hpack","idna","lex/httplex"]
|
packages = ["context","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"]
|
||||||
revision = "a04bdaca5b32abe1c069418fb7088ae607de5bd0"
|
revision = "a04bdaca5b32abe1c069418fb7088ae607de5bd0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
@ -283,6 +313,18 @@
|
||||||
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable","width"]
|
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable","width"]
|
||||||
revision = "825fc78a2fd6fa0a5447e300189e3219e05e1f25"
|
revision = "825fc78a2fd6fa0a5447e300189e3219e05e1f25"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "google.golang.org/genproto"
|
||||||
|
packages = ["googleapis/rpc/status"]
|
||||||
|
revision = "f676e0f3ac6395ff1a529ae59a6670878a8371a6"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "google.golang.org/grpc"
|
||||||
|
packages = [".","codes","connectivity","credentials","grpclb/grpc_lb_v1/messages","grpclog","internal","keepalive","metadata","naming","peer","stats","status","tap","transport"]
|
||||||
|
revision = "f92cdcd7dcdc69e81b2d7b338479a19a8723cfa3"
|
||||||
|
version = "v1.6.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "gopkg.in/fsnotify.v1"
|
name = "gopkg.in/fsnotify.v1"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
|
@ -313,22 +355,28 @@
|
||||||
packages = ["admissionregistration/v1alpha1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1beta1"]
|
packages = ["admissionregistration/v1alpha1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1beta1"]
|
||||||
revision = "81aa34336d28aadc3a8e8da7dfd9258c5157e5e4"
|
revision = "81aa34336d28aadc3a8e8da7dfd9258c5157e5e4"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "k8s.io/apiextensions-apiserver"
|
||||||
|
packages = ["pkg/features"]
|
||||||
|
revision = "a5bbfd114a9b122acd741c61d88c84812375d9e1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "k8s.io/apimachinery"
|
name = "k8s.io/apimachinery"
|
||||||
packages = ["pkg/api/equality","pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apimachinery","pkg/apimachinery/announced","pkg/apimachinery/registered","pkg/apis/meta/internalversion","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1alpha1","pkg/conversion","pkg/conversion/queryparams","pkg/conversion/unstructured","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/mergepatch","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/strategicpatch","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/json","third_party/forked/golang/reflect"]
|
packages = ["pkg/api/equality","pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/api/validation","pkg/apimachinery","pkg/apimachinery/announced","pkg/apimachinery/registered","pkg/apis/meta/internalversion","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1/validation","pkg/apis/meta/v1alpha1","pkg/conversion","pkg/conversion/queryparams","pkg/conversion/unstructured","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/httpstream","pkg/util/httpstream/spdy","pkg/util/intstr","pkg/util/json","pkg/util/mergepatch","pkg/util/net","pkg/util/rand","pkg/util/remotecommand","pkg/util/runtime","pkg/util/sets","pkg/util/strategicpatch","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/json","third_party/forked/golang/netutil","third_party/forked/golang/reflect"]
|
||||||
revision = "3b05bbfa0a45413bfa184edbf9af617e277962fb"
|
revision = "3b05bbfa0a45413bfa184edbf9af617e277962fb"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "k8s.io/apiserver"
|
name = "k8s.io/apiserver"
|
||||||
packages = ["pkg/server/healthz"]
|
packages = ["pkg/authentication/authenticator","pkg/authentication/serviceaccount","pkg/authentication/user","pkg/features","pkg/server/healthz","pkg/util/feature"]
|
||||||
revision = "c1e53d745d0fe45bf7d5d44697e6eface25fceca"
|
revision = "c1e53d745d0fe45bf7d5d44697e6eface25fceca"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "k8s.io/client-go"
|
name = "k8s.io/client-go"
|
||||||
packages = ["discovery","discovery/fake","kubernetes","kubernetes/fake","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1alpha1/fake","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta1/fake","kubernetes/typed/apps/v1beta2","kubernetes/typed/apps/v1beta2/fake","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1/fake","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authentication/v1beta1/fake","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1/fake","kubernetes/typed/authorization/v1beta1","kubernetes/typed/authorization/v1beta1/fake","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v1/fake","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta1/fake","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1/fake","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v1beta1/fake","kubernetes/typed/batch/v2alpha1","kubernetes/typed/batch/v2alpha1/fake","kubernetes/typed/certificates/v1beta1","kubernetes/typed/certificates/v1beta1/fake","kubernetes/typed/core/v1","kubernetes/typed/core/v1/fake","kubernetes/typed/extensions/v1beta1","kubernetes/typed/extensions/v1beta1/fake","kubernetes/typed/networking/v1","kubernetes/typed/networking/v1/fake","kubernetes/typed/policy/v1beta1","kubernetes/typed/policy/v1beta1/fake","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1/fake","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1alpha1/fake","kubernetes/typed/rbac/v1beta1","kubernetes/typed/rbac/v1beta1/fake","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1alpha1/fake","kubernetes/typed/settings/v1alpha1","kubernetes/typed/settings/v1alpha1/fake","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1/fake","kubernetes/typed/storage/v1beta1","kubernetes/typed/storage/v1beta1/fake","pkg/version","rest","rest/watch","testing","tools/auth","tools/cache","tools/cache/testing","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/leaderelection","tools/leaderelection/resourcelock","tools/metrics","tools/pager","tools/record","tools/reference","transport","util/cert","util/cert/triple","util/flowcontrol","util/homedir","util/integer","util/workqueue"]
|
packages = ["discovery","discovery/fake","kubernetes","kubernetes/fake","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1alpha1/fake","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta1/fake","kubernetes/typed/apps/v1beta2","kubernetes/typed/apps/v1beta2/fake","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1/fake","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authentication/v1beta1/fake","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1/fake","kubernetes/typed/authorization/v1beta1","kubernetes/typed/authorization/v1beta1/fake","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v1/fake","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta1/fake","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1/fake","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v1beta1/fake","kubernetes/typed/batch/v2alpha1","kubernetes/typed/batch/v2alpha1/fake","kubernetes/typed/certificates/v1beta1","kubernetes/typed/certificates/v1beta1/fake","kubernetes/typed/core/v1","kubernetes/typed/core/v1/fake","kubernetes/typed/extensions/v1beta1","kubernetes/typed/extensions/v1beta1/fake","kubernetes/typed/networking/v1","kubernetes/typed/networking/v1/fake","kubernetes/typed/policy/v1beta1","kubernetes/typed/policy/v1beta1/fake","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1/fake","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1alpha1/fake","kubernetes/typed/rbac/v1beta1","kubernetes/typed/rbac/v1beta1/fake","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1alpha1/fake","kubernetes/typed/settings/v1alpha1","kubernetes/typed/settings/v1alpha1/fake","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1/fake","kubernetes/typed/storage/v1beta1","kubernetes/typed/storage/v1beta1/fake","pkg/version","rest","rest/watch","testing","tools/auth","tools/cache","tools/cache/testing","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/leaderelection","tools/leaderelection/resourcelock","tools/metrics","tools/pager","tools/record","tools/reference","tools/remotecommand","transport","transport/spdy","util/cert","util/cert/triple","util/exec","util/flowcontrol","util/homedir","util/integer","util/retry","util/workqueue"]
|
||||||
revision = "82aa063804cf055e16e8911250f888bc216e8b61"
|
revision = "82aa063804cf055e16e8911250f888bc216e8b61"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
@ -339,13 +387,19 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "k8s.io/kubernetes"
|
name = "k8s.io/kubernetes"
|
||||||
packages = ["pkg/api","pkg/util/sysctl"]
|
packages = ["pkg/api","pkg/api/helper","pkg/api/install","pkg/api/service","pkg/api/util","pkg/api/v1","pkg/api/v1/helper","pkg/api/v1/pod","pkg/api/validation","pkg/apis/extensions","pkg/apis/networking","pkg/capabilities","pkg/cloudprovider","pkg/controller","pkg/features","pkg/kubelet/apis","pkg/kubelet/apis/cri/v1alpha1/runtime","pkg/kubelet/container","pkg/kubelet/types","pkg/kubelet/util/format","pkg/kubelet/util/ioutils","pkg/kubelet/util/sliceutils","pkg/security/apparmor","pkg/serviceaccount","pkg/util/file","pkg/util/hash","pkg/util/io","pkg/util/mount","pkg/util/net/sets","pkg/util/parsers","pkg/util/pointer","pkg/util/sysctl","pkg/util/taints","pkg/volume","pkg/volume/util","third_party/forked/golang/expansion"]
|
||||||
revision = "0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4"
|
revision = "0b9efaeb34a2fc51ff8e4d34ad9bc6375459c4a4"
|
||||||
version = "v1.8.0"
|
version = "v1.8.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "k8s.io/utils"
|
||||||
|
packages = ["exec"]
|
||||||
|
revision = "4fe312863be2155a7b68acd2aff1c9221b24e68c"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "e17f9cc4f1307ae1c3c913743c5561b3c1836d1a04d5c49c8b98727ef33b9646"
|
inputs-digest = "4511b8e7ec7b35d8752f919dfe51b39a07852a98de8d765ce7f2512032450cde"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
10
Gopkg.toml
10
Gopkg.toml
|
@ -20,6 +20,13 @@
|
||||||
# name = "github.com/x/y"
|
# name = "github.com/x/y"
|
||||||
# version = "2.4.0"
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/docker/distribution"
|
||||||
|
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/opencontainers/go-digest"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -47,7 +54,8 @@
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/ncabatoff/process-exporter"
|
name = "github.com/ncabatoff/process-exporter"
|
||||||
version = "0.1.0"
|
source = "github.com/aledbf/process-exporter"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|
|
@ -22,12 +22,14 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/nginx/controller"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// start a new nginx controller
|
// start a new nginx controller
|
||||||
ngx := newNGINXController()
|
ngx := controller.NewNGINXController()
|
||||||
|
|
||||||
go handleSigterm(ngx)
|
go handleSigterm(ngx)
|
||||||
// start the controller
|
// start the controller
|
||||||
|
@ -40,7 +42,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSigterm(ngx *NGINXController) {
|
func handleSigterm(ngx *controller.NGINXController) {
|
||||||
signalChan := make(chan os.Signal, 1)
|
signalChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(signalChan, syscall.SIGTERM)
|
signal.Notify(signalChan, syscall.SIGTERM)
|
||||||
<-signalChan
|
<-signalChan
|
||||||
|
|
|
@ -1,97 +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 main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/metric/collector"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ngxStatusPath = "/nginx_status"
|
|
||||||
ngxVtsPath = "/nginx_status/format/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NGINXController) setupMonitor(sm statusModule) {
|
|
||||||
csm := n.statusModule
|
|
||||||
if csm != sm {
|
|
||||||
glog.Infof("changing prometheus collector from %v to %v", csm, sm)
|
|
||||||
n.stats.stop(csm)
|
|
||||||
n.stats.start(sm)
|
|
||||||
n.statusModule = sm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type statsCollector struct {
|
|
||||||
process prometheus.Collector
|
|
||||||
basic collector.Stopable
|
|
||||||
vts collector.Stopable
|
|
||||||
|
|
||||||
namespace string
|
|
||||||
watchClass string
|
|
||||||
|
|
||||||
port int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statsCollector) stop(sm statusModule) {
|
|
||||||
switch sm {
|
|
||||||
case defaultStatusModule:
|
|
||||||
s.basic.Stop()
|
|
||||||
prometheus.Unregister(s.basic)
|
|
||||||
case vtsStatusModule:
|
|
||||||
s.vts.Stop()
|
|
||||||
prometheus.Unregister(s.vts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statsCollector) start(sm statusModule) {
|
|
||||||
switch sm {
|
|
||||||
case defaultStatusModule:
|
|
||||||
s.basic = collector.NewNginxStatus(s.namespace, s.watchClass, s.port, ngxStatusPath)
|
|
||||||
prometheus.Register(s.basic)
|
|
||||||
break
|
|
||||||
case vtsStatusModule:
|
|
||||||
s.vts = collector.NewNGINXVTSCollector(s.namespace, s.watchClass, s.port, ngxVtsPath)
|
|
||||||
prometheus.Register(s.vts)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStatsCollector(ns, class, binary string, port int) *statsCollector {
|
|
||||||
glog.Infof("starting new nginx stats collector for Ingress controller running in namespace %v (class %v)", ns, class)
|
|
||||||
glog.Infof("collector extracting information from port %v", port)
|
|
||||||
pc, err := collector.NewNamedProcess(true, collector.BinaryNameMatcher{
|
|
||||||
Name: "nginx",
|
|
||||||
Binary: binary,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("unexpected error registering nginx collector: %v", err)
|
|
||||||
}
|
|
||||||
err = prometheus.Register(pc)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("unexpected error registering nginx collector: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &statsCollector{
|
|
||||||
namespace: ns,
|
|
||||||
watchClass: class,
|
|
||||||
process: pc,
|
|
||||||
port: port,
|
|
||||||
}
|
|
||||||
}
|
|
110
cmd/nginx/tcp.go
110
cmd/nginx/tcp.go
|
@ -1,110 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"github.com/paultag/sniff/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
Hostname string
|
|
||||||
IP string
|
|
||||||
Port int
|
|
||||||
ProxyProtocol bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type proxy struct {
|
|
||||||
ServerList []*server
|
|
||||||
Default *server
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *proxy) Get(host string) *server {
|
|
||||||
if p.ServerList == nil {
|
|
||||||
return p.Default
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range p.ServerList {
|
|
||||||
if s.Hostname == host {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.Default
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *proxy) Handle(conn net.Conn) {
|
|
||||||
defer conn.Close()
|
|
||||||
data := make([]byte, 4096)
|
|
||||||
|
|
||||||
length, err := conn.Read(data)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(4).Infof("error reading the first 4k of the connection: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy := p.Default
|
|
||||||
hostname, err := parser.GetHostname(data[:])
|
|
||||||
if err == nil {
|
|
||||||
glog.V(4).Infof("parsed hostname from TLS Client Hello: %s", hostname)
|
|
||||||
proxy = p.Get(hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
if proxy == nil {
|
|
||||||
glog.V(4).Infof("there is no configured proxy for SSL connections")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", proxy.IP, proxy.Port))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer clientConn.Close()
|
|
||||||
|
|
||||||
if proxy.ProxyProtocol {
|
|
||||||
//Write out the proxy-protocol header
|
|
||||||
localAddr := conn.LocalAddr().(*net.TCPAddr)
|
|
||||||
remoteAddr := conn.RemoteAddr().(*net.TCPAddr)
|
|
||||||
protocol := "UNKNOWN"
|
|
||||||
if remoteAddr.IP.To4() != nil {
|
|
||||||
protocol = "TCP4"
|
|
||||||
} else if remoteAddr.IP.To16() != nil {
|
|
||||||
protocol = "TCP6"
|
|
||||||
}
|
|
||||||
proxyProtocolHeader := fmt.Sprintf("PROXY %s %s %s %d %d\r\n", protocol, remoteAddr.IP.String(), localAddr.IP.String(), remoteAddr.Port, localAddr.Port)
|
|
||||||
glog.V(4).Infof("Writing proxy protocol header - %s", proxyProtocolHeader)
|
|
||||||
_, err = fmt.Fprintf(clientConn, proxyProtocolHeader)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error writing proxy-protocol header: %s", err)
|
|
||||||
clientConn.Close()
|
|
||||||
} else {
|
|
||||||
_, err = clientConn.Write(data[:length])
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error writing first 4k of proxy data: %s", err)
|
|
||||||
clientConn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pipe(clientConn, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pipe(client, server net.Conn) {
|
|
||||||
doCopy := func(s, c net.Conn, cancel chan<- bool) {
|
|
||||||
io.Copy(s, c)
|
|
||||||
cancel <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel := make(chan bool, 2)
|
|
||||||
|
|
||||||
go doCopy(server, client, cancel)
|
|
||||||
go doCopy(client, server, cancel)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-cancel:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/util/sysctl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sysctlSomaxconn returns the value of net.core.somaxconn, i.e.
|
|
||||||
// maximum number of connections that can be queued for acceptance
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
|
|
||||||
func sysctlSomaxconn() int {
|
|
||||||
maxConns, err := sysctl.New().GetSysctl("net/core/somaxconn")
|
|
||||||
if err != nil || maxConns < 512 {
|
|
||||||
glog.V(3).Infof("system net.core.somaxconn=%v (using system default)", maxConns)
|
|
||||||
return 511
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxConns
|
|
||||||
}
|
|
||||||
|
|
||||||
// sysctlFSFileMax returns the value of fs.file-max, i.e.
|
|
||||||
// maximum number of open file descriptors
|
|
||||||
func sysctlFSFileMax() int {
|
|
||||||
var rLimit syscall.Rlimit
|
|
||||||
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error reading system maximum number of open file descriptors (RLIMIT_NOFILE): %v", err)
|
|
||||||
// returning 0 means don't render the value
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(rLimit.Max)
|
|
||||||
}
|
|
||||||
|
|
||||||
func diff(b1, b2 []byte) ([]byte, error) {
|
|
||||||
f1, err := ioutil.TempFile("", "a")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f1.Close()
|
|
||||||
defer os.Remove(f1.Name())
|
|
||||||
|
|
||||||
f2, err := ioutil.TempFile("", "b")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f2.Close()
|
|
||||||
defer os.Remove(f2.Name())
|
|
||||||
|
|
||||||
f1.Write(b1)
|
|
||||||
f2.Write(b2)
|
|
||||||
|
|
||||||
out, _ := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPortAvailable(p int) bool {
|
|
||||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%v", p))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ln.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 main
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestDiff(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
a []byte
|
|
||||||
b []byte
|
|
||||||
empty bool
|
|
||||||
}{
|
|
||||||
{[]byte(""), []byte(""), true},
|
|
||||||
{[]byte("a"), []byte("a"), true},
|
|
||||||
{[]byte("a"), []byte("b"), false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
b, err := diff(test.a, test.b)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error returned: %v", err)
|
|
||||||
}
|
|
||||||
if len(b) == 0 && !test.empty {
|
|
||||||
t.Fatalf("expected empty but returned %s", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
529
config/config.go
529
config/config.go
|
@ -1,529 +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 config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
bodySize = "1m"
|
|
||||||
|
|
||||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
|
||||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
|
||||||
// Log levels above are listed in the order of increasing severity
|
|
||||||
errorLevel = "notice"
|
|
||||||
|
|
||||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
|
||||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
|
||||||
// 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"
|
|
||||||
|
|
||||||
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`
|
|
||||||
|
|
||||||
logFormatStream = `[$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.
|
|
||||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
|
||||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
|
||||||
sslBufferSize = "4k"
|
|
||||||
|
|
||||||
// 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-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA"
|
|
||||||
|
|
||||||
// SSL enabled protocols to use
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
|
||||||
sslProtocols = "TLSv1 TLSv1.1 TLSv1.2"
|
|
||||||
|
|
||||||
// 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"
|
|
||||||
|
|
||||||
// Size of the SSL shared cache between all worker processes.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
|
||||||
sslSessionCacheSize = "10m"
|
|
||||||
|
|
||||||
// Default setting for load balancer algorithm
|
|
||||||
defaultLoadBalancerAlgorithm = "least_conn"
|
|
||||||
|
|
||||||
// Parameters for a shared memory zone that will keep states for various keys.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone
|
|
||||||
defaultLimitConnZoneVariable = "$binary_remote_addr"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configuration represents the content of nginx.conf file
|
|
||||||
type Configuration struct {
|
|
||||||
defaults.Backend `json:",squash"`
|
|
||||||
|
|
||||||
// Sets the name of the configmap that contains the headers to pass to the client
|
|
||||||
AddHeaders string `json:"add-headers,omitempty"`
|
|
||||||
|
|
||||||
// AllowBackendServerHeader enables the return of the header Server from the backend
|
|
||||||
// instead of the generic nginx string.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_hide_header
|
|
||||||
// By default this is disabled
|
|
||||||
AllowBackendServerHeader bool `json:"allow-backend-server-header"`
|
|
||||||
|
|
||||||
// AccessLogPath sets the path of the access logs if enabled
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
|
|
||||||
// By default access logs go to /var/log/nginx/access.log
|
|
||||||
AccessLogPath string `json:"access-log-path,omitempty"`
|
|
||||||
|
|
||||||
// ErrorLogPath sets the path of the error logs
|
|
||||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
|
||||||
// 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"`
|
|
||||||
|
|
||||||
// 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
|
|
||||||
ClientHeaderBufferSize string `json:"client-header-buffer-size"`
|
|
||||||
|
|
||||||
// Defines a timeout for reading client request header, in seconds
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout
|
|
||||||
ClientHeaderTimeout int `json:"client-header-timeout,omitempty"`
|
|
||||||
|
|
||||||
// Sets buffer size for reading client request body
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_buffer_size
|
|
||||||
ClientBodyBufferSize string `json:"client-body-buffer-size,omitempty"`
|
|
||||||
|
|
||||||
// Defines a timeout for reading client request body, in seconds
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_timeout
|
|
||||||
ClientBodyTimeout int `json:"client-body-timeout,omitempty"`
|
|
||||||
|
|
||||||
// DisableAccessLog disables the Access Log globally from NGINX ingress controller
|
|
||||||
//http://nginx.org/en/docs/http/ngx_http_log_module.html
|
|
||||||
DisableAccessLog bool `json:"disable-access-log,omitempty"`
|
|
||||||
|
|
||||||
// DisableIpv6 disable listening on ipv6 address
|
|
||||||
DisableIpv6 bool `json:"disable-ipv6,omitempty"`
|
|
||||||
|
|
||||||
// EnableUnderscoresInHeaders enables underscores in header names
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#underscores_in_headers
|
|
||||||
// By default this is disabled
|
|
||||||
EnableUnderscoresInHeaders bool `json:"enable-underscores-in-headers"`
|
|
||||||
|
|
||||||
// IgnoreInvalidHeaders set if header fields with invalid names should be ignored
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#ignore_invalid_headers
|
|
||||||
// By default this is enabled
|
|
||||||
IgnoreInvalidHeaders bool `json:"ignore-invalid-headers"`
|
|
||||||
|
|
||||||
// EnableVtsStatus allows the replacement of the default status page with a third party module named
|
|
||||||
// nginx-module-vts - https://github.com/vozlt/nginx-module-vts
|
|
||||||
// By default this is disabled
|
|
||||||
EnableVtsStatus bool `json:"enable-vts-status,omitempty"`
|
|
||||||
|
|
||||||
// Vts config on http level
|
|
||||||
// Description: Sets parameters for a shared memory zone that will keep states for various keys. The cache is shared between all worker processe
|
|
||||||
// https://github.com/vozlt/nginx-module-vts#vhost_traffic_status_zone
|
|
||||||
// Default value is 10m
|
|
||||||
VtsStatusZoneSize string `json:"vts-status-zone-size,omitempty"`
|
|
||||||
|
|
||||||
// Vts config on http level
|
|
||||||
// Description: Enables the keys by user defined variable. The key is a key string to calculate traffic.
|
|
||||||
// The name is a group string to calculate traffic. The key and name can contain variables such as $host,
|
|
||||||
// $server_name. The name's group belongs to filterZones if specified. The key's group belongs to serverZones
|
|
||||||
// if not specified second argument name. The example with geoip module is as follows:
|
|
||||||
// https://github.com/vozlt/nginx-module-vts#vhost_traffic_status_filter_by_set_key
|
|
||||||
// Default value is $geoip_country_code country::*
|
|
||||||
VtsDefaultFilterKey string `json:"vts-default-filter-key,omitempty"`
|
|
||||||
|
|
||||||
// RetryNonIdempotent since 1.9.13 NGINX will not retry non-idempotent requests (POST, LOCK, PATCH)
|
|
||||||
// in case of an error. The previous behavior can be restored using the value true
|
|
||||||
RetryNonIdempotent bool `json:"retry-non-idempotent"`
|
|
||||||
|
|
||||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
|
||||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
|
||||||
// Log levels above are listed in the order of increasing severity
|
|
||||||
ErrorLogLevel string `json:"error-log-level,omitempty"`
|
|
||||||
|
|
||||||
// https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_field_size
|
|
||||||
// HTTP2MaxFieldSize Limits the maximum size of an HPACK-compressed request header field
|
|
||||||
HTTP2MaxFieldSize string `json:"http2-max-field-size,omitempty"`
|
|
||||||
|
|
||||||
// https://nginx.org/en/docs/http/ngx_http_v2_module.html#http2_max_header_size
|
|
||||||
// HTTP2MaxHeaderSize Limits the maximum size of the entire request header list after HPACK decompression
|
|
||||||
HTTP2MaxHeaderSize string `json:"http2-max-header-size,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables the header HSTS in servers running SSL
|
|
||||||
HSTS bool `json:"hsts,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables the use of HSTS in all the subdomains of the servername
|
|
||||||
// Default: true
|
|
||||||
HSTSIncludeSubdomains bool `json:"hsts-include-subdomains,omitempty"`
|
|
||||||
|
|
||||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
|
||||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
|
||||||
// max-age is the time, in seconds, that the browser should remember that this site is only to be
|
|
||||||
// accessed using HTTPS.
|
|
||||||
HSTSMaxAge string `json:"hsts-max-age,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables the preload attribute in HSTS feature
|
|
||||||
HSTSPreload bool `json:"hsts-preload,omitempty"`
|
|
||||||
|
|
||||||
// Time during which a keep-alive client connection will stay open on the server side.
|
|
||||||
// The zero value disables keep-alive client connections
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
|
|
||||||
KeepAlive int `json:"keep-alive,omitempty"`
|
|
||||||
|
|
||||||
// Sets the maximum number of requests that can be served through one keep-alive connection.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_requests
|
|
||||||
KeepAliveRequests int `json:"keep-alive-requests,omitempty"`
|
|
||||||
|
|
||||||
// LargeClientHeaderBuffers Sets the maximum number and size of buffers used for reading
|
|
||||||
// large client request header.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers
|
|
||||||
// Default: 4 8k
|
|
||||||
LargeClientHeaderBuffers string `json:"large-client-header-buffers"`
|
|
||||||
|
|
||||||
// Enable json escaping
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
|
|
||||||
LogFormatEscapeJSON bool `json:"log-format-escape-json,omitempty"`
|
|
||||||
|
|
||||||
// Customize upstream log_format
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
|
|
||||||
LogFormatUpstream string `json:"log-format-upstream,omitempty"`
|
|
||||||
|
|
||||||
// Customize stream log_format
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_log_module.html#log_format
|
|
||||||
LogFormatStream string `json:"log-format-stream,omitempty"`
|
|
||||||
|
|
||||||
// Maximum number of simultaneous connections that can be opened by each worker process
|
|
||||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_connections
|
|
||||||
MaxWorkerConnections int `json:"max-worker-connections,omitempty"`
|
|
||||||
|
|
||||||
// Sets the bucket size for the map variables hash tables.
|
|
||||||
// Default value depends on the processor’s cache line size.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#map_hash_bucket_size
|
|
||||||
MapHashBucketSize int `json:"map-hash-bucket-size,omitempty"`
|
|
||||||
|
|
||||||
// If UseProxyProtocol is enabled ProxyRealIPCIDR defines the default the IP/network address
|
|
||||||
// of your external load balancer
|
|
||||||
ProxyRealIPCIDR []string `json:"proxy-real-ip-cidr,omitempty"`
|
|
||||||
|
|
||||||
// Sets the name of the configmap that contains the headers to pass to the backend
|
|
||||||
ProxySetHeaders string `json:"proxy-set-headers,omitempty"`
|
|
||||||
|
|
||||||
// Maximum size of the server names hash tables used in server names, map directive’s values,
|
|
||||||
// MIME types, names of request header strings, etcd.
|
|
||||||
// http://nginx.org/en/docs/hash.html
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_max_size
|
|
||||||
ServerNameHashMaxSize int `json:"server-name-hash-max-size,omitempty"`
|
|
||||||
|
|
||||||
// Size of the bucket for the server names hash tables
|
|
||||||
// http://nginx.org/en/docs/hash.html
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_bucket_size
|
|
||||||
ServerNameHashBucketSize int `json:"server-name-hash-bucket-size,omitempty"`
|
|
||||||
|
|
||||||
// Size of the bucket for the proxy headers hash tables
|
|
||||||
// http://nginx.org/en/docs/hash.html
|
|
||||||
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_headers_hash_max_size
|
|
||||||
ProxyHeadersHashMaxSize int `json:"proxy-headers-hash-max-size,omitempty"`
|
|
||||||
|
|
||||||
// Maximum size of the bucket for the proxy headers hash tables
|
|
||||||
// http://nginx.org/en/docs/hash.html
|
|
||||||
// https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_headers_hash_bucket_size
|
|
||||||
ProxyHeadersHashBucketSize int `json:"proxy-headers-hash-bucket-size,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables emitting nginx version in error messages and in the “Server” response header field.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens
|
|
||||||
// Default: true
|
|
||||||
ShowServerTokens bool `json:"server-tokens"`
|
|
||||||
|
|
||||||
// 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 string `json:"ssl-ciphers,omitempty"`
|
|
||||||
|
|
||||||
// Specifies a curve for ECDHE ciphers.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ecdh_curve
|
|
||||||
SSLECDHCurve string `json:"ssl-ecdh-curve,omitempty"`
|
|
||||||
|
|
||||||
// The secret that contains Diffie-Hellman key to help with "Perfect Forward Secrecy"
|
|
||||||
// https://www.openssl.org/docs/manmaster/apps/dhparam.html
|
|
||||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
|
||||||
SSLDHParam string `json:"ssl-dh-param,omitempty"`
|
|
||||||
|
|
||||||
// SSL enabled protocols to use
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
|
||||||
SSLProtocols string `json:"ssl-protocols,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"`
|
|
||||||
|
|
||||||
// Size of the SSL shared cache between all worker processes.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
|
||||||
SSLSessionCacheSize string `json:"ssl-session-cache-size,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables session resumption through TLS session tickets.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_tickets
|
|
||||||
SSLSessionTickets bool `json:"ssl-session-tickets,omitempty"`
|
|
||||||
|
|
||||||
// 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 string `json:"ssl-session-timeout,omitempty"`
|
|
||||||
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
|
||||||
// Sets the size of the buffer used for sending data.
|
|
||||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
|
||||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
|
||||||
SSLBufferSize string `json:"ssl-buffer-size,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables the use of the PROXY protocol to receive client connection
|
|
||||||
// (real IP address) information passed through proxy servers and load balancers
|
|
||||||
// such as HAproxy and Amazon Elastic Load Balancer (ELB).
|
|
||||||
// https://www.nginx.com/resources/admin-guide/proxy-protocol/
|
|
||||||
UseProxyProtocol bool `json:"use-proxy-protocol,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables the use of the nginx module that compresses responses using the "gzip" method
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
|
||||||
UseGzip bool `json:"use-gzip,omitempty"`
|
|
||||||
|
|
||||||
// Enables or disables the HTTP/2 support in secure connections
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_v2_module.html
|
|
||||||
// Default: true
|
|
||||||
UseHTTP2 bool `json:"use-http2,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"`
|
|
||||||
|
|
||||||
// Defines the number of worker processes. By default auto means number of available CPU cores
|
|
||||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
|
|
||||||
WorkerProcesses string `json:"worker-processes,omitempty"`
|
|
||||||
|
|
||||||
// Defines a timeout for a graceful shutdown of worker processes
|
|
||||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout
|
|
||||||
WorkerShutdownTimeout string `json:"worker-shutdown-timeout,omitempty"`
|
|
||||||
|
|
||||||
// Defines the load balancing algorithm to use. The deault is round-robin
|
|
||||||
LoadBalanceAlgorithm string `json:"load-balance,omitempty"`
|
|
||||||
|
|
||||||
// Sets the bucket size for the variables hash table.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#variables_hash_bucket_size
|
|
||||||
VariablesHashBucketSize int `json:"variables-hash-bucket-size,omitempty"`
|
|
||||||
|
|
||||||
// Sets the maximum size of the variables hash table.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#variables_hash_max_size
|
|
||||||
VariablesHashMaxSize int `json:"variables-hash-max-size,omitempty"`
|
|
||||||
|
|
||||||
// Activates the cache for connections to upstream servers.
|
|
||||||
// The connections parameter sets the maximum number of idle keepalive connections to
|
|
||||||
// upstream servers that are preserved in the cache of each worker process. When this
|
|
||||||
// number is exceeded, the least recently used connections are closed.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
|
|
||||||
// Default: 32
|
|
||||||
UpstreamKeepaliveConnections int `json:"upstream-keepalive-connections,omitempty"`
|
|
||||||
|
|
||||||
// Sets the maximum size of the variables hash table.
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_map_module.html#variables_hash_max_size
|
|
||||||
LimitConnZoneVariable string `json:"limit-conn-zone-variable,omitempty"`
|
|
||||||
|
|
||||||
// Sets the timeout between two successive read or write operations on client or proxied server connections.
|
|
||||||
// If no data is transmitted within this time, the connection is closed.
|
|
||||||
// http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_timeout
|
|
||||||
ProxyStreamTimeout string `json:"proxy-stream-timeout,omitempty"`
|
|
||||||
|
|
||||||
// Sets the ipv4 addresses on which the server will accept requests.
|
|
||||||
BindAddressIpv4 []string `json:"bind-address-ipv4,omitempty"`
|
|
||||||
|
|
||||||
// Sets the ipv6 addresses on which the server will accept requests.
|
|
||||||
BindAddressIpv6 []string `json:"bind-address-ipv6,omitempty"`
|
|
||||||
|
|
||||||
// Sets the header field for identifying the originating IP address of a client
|
|
||||||
// Default is X-Forwarded-For
|
|
||||||
ForwardedForHeader string `json:"forwarded-for-header,omitempty"`
|
|
||||||
|
|
||||||
// EnableOpentracing enables the nginx Opentracing extension
|
|
||||||
// https://github.com/rnburn/nginx-opentracing
|
|
||||||
// By default this is disabled
|
|
||||||
EnableOpentracing bool `json:"enable-opentracing"`
|
|
||||||
|
|
||||||
// ZipkinCollectorHost specifies the host to use when uploading traces
|
|
||||||
ZipkinCollectorHost string `json:"zipkin-collector-host"`
|
|
||||||
|
|
||||||
// ZipkinCollectorPort specifies the port to use when uploading traces
|
|
||||||
ZipkinCollectorPort int `json:"zipkin-collector-port"`
|
|
||||||
|
|
||||||
// ZipkinServiceName specifies the service name to use for any traces created
|
|
||||||
// Default: nginx
|
|
||||||
ZipkinServiceName string `json:"zipkin-service-name"`
|
|
||||||
|
|
||||||
// HTTPSnippet adds custom configuration to the http section of the nginx configuration
|
|
||||||
HTTPSnippet string `json:"http-snippet"`
|
|
||||||
|
|
||||||
// ServerSnippet adds custom configuration to all the servers in the nginx configuration
|
|
||||||
ServerSnippet string `json:"server-snippet"`
|
|
||||||
|
|
||||||
// LocationSnippet adds custom configuration to all the locations in the nginx configuration
|
|
||||||
LocationSnippet string `json:"location-snippet"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefault returns the default nginx configuration
|
|
||||||
func NewDefault() Configuration {
|
|
||||||
defIPCIDR := make([]string, 0)
|
|
||||||
defIPCIDR = append(defIPCIDR, "0.0.0.0/0")
|
|
||||||
defBindAddress := make([]string, 0)
|
|
||||||
cfg := Configuration{
|
|
||||||
AllowBackendServerHeader: false,
|
|
||||||
AccessLogPath: "/var/log/nginx/access.log",
|
|
||||||
ErrorLogPath: "/var/log/nginx/error.log",
|
|
||||||
ClientHeaderBufferSize: "1k",
|
|
||||||
ClientHeaderTimeout: 60,
|
|
||||||
ClientBodyBufferSize: "8k",
|
|
||||||
ClientBodyTimeout: 60,
|
|
||||||
EnableDynamicTLSRecords: true,
|
|
||||||
EnableUnderscoresInHeaders: false,
|
|
||||||
ErrorLogLevel: errorLevel,
|
|
||||||
ForwardedForHeader: "X-Forwarded-For",
|
|
||||||
HTTP2MaxFieldSize: "4k",
|
|
||||||
HTTP2MaxHeaderSize: "16k",
|
|
||||||
HSTS: true,
|
|
||||||
HSTSIncludeSubdomains: true,
|
|
||||||
HSTSMaxAge: hstsMaxAge,
|
|
||||||
HSTSPreload: false,
|
|
||||||
IgnoreInvalidHeaders: true,
|
|
||||||
GzipTypes: gzipTypes,
|
|
||||||
KeepAlive: 75,
|
|
||||||
KeepAliveRequests: 100,
|
|
||||||
LargeClientHeaderBuffers: "4 8k",
|
|
||||||
LogFormatEscapeJSON: false,
|
|
||||||
LogFormatStream: logFormatStream,
|
|
||||||
LogFormatUpstream: logFormatUpstream,
|
|
||||||
MaxWorkerConnections: 16384,
|
|
||||||
MapHashBucketSize: 64,
|
|
||||||
ProxyRealIPCIDR: defIPCIDR,
|
|
||||||
ServerNameHashMaxSize: 1024,
|
|
||||||
ProxyHeadersHashMaxSize: 512,
|
|
||||||
ProxyHeadersHashBucketSize: 64,
|
|
||||||
ShowServerTokens: true,
|
|
||||||
SSLBufferSize: sslBufferSize,
|
|
||||||
SSLCiphers: sslCiphers,
|
|
||||||
SSLECDHCurve: "auto",
|
|
||||||
SSLProtocols: sslProtocols,
|
|
||||||
SSLSessionCache: true,
|
|
||||||
SSLSessionCacheSize: sslSessionCacheSize,
|
|
||||||
SSLSessionTickets: true,
|
|
||||||
SSLSessionTimeout: sslSessionTimeout,
|
|
||||||
UseGzip: true,
|
|
||||||
WorkerProcesses: strconv.Itoa(runtime.NumCPU()),
|
|
||||||
WorkerShutdownTimeout: "10s",
|
|
||||||
LoadBalanceAlgorithm: defaultLoadBalancerAlgorithm,
|
|
||||||
VtsStatusZoneSize: "10m",
|
|
||||||
VtsDefaultFilterKey: "$geoip_country_code country::*",
|
|
||||||
VariablesHashBucketSize: 64,
|
|
||||||
VariablesHashMaxSize: 2048,
|
|
||||||
UseHTTP2: true,
|
|
||||||
ProxyStreamTimeout: "600s",
|
|
||||||
Backend: defaults.Backend{
|
|
||||||
ProxyBodySize: bodySize,
|
|
||||||
ProxyConnectTimeout: 5,
|
|
||||||
ProxyReadTimeout: 60,
|
|
||||||
ProxySendTimeout: 60,
|
|
||||||
ProxyBufferSize: "4k",
|
|
||||||
ProxyCookieDomain: "off",
|
|
||||||
ProxyCookiePath: "off",
|
|
||||||
ProxyNextUpstream: "error timeout invalid_header http_502 http_503 http_504",
|
|
||||||
ProxyRequestBuffering: "on",
|
|
||||||
SSLRedirect: true,
|
|
||||||
CustomHTTPErrors: []int{},
|
|
||||||
WhitelistSourceRange: []string{},
|
|
||||||
SkipAccessLogURLs: []string{},
|
|
||||||
LimitRate: 0,
|
|
||||||
LimitRateAfter: 0,
|
|
||||||
},
|
|
||||||
UpstreamKeepaliveConnections: 32,
|
|
||||||
LimitConnZoneVariable: defaultLimitConnZoneVariable,
|
|
||||||
BindAddressIpv4: defBindAddress,
|
|
||||||
BindAddressIpv6: defBindAddress,
|
|
||||||
ZipkinCollectorPort: 9411,
|
|
||||||
ZipkinServiceName: "nginx",
|
|
||||||
}
|
|
||||||
|
|
||||||
if glog.V(5) {
|
|
||||||
cfg.ErrorLogLevel = "debug"
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
MaxOpenFiles int
|
|
||||||
BacklogSize int
|
|
||||||
Backends []*ingress.Backend
|
|
||||||
PassthroughBackends []*ingress.SSLPassthroughBackend
|
|
||||||
Servers []*ingress.Server
|
|
||||||
TCPBackends []ingress.L4Service
|
|
||||||
UDPBackends []ingress.L4Service
|
|
||||||
HealthzURI string
|
|
||||||
CustomErrors bool
|
|
||||||
Cfg Configuration
|
|
||||||
IsIPV6Enabled bool
|
|
||||||
IsSSLPassthroughEnabled bool
|
|
||||||
RedirectServers map[string]string
|
|
||||||
ListenPorts *ListenPorts
|
|
||||||
PublishService *apiv1.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPorts describe the ports required to run the
|
|
||||||
// NGINX Ingress controller
|
|
||||||
type ListenPorts struct {
|
|
||||||
HTTP int
|
|
||||||
HTTPS int
|
|
||||||
Status int
|
|
||||||
Health int
|
|
||||||
Default int
|
|
||||||
SSLProxy int
|
|
||||||
}
|
|
|
@ -1,41 +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 alias
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/server-alias"
|
|
||||||
)
|
|
||||||
|
|
||||||
type alias struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new Alias annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return alias{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
api "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/file"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
ing_errors "k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
authType = "ingress.kubernetes.io/auth-type"
|
|
||||||
authSecret = "ingress.kubernetes.io/auth-secret"
|
|
||||||
authRealm = "ingress.kubernetes.io/auth-realm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
authTypeRegex = regexp.MustCompile(`basic|digest`)
|
|
||||||
// AuthDirectory default directory used to store files
|
|
||||||
// to authenticate request
|
|
||||||
AuthDirectory = "/etc/ingress-controller/auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BasicDigest returns authentication configuration for an Ingress rule
|
|
||||||
type BasicDigest struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Realm string `json:"realm"`
|
|
||||||
File string `json:"file"`
|
|
||||||
Secured bool `json:"secured"`
|
|
||||||
FileSHA string `json:"fileSha"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two BasicDigest types
|
|
||||||
func (bd1 *BasicDigest) Equal(bd2 *BasicDigest) bool {
|
|
||||||
if bd1 == bd2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if bd1 == nil || bd2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bd1.Type != bd2.Type {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bd1.Realm != bd2.Realm {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bd1.File != bd2.File {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bd1.Secured != bd2.Secured {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bd1.FileSHA != bd2.FileSHA {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type auth struct {
|
|
||||||
secretResolver resolver.Secret
|
|
||||||
authDirectory string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new authentication annotation parser
|
|
||||||
func NewParser(authDirectory string, sr resolver.Secret) parser.IngressAnnotation {
|
|
||||||
os.MkdirAll(authDirectory, 0755)
|
|
||||||
|
|
||||||
currPath := authDirectory
|
|
||||||
for currPath != "/" {
|
|
||||||
currPath = path.Dir(currPath)
|
|
||||||
err := os.Chmod(currPath, 0755)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return auth{sr, authDirectory}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the annotations contained in the ingress
|
|
||||||
// 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) {
|
|
||||||
at, err := parser.GetStringAnnotation(authType, ing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !authTypeRegex.MatchString(at) {
|
|
||||||
return nil, ing_errors.NewLocationDenied("invalid authentication type")
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := parser.GetStringAnnotation(authSecret, ing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ing_errors.LocationDenied{
|
|
||||||
Reason: errors.Wrap(err, "error reading secret name from annotation"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
|
|
||||||
secret, err := a.secretResolver.GetSecret(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ing_errors.LocationDenied{
|
|
||||||
Reason: errors.Wrapf(err, "unexpected error reading secret %v", name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
realm, _ := parser.GetStringAnnotation(authRealm, ing)
|
|
||||||
|
|
||||||
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
|
|
||||||
err = dumpSecret(passFile, secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &BasicDigest{
|
|
||||||
Type: at,
|
|
||||||
Realm: realm,
|
|
||||||
File: passFile,
|
|
||||||
Secured: true,
|
|
||||||
FileSHA: file.SHA1(passFile),
|
|
||||||
}, 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 {
|
|
||||||
val, ok := secret.Data["auth"]
|
|
||||||
if !ok {
|
|
||||||
return ing_errors.LocationDenied{
|
|
||||||
Reason: errors.Errorf("the secret %v does not contain a key with value auth", secret.Name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check permissions required
|
|
||||||
err := ioutil.WriteFile(filename, val, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return ing_errors.LocationDenied{
|
|
||||||
Reason: errors.Wrap(err, "unexpected error creating password file"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,183 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 authreq
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
ing_errors "k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// external URL that provides the authentication
|
|
||||||
authURL = "ingress.kubernetes.io/auth-url"
|
|
||||||
authSigninURL = "ingress.kubernetes.io/auth-signin"
|
|
||||||
authMethod = "ingress.kubernetes.io/auth-method"
|
|
||||||
authBody = "ingress.kubernetes.io/auth-send-body"
|
|
||||||
authHeaders = "ingress.kubernetes.io/auth-response-headers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// External returns external authentication configuration for an Ingress rule
|
|
||||||
type External 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"`
|
|
||||||
SendBody bool `json:"sendBody"`
|
|
||||||
ResponseHeaders []string `json:"responseHeaders,omitEmpty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two External types
|
|
||||||
func (e1 *External) Equal(e2 *External) bool {
|
|
||||||
if e1 == e2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if e1 == nil || e2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.URL != e2.URL {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.Host != e2.Host {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.SigninURL != e2.SigninURL {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.Method != e2.Method {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.SendBody != e2.SendBody {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e1.Method != e2.Method {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ep1 := range e1.ResponseHeaders {
|
|
||||||
found := false
|
|
||||||
for _, ep2 := range e2.ResponseHeaders {
|
|
||||||
if ep1 == ep2 {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
|
|
||||||
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
func validMethod(method string) bool {
|
|
||||||
if len(method) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range methods {
|
|
||||||
if method == m {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func validHeader(header string) bool {
|
|
||||||
return headerRegexp.Match([]byte(header))
|
|
||||||
}
|
|
||||||
|
|
||||||
type authReq struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new authentication request annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return authReq{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
|
||||||
// rule used to use an external URL as source for authentication
|
|
||||||
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
str, err := parser.GetStringAnnotation(authURL, ing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if str == "" {
|
|
||||||
return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
signin, _ := parser.GetStringAnnotation(authSigninURL, ing)
|
|
||||||
|
|
||||||
ur, err := url.Parse(str)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ur.Scheme == "" {
|
|
||||||
return nil, ing_errors.NewLocationDenied("url scheme is empty")
|
|
||||||
}
|
|
||||||
if ur.Host == "" {
|
|
||||||
return nil, ing_errors.NewLocationDenied("url host is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(ur.Host, "..") {
|
|
||||||
return nil, ing_errors.NewLocationDenied("invalid url host")
|
|
||||||
}
|
|
||||||
|
|
||||||
m, _ := parser.GetStringAnnotation(authMethod, ing)
|
|
||||||
if len(m) != 0 && !validMethod(m) {
|
|
||||||
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
|
|
||||||
}
|
|
||||||
|
|
||||||
h := []string{}
|
|
||||||
hstr, _ := parser.GetStringAnnotation(authHeaders, ing)
|
|
||||||
if len(hstr) != 0 {
|
|
||||||
|
|
||||||
harr := strings.Split(hstr, ",")
|
|
||||||
for _, header := range harr {
|
|
||||||
header = strings.TrimSpace(header)
|
|
||||||
if len(header) > 0 {
|
|
||||||
if !validHeader(header) {
|
|
||||||
return nil, ing_errors.NewLocationDenied("invalid headers list")
|
|
||||||
}
|
|
||||||
h = append(h, header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb, _ := parser.GetBoolAnnotation(authBody, ing)
|
|
||||||
|
|
||||||
return &External{
|
|
||||||
URL: str,
|
|
||||||
Host: ur.Hostname(),
|
|
||||||
SigninURL: signin,
|
|
||||||
Method: m,
|
|
||||||
SendBody: sb,
|
|
||||||
ResponseHeaders: h,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 authtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
ing_errors "k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
"k8s.io/ingress-nginx/k8s"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// name of the secret
|
|
||||||
annotationAuthTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
|
||||||
annotationAuthVerifyClient = "ingress.kubernetes.io/auth-tls-verify-client"
|
|
||||||
annotationAuthTLSDepth = "ingress.kubernetes.io/auth-tls-verify-depth"
|
|
||||||
annotationAuthTLSErrorPage = "ingress.kubernetes.io/auth-tls-error-page"
|
|
||||||
defaultAuthTLSDepth = 1
|
|
||||||
defaultAuthVerifyClient = "on"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// AuthSSLConfig contains the AuthSSLCert used for muthual autentication
|
|
||||||
// and the configured ValidationDepth
|
|
||||||
type AuthSSLConfig struct {
|
|
||||||
resolver.AuthSSLCert
|
|
||||||
VerifyClient string `json:"verify_client"`
|
|
||||||
ValidationDepth int `json:"validationDepth"`
|
|
||||||
ErrorPage string `json:"errorPage"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two AuthSSLConfig types
|
|
||||||
func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool {
|
|
||||||
if assl1 == assl2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if assl1 == nil || assl2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !(&assl1.AuthSSLCert).Equal(&assl2.AuthSSLCert) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if assl1.VerifyClient != assl2.VerifyClient {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if assl1.ValidationDepth != assl2.ValidationDepth {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if assl1.ErrorPage != assl2.ErrorPage {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new TLS authentication annotation parser
|
|
||||||
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
|
|
||||||
return authTLS{resolver}
|
|
||||||
}
|
|
||||||
|
|
||||||
type authTLS struct {
|
|
||||||
certResolver resolver.AuthCertificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
|
|
||||||
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
|
|
||||||
if err != nil {
|
|
||||||
return &AuthSSLConfig{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tlsauthsecret == "" {
|
|
||||||
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = k8s.ParseNameNS(tlsauthsecret)
|
|
||||||
if err != nil {
|
|
||||||
return &AuthSSLConfig{}, ing_errors.NewLocationDenied(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsVerifyClient, err := parser.GetStringAnnotation(annotationAuthVerifyClient, ing)
|
|
||||||
if err != nil || !authVerifyClientRegex.MatchString(tlsVerifyClient) {
|
|
||||||
tlsVerifyClient = defaultAuthVerifyClient
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsdepth, err := parser.GetIntAnnotation(annotationAuthTLSDepth, ing)
|
|
||||||
if err != nil || tlsdepth == 0 {
|
|
||||||
tlsdepth = defaultAuthTLSDepth
|
|
||||||
}
|
|
||||||
|
|
||||||
authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret)
|
|
||||||
if err != nil {
|
|
||||||
return &AuthSSLConfig{}, ing_errors.LocationDenied{
|
|
||||||
Reason: errors.Wrap(err, "error obtaining certificate"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorpage, err := parser.GetStringAnnotation(annotationAuthTLSErrorPage, ing)
|
|
||||||
if err != nil || errorpage == "" {
|
|
||||||
errorpage = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return &AuthSSLConfig{
|
|
||||||
AuthSSLCert: *authCert,
|
|
||||||
VerifyClient: tlsVerifyClient,
|
|
||||||
ValidationDepth: tlsdepth,
|
|
||||||
ErrorPage: errorpage,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 class
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/golang/glog"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// IngressKey picks a specific "class" for the Ingress.
|
|
||||||
// The controller only processes Ingresses with this annotation either
|
|
||||||
// unset, or set to either the configured value or the empty string.
|
|
||||||
IngressKey = "kubernetes.io/ingress.class"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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, controller, defClass string) bool {
|
|
||||||
ingress, err := parser.GetStringAnnotation(IngressKey, ing)
|
|
||||||
if err != nil && !errors.IsMissingAnnotations(err) {
|
|
||||||
glog.Warningf("unexpected error reading ingress annotation: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have 2 valid combinations
|
|
||||||
// 1 - ingress with default class | blank annotation on ingress
|
|
||||||
// 2 - ingress with specific class | same annotation on ingress
|
|
||||||
//
|
|
||||||
// and 2 invalid combinations
|
|
||||||
// 3 - ingress with default class | fixed annotation on ingress
|
|
||||||
// 4 - ingress with specific class | different annotation on ingress
|
|
||||||
if ingress == "" && controller == defClass {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return ingress == controller
|
|
||||||
}
|
|
|
@ -1,41 +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 clientbodybuffersize
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/client-body-buffer-size"
|
|
||||||
)
|
|
||||||
|
|
||||||
type clientBodyBufferSize struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new clientBodyBufferSize annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return clientBodyBufferSize{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the annotations contained in the ingress rule
|
|
||||||
// used to add an client-body-buffer-size to the provided locations
|
|
||||||
func (a clientBodyBufferSize) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
return parser.GetStringAnnotation(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,41 +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 cors
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/enable-cors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cors struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new CORS annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return cors{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the annotations contained in the ingress
|
|
||||||
// rule used to indicate if the location/s should allows CORS
|
|
||||||
func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
return parser.GetBoolAnnotation(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 defaultbackend
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultBackend = "ingress.kubernetes.io/default-backend"
|
|
||||||
)
|
|
||||||
|
|
||||||
type backend struct {
|
|
||||||
serviceResolver resolver.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new default backend annotation parser
|
|
||||||
func NewParser(sr resolver.Service) parser.IngressAnnotation {
|
|
||||||
return backend{sr}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses the annotations contained in the ingress to use
|
|
||||||
// a custom default backend
|
|
||||||
func (db backend) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
s, err := parser.GetStringAnnotation(defaultBackend, ing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
|
|
||||||
svc, err := db.serviceResolver.GetService(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "unexpected error reading service %v", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc, nil
|
|
||||||
}
|
|
|
@ -1,66 +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 healthcheck
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
upsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
|
|
||||||
upsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Upstream returns the URL and method to use check the status of
|
|
||||||
// the upstream server/s
|
|
||||||
type Upstream struct {
|
|
||||||
MaxFails int `json:"maxFails"`
|
|
||||||
FailTimeout int `json:"failTimeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type healthCheck struct {
|
|
||||||
backendResolver resolver.DefaultBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new health check annotation parser
|
|
||||||
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
|
||||||
return healthCheck{br}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
|
||||||
// rule used to configure upstream check parameters
|
|
||||||
func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
defBackend := a.backendResolver.GetDefaultBackend()
|
|
||||||
if ing.GetAnnotations() == nil {
|
|
||||||
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
|
|
||||||
if err != nil {
|
|
||||||
mf = defBackend.UpstreamMaxFails
|
|
||||||
}
|
|
||||||
|
|
||||||
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
|
|
||||||
if err != nil {
|
|
||||||
ft = defBackend.UpstreamFailTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Upstream{mf, ft}, nil
|
|
||||||
}
|
|
|
@ -1,92 +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 healthcheck
|
|
||||||
|
|
||||||
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/apimachinery/pkg/util/intstr"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBackend struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|
||||||
return defaults.Backend{UpstreamFailTimeout: 1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIngressHealthCheck(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[upsMaxFails] = "2"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
hzi, _ := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
nginxHz, ok := hzi.(*Upstream)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a Upstream type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if nginxHz.MaxFails != 2 {
|
|
||||||
t.Errorf("expected 2 as max-fails but returned %v", nginxHz.MaxFails)
|
|
||||||
}
|
|
||||||
|
|
||||||
if nginxHz.FailTimeout != 1 {
|
|
||||||
t.Errorf("expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +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 ipwhitelist
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/ingress-nginx/net"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
ing_errors "k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
whitelist = "ingress.kubernetes.io/whitelist-source-range"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SourceRange returns the CIDR
|
|
||||||
type SourceRange struct {
|
|
||||||
CIDR []string `json:"cidr,omitEmpty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two SourceRange types
|
|
||||||
func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool {
|
|
||||||
if sr1 == sr2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if sr1 == nil || sr2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sr1.CIDR) != len(sr2.CIDR) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s1l := range sr1.CIDR {
|
|
||||||
found := false
|
|
||||||
for _, sl2 := range sr2.CIDR {
|
|
||||||
if s1l == sl2 {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type ipwhitelist struct {
|
|
||||||
backendResolver resolver.DefaultBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new whitelist annotation parser
|
|
||||||
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
|
||||||
return ipwhitelist{br}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
|
||||||
// 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) {
|
|
||||||
defBackend := a.backendResolver.GetDefaultBackend()
|
|
||||||
sort.Strings(defBackend.WhitelistSourceRange)
|
|
||||||
|
|
||||||
val, err := parser.GetStringAnnotation(whitelist, ing)
|
|
||||||
// A missing annotation is not a problem, just use the default
|
|
||||||
if err == ing_errors.ErrMissingAnnotations {
|
|
||||||
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
values := strings.Split(val, ",")
|
|
||||||
ipnets, ips, err := net.ParseIPNets(values...)
|
|
||||||
if err != nil && len(ips) == 0 {
|
|
||||||
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, ing_errors.LocationDenied{
|
|
||||||
Reason: errors.Wrap(err, "the annotation does not contain a valid IP address or network"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cidrs := []string{}
|
|
||||||
for k := range ipnets {
|
|
||||||
cidrs = append(cidrs, k)
|
|
||||||
}
|
|
||||||
for k := range ips {
|
|
||||||
cidrs = append(cidrs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(cidrs)
|
|
||||||
|
|
||||||
return &SourceRange{cidrs}, nil
|
|
||||||
}
|
|
|
@ -1,199 +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 ipwhitelist
|
|
||||||
|
|
||||||
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/apimachinery/pkg/util/intstr"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBackend struct {
|
|
||||||
defaults.Backend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|
||||||
return m.Backend
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseAnnotations(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
tests := map[string]struct {
|
|
||||||
net string
|
|
||||||
expectCidr []string
|
|
||||||
expectErr bool
|
|
||||||
errOut string
|
|
||||||
}{
|
|
||||||
"test parse a valid net": {
|
|
||||||
net: "10.0.0.0/24",
|
|
||||||
expectCidr: []string{"10.0.0.0/24"},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
"test parse a invalid net": {
|
|
||||||
net: "ww",
|
|
||||||
expectErr: true,
|
|
||||||
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ww",
|
|
||||||
},
|
|
||||||
"test parse a empty net": {
|
|
||||||
net: "",
|
|
||||||
expectErr: true,
|
|
||||||
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ",
|
|
||||||
},
|
|
||||||
"test parse multiple valid cidr": {
|
|
||||||
net: "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24",
|
|
||||||
expectCidr: []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for testName, test := range tests {
|
|
||||||
data := map[string]string{}
|
|
||||||
data[whitelist] = test.net
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
p := NewParser(mockBackend{})
|
|
||||||
i, err := p.Parse(ing)
|
|
||||||
if err != nil && !test.expectErr {
|
|
||||||
t.Errorf("%v:unexpected error: %v", testName, err)
|
|
||||||
}
|
|
||||||
if test.expectErr {
|
|
||||||
if err.Error() != test.errOut {
|
|
||||||
t.Errorf("%v:expected error: %v but %v return", testName, test.errOut, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !test.expectErr {
|
|
||||||
sr, ok := i.(*SourceRange)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%v:expected a SourceRange type", testName)
|
|
||||||
}
|
|
||||||
if !strsEquals(sr.CIDR, test.expectCidr) {
|
|
||||||
t.Errorf("%v:expected %v CIDR but %v returned", testName, test.expectCidr, sr.CIDR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test that when we have a whitelist set on the Backend that is used when we
|
|
||||||
// don't have the annotation
|
|
||||||
func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
mockBackend := mockBackend{}
|
|
||||||
mockBackend.Backend.WhitelistSourceRange = []string{"4.4.4.0/24", "1.2.3.4/32"}
|
|
||||||
tests := map[string]struct {
|
|
||||||
net string
|
|
||||||
expectCidr []string
|
|
||||||
expectErr bool
|
|
||||||
errOut string
|
|
||||||
}{
|
|
||||||
"test parse a valid net": {
|
|
||||||
net: "10.0.0.0/24",
|
|
||||||
expectCidr: []string{"10.0.0.0/24"},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
"test parse a invalid net": {
|
|
||||||
net: "ww",
|
|
||||||
expectErr: true,
|
|
||||||
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ww",
|
|
||||||
},
|
|
||||||
"test parse a empty net": {
|
|
||||||
net: "",
|
|
||||||
expectErr: true,
|
|
||||||
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ",
|
|
||||||
},
|
|
||||||
"test parse multiple valid cidr": {
|
|
||||||
net: "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24",
|
|
||||||
expectCidr: []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for testName, test := range tests {
|
|
||||||
data := map[string]string{}
|
|
||||||
data[whitelist] = test.net
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
p := NewParser(mockBackend)
|
|
||||||
i, err := p.Parse(ing)
|
|
||||||
if err != nil && !test.expectErr {
|
|
||||||
t.Errorf("%v:unexpected error: %v", testName, err)
|
|
||||||
}
|
|
||||||
if test.expectErr {
|
|
||||||
if err.Error() != test.errOut {
|
|
||||||
t.Errorf("%v:expected error: %v but %v return", testName, test.errOut, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !test.expectErr {
|
|
||||||
sr, ok := i.(*SourceRange)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("%v:expected a SourceRange type", testName)
|
|
||||||
}
|
|
||||||
if !strsEquals(sr.CIDR, test.expectCidr) {
|
|
||||||
t.Errorf("%v:expected %v CIDR but %v returned", testName, test.expectCidr, sr.CIDR)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func strsEquals(a, b []string) bool {
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, v := range a {
|
|
||||||
if v != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 parser
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IngressAnnotation has a method to parse annotations located in Ingress
|
|
||||||
type IngressAnnotation interface {
|
|
||||||
Parse(ing *extensions.Ingress) (interface{}, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ingAnnotations map[string]string
|
|
||||||
|
|
||||||
func (a ingAnnotations) parseBool(name string) (bool, error) {
|
|
||||||
val, ok := a[name]
|
|
||||||
if ok {
|
|
||||||
b, err := strconv.ParseBool(val)
|
|
||||||
if err != nil {
|
|
||||||
return false, errors.NewInvalidAnnotationContent(name, val)
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
return false, errors.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ingAnnotations) parseString(name string) (string, error) {
|
|
||||||
val, ok := a[name]
|
|
||||||
if ok {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
return "", errors.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a ingAnnotations) parseInt(name string) (int, error) {
|
|
||||||
val, ok := a[name]
|
|
||||||
if ok {
|
|
||||||
i, err := strconv.Atoi(val)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.NewInvalidAnnotationContent(name, val)
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
return 0, errors.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAnnotation(name string, ing *extensions.Ingress) error {
|
|
||||||
if ing == nil || len(ing.GetAnnotations()) == 0 {
|
|
||||||
return errors.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
if name == "" {
|
|
||||||
return errors.ErrInvalidAnnotationName
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBoolAnnotation extracts a boolean from an Ingress annotation
|
|
||||||
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
|
|
||||||
err := checkAnnotation(name, ing)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return ingAnnotations(ing.GetAnnotations()).parseBool(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStringAnnotation extracts a string from an Ingress annotation
|
|
||||||
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
|
|
||||||
err := checkAnnotation(name, ing)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return ingAnnotations(ing.GetAnnotations()).parseString(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIntAnnotation extracts an int from an Ingress annotation
|
|
||||||
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
|
|
||||||
err := checkAnnotation(name, ing)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
|
|
||||||
}
|
|
|
@ -1,48 +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 portinredirect
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/use-port-in-redirects"
|
|
||||||
)
|
|
||||||
|
|
||||||
type portInRedirect struct {
|
|
||||||
backendResolver resolver.DefaultBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new port in redirect annotation parser
|
|
||||||
func NewParser(db resolver.DefaultBackend) parser.IngressAnnotation {
|
|
||||||
return portInRedirect{db}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
up, err := parser.GetBoolAnnotation(annotation, ing)
|
|
||||||
if err != nil {
|
|
||||||
return a.backendResolver.GetDefaultBackend().UsePortInRedirects, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return up, nil
|
|
||||||
}
|
|
|
@ -1,121 +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 portinredirect
|
|
||||||
|
|
||||||
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/apimachinery/pkg/util/intstr"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBackend struct {
|
|
||||||
usePortInRedirects bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|
||||||
return defaults.Backend{UsePortInRedirects: m.usePortInRedirects}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPortInRedirect(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
title string
|
|
||||||
usePort *bool
|
|
||||||
def bool
|
|
||||||
exp bool
|
|
||||||
}{
|
|
||||||
{"false - default false", newFalse(), false, false},
|
|
||||||
{"false - default true", newFalse(), true, false},
|
|
||||||
{"no annotation - default false", nil, false, false},
|
|
||||||
{"no annotation - default true", nil, true, true},
|
|
||||||
{"true - default true", newTrue(), true, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
if test.usePort != nil {
|
|
||||||
data[annotation] = fmt.Sprintf("%v", *test.usePort)
|
|
||||||
}
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, err := NewParser(mockBackend{test.def}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error parsing a valid")
|
|
||||||
}
|
|
||||||
p, ok := i.(bool)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a bool type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p != test.exp {
|
|
||||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.exp, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTrue() *bool {
|
|
||||||
b := true
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFalse() *bool {
|
|
||||||
b := false
|
|
||||||
return &b
|
|
||||||
}
|
|
|
@ -1,160 +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 proxy
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
bodySize = "ingress.kubernetes.io/proxy-body-size"
|
|
||||||
connect = "ingress.kubernetes.io/proxy-connect-timeout"
|
|
||||||
send = "ingress.kubernetes.io/proxy-send-timeout"
|
|
||||||
read = "ingress.kubernetes.io/proxy-read-timeout"
|
|
||||||
bufferSize = "ingress.kubernetes.io/proxy-buffer-size"
|
|
||||||
cookiePath = "ingress.kubernetes.io/proxy-cookie-path"
|
|
||||||
cookieDomain = "ingress.kubernetes.io/proxy-cookie-domain"
|
|
||||||
nextUpstream = "ingress.kubernetes.io/proxy-next-upstream"
|
|
||||||
passParams = "ingress.kubernetes.io/proxy-pass-params"
|
|
||||||
requestBuffering = "ingress.kubernetes.io/proxy-request-buffering"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configuration returns the proxy timeout to use in the upstream server/s
|
|
||||||
type Configuration struct {
|
|
||||||
BodySize string `json:"bodySize"`
|
|
||||||
ConnectTimeout int `json:"connectTimeout"`
|
|
||||||
SendTimeout int `json:"sendTimeout"`
|
|
||||||
ReadTimeout int `json:"readTimeout"`
|
|
||||||
BufferSize string `json:"bufferSize"`
|
|
||||||
CookieDomain string `json:"cookieDomain"`
|
|
||||||
CookiePath string `json:"cookiePath"`
|
|
||||||
NextUpstream string `json:"nextUpstream"`
|
|
||||||
PassParams string `json:"passParams"`
|
|
||||||
RequestBuffering string `json:"requestBuffering"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two Configuration types
|
|
||||||
func (l1 *Configuration) Equal(l2 *Configuration) bool {
|
|
||||||
if l1 == l2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if l1 == nil || l2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.BodySize != l2.BodySize {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.ConnectTimeout != l2.ConnectTimeout {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.SendTimeout != l2.SendTimeout {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.ReadTimeout != l2.ReadTimeout {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.BufferSize != l2.BufferSize {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.CookieDomain != l2.CookieDomain {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.CookiePath != l2.CookiePath {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.NextUpstream != l2.NextUpstream {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if l1.PassParams != l2.PassParams {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if l1.RequestBuffering != l2.RequestBuffering {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type proxy struct {
|
|
||||||
backendResolver resolver.DefaultBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new reverse proxy configuration annotation parser
|
|
||||||
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
|
||||||
return proxy{br}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
|
||||||
// rule used to configure upstream check parameters
|
|
||||||
func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
defBackend := a.backendResolver.GetDefaultBackend()
|
|
||||||
ct, err := parser.GetIntAnnotation(connect, ing)
|
|
||||||
if err != nil {
|
|
||||||
ct = defBackend.ProxyConnectTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
st, err := parser.GetIntAnnotation(send, ing)
|
|
||||||
if err != nil {
|
|
||||||
st = defBackend.ProxySendTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
rt, err := parser.GetIntAnnotation(read, ing)
|
|
||||||
if err != nil {
|
|
||||||
rt = defBackend.ProxyReadTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
bufs, err := parser.GetStringAnnotation(bufferSize, ing)
|
|
||||||
if err != nil || bufs == "" {
|
|
||||||
bufs = defBackend.ProxyBufferSize
|
|
||||||
}
|
|
||||||
|
|
||||||
cp, err := parser.GetStringAnnotation(cookiePath, ing)
|
|
||||||
if err != nil || cp == "" {
|
|
||||||
cp = defBackend.ProxyCookiePath
|
|
||||||
}
|
|
||||||
|
|
||||||
cd, err := parser.GetStringAnnotation(cookieDomain, ing)
|
|
||||||
if err != nil || cd == "" {
|
|
||||||
cd = defBackend.ProxyCookieDomain
|
|
||||||
}
|
|
||||||
|
|
||||||
bs, err := parser.GetStringAnnotation(bodySize, ing)
|
|
||||||
if err != nil || bs == "" {
|
|
||||||
bs = defBackend.ProxyBodySize
|
|
||||||
}
|
|
||||||
|
|
||||||
nu, err := parser.GetStringAnnotation(nextUpstream, ing)
|
|
||||||
if err != nil || nu == "" {
|
|
||||||
nu = defBackend.ProxyNextUpstream
|
|
||||||
}
|
|
||||||
|
|
||||||
pp, err := parser.GetStringAnnotation(passParams, ing)
|
|
||||||
if err != nil || pp == "" {
|
|
||||||
pp = defBackend.ProxyPassParams
|
|
||||||
}
|
|
||||||
|
|
||||||
rb, err := parser.GetStringAnnotation(requestBuffering, ing)
|
|
||||||
if err != nil || rb == "" {
|
|
||||||
rb = defBackend.ProxyRequestBuffering
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Configuration{bs, ct, st, rt, bufs, cd, cp, nu, pp, rb}, nil
|
|
||||||
}
|
|
|
@ -1,168 +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 proxy
|
|
||||||
|
|
||||||
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/apimachinery/pkg/util/intstr"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBackend struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|
||||||
return defaults.Backend{
|
|
||||||
UpstreamFailTimeout: 1,
|
|
||||||
ProxyConnectTimeout: 10,
|
|
||||||
ProxySendTimeout: 15,
|
|
||||||
ProxyReadTimeout: 20,
|
|
||||||
ProxyBufferSize: "10k",
|
|
||||||
ProxyBodySize: "3k",
|
|
||||||
ProxyNextUpstream: "error",
|
|
||||||
ProxyPassParams: "nocanon keepalive=On",
|
|
||||||
ProxyRequestBuffering: "on",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxy(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[connect] = "1"
|
|
||||||
data[send] = "2"
|
|
||||||
data[read] = "3"
|
|
||||||
data[bufferSize] = "1k"
|
|
||||||
data[bodySize] = "2k"
|
|
||||||
data[nextUpstream] = "off"
|
|
||||||
data[passParams] = "smax=5 max=10"
|
|
||||||
data[requestBuffering] = "off"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error parsing a valid")
|
|
||||||
}
|
|
||||||
p, ok := i.(*Configuration)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected a Configuration type")
|
|
||||||
}
|
|
||||||
if p.ConnectTimeout != 1 {
|
|
||||||
t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
|
|
||||||
}
|
|
||||||
if p.SendTimeout != 2 {
|
|
||||||
t.Errorf("expected 2 as send-timeout but returned %v", p.SendTimeout)
|
|
||||||
}
|
|
||||||
if p.ReadTimeout != 3 {
|
|
||||||
t.Errorf("expected 3 as read-timeout but returned %v", p.ReadTimeout)
|
|
||||||
}
|
|
||||||
if p.BufferSize != "1k" {
|
|
||||||
t.Errorf("expected 1k as buffer-size but returned %v", p.BufferSize)
|
|
||||||
}
|
|
||||||
if p.BodySize != "2k" {
|
|
||||||
t.Errorf("expected 2k as body-size but returned %v", p.BodySize)
|
|
||||||
}
|
|
||||||
if p.NextUpstream != "off" {
|
|
||||||
t.Errorf("expected off as next-upstream but returned %v", p.NextUpstream)
|
|
||||||
}
|
|
||||||
if p.PassParams != "smax=5 max=10" {
|
|
||||||
t.Errorf("expected \"smax=5 max=10\" as pass-params but returned \"%v\"", p.PassParams)
|
|
||||||
}
|
|
||||||
if p.RequestBuffering != "off" {
|
|
||||||
t.Errorf("expected off as request-buffering but returned %v", p.RequestBuffering)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProxyWithNoAnnotation(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error parsing a valid")
|
|
||||||
}
|
|
||||||
p, ok := i.(*Configuration)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("expected a Configuration type")
|
|
||||||
}
|
|
||||||
if p.ConnectTimeout != 10 {
|
|
||||||
t.Errorf("expected 10 as connect-timeout but returned %v", p.ConnectTimeout)
|
|
||||||
}
|
|
||||||
if p.SendTimeout != 15 {
|
|
||||||
t.Errorf("expected 15 as send-timeout but returned %v", p.SendTimeout)
|
|
||||||
}
|
|
||||||
if p.ReadTimeout != 20 {
|
|
||||||
t.Errorf("expected 20 as read-timeout but returned %v", p.ReadTimeout)
|
|
||||||
}
|
|
||||||
if p.BufferSize != "10k" {
|
|
||||||
t.Errorf("expected 10k as buffer-size but returned %v", p.BufferSize)
|
|
||||||
}
|
|
||||||
if p.BodySize != "3k" {
|
|
||||||
t.Errorf("expected 3k as body-size but returned %v", p.BodySize)
|
|
||||||
}
|
|
||||||
if p.NextUpstream != "error" {
|
|
||||||
t.Errorf("expected error as next-upstream but returned %v", p.NextUpstream)
|
|
||||||
}
|
|
||||||
if p.PassParams != "nocanon keepalive=On" {
|
|
||||||
t.Errorf("expected \"nocanon keepalive=On\" as pass-params but returned \"%v\"", p.PassParams)
|
|
||||||
}
|
|
||||||
if p.RequestBuffering != "on" {
|
|
||||||
t.Errorf("expected on as request-buffering but returned %v", p.RequestBuffering)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,255 +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 ratelimit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
"k8s.io/ingress-nginx/net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
limitIP = "ingress.kubernetes.io/limit-connections"
|
|
||||||
limitRPS = "ingress.kubernetes.io/limit-rps"
|
|
||||||
limitRPM = "ingress.kubernetes.io/limit-rpm"
|
|
||||||
limitRATE = "ingress.kubernetes.io/limit-rate"
|
|
||||||
limitRATEAFTER = "ingress.kubernetes.io/limit-rate-after"
|
|
||||||
limitWhitelist = "ingress.kubernetes.io/limit-whitelist"
|
|
||||||
|
|
||||||
// allow 5 times the specified limit as burst
|
|
||||||
defBurst = 5
|
|
||||||
|
|
||||||
// 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states
|
|
||||||
// default is 5MB
|
|
||||||
defSharedSize = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
// RateLimit returns rate limit configuration for an Ingress rule limiting the
|
|
||||||
// number of connections per IP address and/or connections per second.
|
|
||||||
// If you both annotations are specified in a single Ingress rule, RPS limits
|
|
||||||
// takes precedence
|
|
||||||
type RateLimit struct {
|
|
||||||
// Connections indicates a limit with the number of connections per IP address
|
|
||||||
Connections Zone `json:"connections"`
|
|
||||||
// RPS indicates a limit with the number of connections per second
|
|
||||||
RPS Zone `json:"rps"`
|
|
||||||
|
|
||||||
RPM Zone `json:"rpm"`
|
|
||||||
|
|
||||||
LimitRate int `json:"limit-rate"`
|
|
||||||
|
|
||||||
LimitRateAfter int `json:"limit-rate-after"`
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
ID string `json:"id"`
|
|
||||||
|
|
||||||
Whitelist []string `json:"whitelist"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two RateLimit types
|
|
||||||
func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
|
|
||||||
if rt1 == rt2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if rt1 == nil || rt2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !(&rt1.Connections).Equal(&rt2.Connections) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !(&rt1.RPM).Equal(&rt2.RPM) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !(&rt1.RPS).Equal(&rt2.RPS) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if rt1.LimitRate != rt2.LimitRate {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if rt1.LimitRateAfter != rt2.LimitRateAfter {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if rt1.ID != rt2.ID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if rt1.Name != rt2.Name {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(rt1.Whitelist) != len(rt2.Whitelist) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, r1l := range rt1.Whitelist {
|
|
||||||
found := false
|
|
||||||
for _, rl2 := range rt2.Whitelist {
|
|
||||||
if r1l == rl2 {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zone returns information about the NGINX rate limit (limit_req_zone)
|
|
||||||
// http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
|
|
||||||
type Zone struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Limit int `json:"limit"`
|
|
||||||
Burst int `json:"burst"`
|
|
||||||
// SharedSize amount of shared memory for the zone
|
|
||||||
SharedSize int `json:"sharedSize"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two Zone types
|
|
||||||
func (z1 *Zone) Equal(z2 *Zone) bool {
|
|
||||||
if z1 == z2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if z1 == nil || z2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if z1.Name != z2.Name {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if z1.Limit != z2.Limit {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if z1.Burst != z2.Burst {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if z1.SharedSize != z2.SharedSize {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type ratelimit struct {
|
|
||||||
backendResolver resolver.DefaultBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new ratelimit annotation parser
|
|
||||||
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
|
||||||
return ratelimit{br}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
|
||||||
// rule used to rewrite the defined paths
|
|
||||||
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
defBackend := a.backendResolver.GetDefaultBackend()
|
|
||||||
lr, err := parser.GetIntAnnotation(limitRATE, ing)
|
|
||||||
if err != nil {
|
|
||||||
lr = defBackend.LimitRate
|
|
||||||
}
|
|
||||||
lra, err := parser.GetIntAnnotation(limitRATEAFTER, ing)
|
|
||||||
if err != nil {
|
|
||||||
lra = defBackend.LimitRateAfter
|
|
||||||
}
|
|
||||||
|
|
||||||
rpm, _ := parser.GetIntAnnotation(limitRPM, ing)
|
|
||||||
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
|
|
||||||
conn, _ := parser.GetIntAnnotation(limitIP, ing)
|
|
||||||
|
|
||||||
val, _ := parser.GetStringAnnotation(limitWhitelist, ing)
|
|
||||||
|
|
||||||
cidrs, err := parseCIDRs(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if rpm == 0 && rps == 0 && conn == 0 {
|
|
||||||
return &RateLimit{
|
|
||||||
Connections: Zone{},
|
|
||||||
RPS: Zone{},
|
|
||||||
RPM: Zone{},
|
|
||||||
LimitRate: lr,
|
|
||||||
LimitRateAfter: lra,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
|
|
||||||
|
|
||||||
return &RateLimit{
|
|
||||||
Connections: Zone{
|
|
||||||
Name: fmt.Sprintf("%v_conn", zoneName),
|
|
||||||
Limit: conn,
|
|
||||||
Burst: conn * defBurst,
|
|
||||||
SharedSize: defSharedSize,
|
|
||||||
},
|
|
||||||
RPS: Zone{
|
|
||||||
Name: fmt.Sprintf("%v_rps", zoneName),
|
|
||||||
Limit: rps,
|
|
||||||
Burst: rps * defBurst,
|
|
||||||
SharedSize: defSharedSize,
|
|
||||||
},
|
|
||||||
RPM: Zone{
|
|
||||||
Name: fmt.Sprintf("%v_rpm", zoneName),
|
|
||||||
Limit: rpm,
|
|
||||||
Burst: rpm * defBurst,
|
|
||||||
SharedSize: defSharedSize,
|
|
||||||
},
|
|
||||||
LimitRate: lr,
|
|
||||||
LimitRateAfter: lra,
|
|
||||||
Name: zoneName,
|
|
||||||
ID: encode(zoneName),
|
|
||||||
Whitelist: cidrs,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCIDRs(s string) ([]string, error) {
|
|
||||||
if s == "" {
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
values := strings.Split(s, ",")
|
|
||||||
|
|
||||||
ipnets, ips, err := net.ParseIPNets(values...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cidrs := []string{}
|
|
||||||
for k := range ipnets {
|
|
||||||
cidrs = append(cidrs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range ips {
|
|
||||||
cidrs = append(cidrs, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(cidrs)
|
|
||||||
|
|
||||||
return cidrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(s string) string {
|
|
||||||
str := base64.URLEncoding.EncodeToString([]byte(s))
|
|
||||||
return strings.Replace(str, "=", "", -1)
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 ratelimit
|
|
||||||
|
|
||||||
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/apimachinery/pkg/util/intstr"
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBackend struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|
||||||
return defaults.Backend{
|
|
||||||
LimitRateAfter: 0,
|
|
||||||
LimitRate: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithoutAnnotations(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("unexpected error with ingress without annotations")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBadRateLimiting(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[limitIP] = "0"
|
|
||||||
data[limitRPS] = "0"
|
|
||||||
data[limitRPM] = "0"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error with invalid limits (0)")
|
|
||||||
}
|
|
||||||
|
|
||||||
data = map[string]string{}
|
|
||||||
data[limitIP] = "5"
|
|
||||||
data[limitRPS] = "100"
|
|
||||||
data[limitRPM] = "10"
|
|
||||||
data[limitRATEAFTER] = "100"
|
|
||||||
data[limitRATE] = "10"
|
|
||||||
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
rateLimit, ok := i.(*RateLimit)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a RateLimit type")
|
|
||||||
}
|
|
||||||
if rateLimit.Connections.Limit != 5 {
|
|
||||||
t.Errorf("expected 5 in limit by ip but %v was returend", rateLimit.Connections)
|
|
||||||
}
|
|
||||||
if rateLimit.RPS.Limit != 100 {
|
|
||||||
t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS)
|
|
||||||
}
|
|
||||||
if rateLimit.RPM.Limit != 10 {
|
|
||||||
t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM)
|
|
||||||
}
|
|
||||||
if rateLimit.LimitRateAfter != 100 {
|
|
||||||
t.Errorf("expected 100 in limit by limitrateafter but %v was returend", rateLimit.LimitRateAfter)
|
|
||||||
}
|
|
||||||
if rateLimit.LimitRate != 10 {
|
|
||||||
t.Errorf("expected 10 in limit by limitrate but %v was returend", rateLimit.LimitRate)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +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 rewrite
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
rewriteTo = "ingress.kubernetes.io/rewrite-target"
|
|
||||||
addBaseURL = "ingress.kubernetes.io/add-base-url"
|
|
||||||
baseURLScheme = "ingress.kubernetes.io/base-url-scheme"
|
|
||||||
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
|
|
||||||
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
|
|
||||||
appRoot = "ingress.kubernetes.io/app-root"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Redirect describes the per location redirect config
|
|
||||||
type Redirect struct {
|
|
||||||
// Target URI where the traffic must be redirected
|
|
||||||
Target string `json:"target"`
|
|
||||||
// AddBaseURL indicates if is required to add a base tag in the head
|
|
||||||
// of the responses from the upstream servers
|
|
||||||
AddBaseURL bool `json:"addBaseUrl"`
|
|
||||||
// BaseURLScheme override for the scheme passed to the base tag
|
|
||||||
BaseURLScheme string `json:"baseUrlScheme"`
|
|
||||||
// SSLRedirect indicates if the location section is accessible SSL only
|
|
||||||
SSLRedirect bool `json:"sslRedirect"`
|
|
||||||
// ForceSSLRedirect indicates if the location section is accessible SSL only
|
|
||||||
ForceSSLRedirect bool `json:"forceSSLRedirect"`
|
|
||||||
// AppRoot defines the Application Root that the Controller must redirect if it's not in '/' context
|
|
||||||
AppRoot string `json:"appRoot"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two Redirect types
|
|
||||||
func (r1 *Redirect) Equal(r2 *Redirect) bool {
|
|
||||||
if r1 == r2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if r1 == nil || r2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r1.Target != r2.Target {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r1.AddBaseURL != r2.AddBaseURL {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r1.BaseURLScheme != r2.BaseURLScheme {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r1.SSLRedirect != r2.SSLRedirect {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r1.ForceSSLRedirect != r2.ForceSSLRedirect {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if r1.AppRoot != r2.AppRoot {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type rewrite struct {
|
|
||||||
backendResolver resolver.DefaultBackend
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new reqrite annotation parser
|
|
||||||
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
|
||||||
return rewrite{br}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
|
||||||
// rule used to rewrite the defined paths
|
|
||||||
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
|
||||||
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
|
||||||
if err != nil {
|
|
||||||
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
|
|
||||||
}
|
|
||||||
fSslRe, err := parser.GetBoolAnnotation(forceSSLRedirect, ing)
|
|
||||||
if err != nil {
|
|
||||||
fSslRe = a.backendResolver.GetDefaultBackend().ForceSSLRedirect
|
|
||||||
}
|
|
||||||
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
|
||||||
bus, _ := parser.GetStringAnnotation(baseURLScheme, ing)
|
|
||||||
ar, _ := parser.GetStringAnnotation(appRoot, ing)
|
|
||||||
return &Redirect{
|
|
||||||
Target: rt,
|
|
||||||
AddBaseURL: abu,
|
|
||||||
BaseURLScheme: bus,
|
|
||||||
SSLRedirect: sslRe,
|
|
||||||
ForceSSLRedirect: fSslRe,
|
|
||||||
AppRoot: ar,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 rewrite
|
|
||||||
|
|
||||||
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/apimachinery/pkg/util/intstr"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defRoute = "/demo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockBackend struct {
|
|
||||||
redirect bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|
||||||
return defaults.Backend{SSLRedirect: m.redirect}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithoutAnnotations(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error with ingress without annotations: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRedirect(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[rewriteTo] = defRoute
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unexpected error with ingress: %v", err)
|
|
||||||
}
|
|
||||||
redirect, ok := i.(*Redirect)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a Redirect type")
|
|
||||||
}
|
|
||||||
if redirect.Target != defRoute {
|
|
||||||
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSSLRedirect(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[rewriteTo] = defRoute
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, _ := NewParser(mockBackend{true}).Parse(ing)
|
|
||||||
redirect, ok := i.(*Redirect)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a Redirect type")
|
|
||||||
}
|
|
||||||
if !redirect.SSLRedirect {
|
|
||||||
t.Errorf("Expected true but returned false")
|
|
||||||
}
|
|
||||||
|
|
||||||
data[sslRedirect] = "false"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, _ = NewParser(mockBackend{false}).Parse(ing)
|
|
||||||
redirect, ok = i.(*Redirect)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a Redirect type")
|
|
||||||
}
|
|
||||||
if redirect.SSLRedirect {
|
|
||||||
t.Errorf("Expected false but returned true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForceSSLRedirect(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[rewriteTo] = defRoute
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, _ := NewParser(mockBackend{true}).Parse(ing)
|
|
||||||
redirect, ok := i.(*Redirect)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a Redirect type")
|
|
||||||
}
|
|
||||||
if redirect.ForceSSLRedirect {
|
|
||||||
t.Errorf("Expected false but returned true")
|
|
||||||
}
|
|
||||||
|
|
||||||
data[forceSSLRedirect] = "true"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, _ = NewParser(mockBackend{false}).Parse(ing)
|
|
||||||
redirect, ok = i.(*Redirect)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a Redirect type")
|
|
||||||
}
|
|
||||||
if !redirect.ForceSSLRedirect {
|
|
||||||
t.Errorf("Expected true but returned false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestAppRoot(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
|
|
||||||
data := map[string]string{}
|
|
||||||
data[appRoot] = "/app1"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
|
|
||||||
i, _ := NewParser(mockBackend{true}).Parse(ing)
|
|
||||||
redirect, ok := i.(*Redirect)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected a App Context")
|
|
||||||
}
|
|
||||||
if redirect.AppRoot != "/app1" {
|
|
||||||
t.Errorf("Unexpected value got in AppRoot")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,78 +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 secureupstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
secureUpstream = "ingress.kubernetes.io/secure-backends"
|
|
||||||
secureVerifyCASecret = "ingress.kubernetes.io/secure-verify-ca-secret"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Secure describes SSL backend configuration
|
|
||||||
type Secure struct {
|
|
||||||
Secure bool `json:"secure"`
|
|
||||||
CACert resolver.AuthSSLCert `json:"caCert"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type su struct {
|
|
||||||
certResolver resolver.AuthCertificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new secure upstream annotation parser
|
|
||||||
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
|
|
||||||
return su{
|
|
||||||
certResolver: resolver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
s, _ := parser.GetBoolAnnotation(secureUpstream, ing)
|
|
||||||
ca, _ := parser.GetStringAnnotation(secureVerifyCASecret, ing)
|
|
||||||
secure := &Secure{
|
|
||||||
Secure: s,
|
|
||||||
CACert: resolver.AuthSSLCert{},
|
|
||||||
}
|
|
||||||
if !s && 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.certResolver.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 &Secure{
|
|
||||||
Secure: s,
|
|
||||||
CACert: *caCert,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,121 +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 secureupstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
api "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
|
||||||
defaultBackend := extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions.Ingress{
|
|
||||||
ObjectMeta: meta_v1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Spec: extensions.IngressSpec{
|
|
||||||
Backend: &extensions.IngressBackend{
|
|
||||||
ServiceName: "default-backend",
|
|
||||||
ServicePort: intstr.FromInt(80),
|
|
||||||
},
|
|
||||||
Rules: []extensions.IngressRule{
|
|
||||||
{
|
|
||||||
Host: "foo.bar.com",
|
|
||||||
IngressRuleValue: extensions.IngressRuleValue{
|
|
||||||
HTTP: &extensions.HTTPIngressRuleValue{
|
|
||||||
Paths: []extensions.HTTPIngressPath{
|
|
||||||
{
|
|
||||||
Path: "/foo",
|
|
||||||
Backend: defaultBackend,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockCfg struct {
|
|
||||||
certs map[string]resolver.AuthSSLCert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg mockCfg) GetAuthCertificate(secret string) (*resolver.AuthSSLCert, error) {
|
|
||||||
if cert, ok := cfg.certs[secret]; ok {
|
|
||||||
return &cert, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("secret not found: %v", secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnnotations(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
data := map[string]string{}
|
|
||||||
data[secureUpstream] = "true"
|
|
||||||
data[secureVerifyCASecret] = "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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSecretNotFound(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
data := map[string]string{}
|
|
||||||
data[secureUpstream] = "true"
|
|
||||||
data[secureVerifyCASecret] = "secure-verify-ca"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
_, err := NewParser(mockCfg{}).Parse(ing)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Expected secret not found error on ingress")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSecretOnNonSecure(t *testing.T) {
|
|
||||||
ing := buildIngress()
|
|
||||||
data := map[string]string{}
|
|
||||||
data[secureUpstream] = "false"
|
|
||||||
data[secureVerifyCASecret] = "secure-verify-ca"
|
|
||||||
ing.SetAnnotations(data)
|
|
||||||
_, err := NewParser(mockCfg{
|
|
||||||
certs: map[string]resolver.AuthSSLCert{
|
|
||||||
"default/secure-verify-ca": {},
|
|
||||||
},
|
|
||||||
}).Parse(ing)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("Expected CA secret on non secure backend error on ingress")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +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 serversnippet
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/server-snippet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type serverSnippet struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new server snippet annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return serverSnippet{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
return parser.GetStringAnnotation(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,38 +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 serviceupstream
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotationServiceUpstream = "ingress.kubernetes.io/service-upstream"
|
|
||||||
)
|
|
||||||
|
|
||||||
type serviceUpstream struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new serviceUpstream annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return serviceUpstream{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s serviceUpstream) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
return parser.GetBoolAnnotation(annotationServiceUpstream, ing)
|
|
||||||
}
|
|
|
@ -1,115 +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 sessionaffinity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotationAffinityType = "ingress.kubernetes.io/affinity"
|
|
||||||
// If a cookie with this name exists,
|
|
||||||
// its value is used as an index into the list of available backends.
|
|
||||||
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
|
|
||||||
defaultAffinityCookieName = "INGRESSCOOKIE"
|
|
||||||
// This is the algorithm used by nginx to generate a value for the session cookie, if
|
|
||||||
// one isn't supplied and affinity is set to "cookie".
|
|
||||||
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
|
|
||||||
defaultAffinityCookieHash = "md5"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// AffinityConfig describes the per ingress session affinity config
|
|
||||||
type AffinityConfig struct {
|
|
||||||
// The type of affinity that will be used
|
|
||||||
AffinityType string `json:"type"`
|
|
||||||
CookieConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieConfig describes the Config of cookie type affinity
|
|
||||||
type CookieConfig struct {
|
|
||||||
// The name of the cookie that will be used in case of cookie affinity type.
|
|
||||||
Name string `json:"name"`
|
|
||||||
// The hash that will be used to encode the cookie in case of cookie affinity type
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieAffinityParse gets the annotation values related to Cookie Affinity
|
|
||||||
// It also sets default values when no value or incorrect value is found
|
|
||||||
func CookieAffinityParse(ing *extensions.Ingress) *CookieConfig {
|
|
||||||
|
|
||||||
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
|
|
||||||
|
|
||||||
if err != nil || sn == "" {
|
|
||||||
glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, annotationAffinityCookieName, defaultAffinityCookieName)
|
|
||||||
sn = defaultAffinityCookieName
|
|
||||||
}
|
|
||||||
|
|
||||||
sh, err := parser.GetStringAnnotation(annotationAffinityCookieHash, ing)
|
|
||||||
|
|
||||||
if err != nil || !affinityCookieHashRegex.MatchString(sh) {
|
|
||||||
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Setting it to default %v", ing.Name, annotationAffinityCookieHash, defaultAffinityCookieHash)
|
|
||||||
sh = defaultAffinityCookieHash
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CookieConfig{
|
|
||||||
Name: sn,
|
|
||||||
Hash: sh,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new Affinity annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return affinity{}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
cookieAffinityConfig := &CookieConfig{}
|
|
||||||
// Check the type of affinity that will be used
|
|
||||||
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
|
|
||||||
if err != nil {
|
|
||||||
at = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch at {
|
|
||||||
case "cookie":
|
|
||||||
cookieAffinityConfig = CookieAffinityParse(ing)
|
|
||||||
|
|
||||||
default:
|
|
||||||
glog.V(3).Infof("No default affinity was found for Ingress %v", ing.Name)
|
|
||||||
|
|
||||||
}
|
|
||||||
return &AffinityConfig{
|
|
||||||
AffinityType: at,
|
|
||||||
CookieConfig: *cookieAffinityConfig,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,42 +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 snippet
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/configuration-snippet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type snippet struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new CORS annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return snippet{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
return parser.GetStringAnnotation(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,46 +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 sslpassthrough
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
ing_errors "k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
passthrough = "ingress.kubernetes.io/ssl-passthrough"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sslpt struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new SSL passthrough annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return sslpt{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
if ing.GetAnnotations() == nil {
|
|
||||||
return false, ing_errors.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
return parser.GetBoolAnnotation(passthrough, ing)
|
|
||||||
}
|
|
|
@ -1,42 +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 upstreamvhost
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/upstream-vhost"
|
|
||||||
)
|
|
||||||
|
|
||||||
type upstreamVhost struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new upstream VHost annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return upstreamVhost{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
return parser.GetStringAnnotation(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,42 +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 vtsfilterkey
|
|
||||||
|
|
||||||
import (
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
annotation = "ingress.kubernetes.io/vts-filter-key"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vtsFilterKey struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParser creates a new vts filter key annotation parser
|
|
||||||
func NewParser() parser.IngressAnnotation {
|
|
||||||
return vtsFilterKey{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 vtsFilterKey) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|
||||||
return parser.GetStringAnnotation(annotation, ing)
|
|
||||||
}
|
|
|
@ -1,191 +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 controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/alias"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/auth"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/authreq"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/authtls"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/clientbodybuffersize"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/cors"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/defaultbackend"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/healthcheck"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/ipwhitelist"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/portinredirect"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/proxy"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/ratelimit"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/redirect"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/rewrite"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/secureupstream"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/serversnippet"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/serviceupstream"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/sessionaffinity"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/snippet"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/sslpassthrough"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/upstreamvhost"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/vtsfilterkey"
|
|
||||||
"k8s.io/ingress-nginx/ingress/errors"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type extractorConfig interface {
|
|
||||||
resolver.AuthCertificate
|
|
||||||
resolver.DefaultBackend
|
|
||||||
resolver.Secret
|
|
||||||
resolver.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
type annotationExtractor struct {
|
|
||||||
secretResolver resolver.Secret
|
|
||||||
annotations map[string]parser.IngressAnnotation
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
|
||||||
return annotationExtractor{
|
|
||||||
cfg,
|
|
||||||
map[string]parser.IngressAnnotation{
|
|
||||||
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
|
||||||
"ExternalAuth": authreq.NewParser(),
|
|
||||||
"CertificateAuth": authtls.NewParser(cfg),
|
|
||||||
"EnableCORS": cors.NewParser(),
|
|
||||||
"HealthCheck": healthcheck.NewParser(cfg),
|
|
||||||
"Whitelist": ipwhitelist.NewParser(cfg),
|
|
||||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
|
||||||
"Proxy": proxy.NewParser(cfg),
|
|
||||||
"RateLimit": ratelimit.NewParser(cfg),
|
|
||||||
"Redirect": redirect.NewParser(),
|
|
||||||
"Rewrite": rewrite.NewParser(cfg),
|
|
||||||
"SecureUpstream": secureupstream.NewParser(cfg),
|
|
||||||
"ServiceUpstream": serviceupstream.NewParser(),
|
|
||||||
"SessionAffinity": sessionaffinity.NewParser(),
|
|
||||||
"SSLPassthrough": sslpassthrough.NewParser(),
|
|
||||||
"ConfigurationSnippet": snippet.NewParser(),
|
|
||||||
"Alias": alias.NewParser(),
|
|
||||||
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
|
|
||||||
"DefaultBackend": defaultbackend.NewParser(cfg),
|
|
||||||
"UpstreamVhost": upstreamvhost.NewParser(),
|
|
||||||
"VtsFilterKey": vtsfilterkey.NewParser(),
|
|
||||||
"ServerSnippet": serversnippet.NewParser(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interface{} {
|
|
||||||
anns := make(map[string]interface{})
|
|
||||||
for name, annotationParser := range e.annotations {
|
|
||||||
val, err := annotationParser.Parse(ing)
|
|
||||||
glog.V(5).Infof("annotation %v in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), val)
|
|
||||||
if err != nil {
|
|
||||||
if errors.IsMissingAnnotations(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.IsLocationDenied(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, alreadyDenied := anns[DeniedKeyName]
|
|
||||||
if !alreadyDenied {
|
|
||||||
anns[DeniedKeyName] = err
|
|
||||||
glog.Errorf("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(5).Infof("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if val != nil {
|
|
||||||
anns[name] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return anns
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
secureUpstream = "SecureUpstream"
|
|
||||||
healthCheck = "HealthCheck"
|
|
||||||
sslPassthrough = "SSLPassthrough"
|
|
||||||
sessionAffinity = "SessionAffinity"
|
|
||||||
serviceUpstream = "ServiceUpstream"
|
|
||||||
serverAlias = "Alias"
|
|
||||||
clientBodyBufferSize = "ClientBodyBufferSize"
|
|
||||||
certificateAuth = "CertificateAuth"
|
|
||||||
serverSnippet = "ServerSnippet"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool {
|
|
||||||
val, _ := e.annotations[serviceUpstream].Parse(ing)
|
|
||||||
return val.(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) *secureupstream.Secure {
|
|
||||||
val, err := e.annotations[secureUpstream].Parse(ing)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("error parsing secure upstream: %v", err)
|
|
||||||
}
|
|
||||||
secure := val.(*secureupstream.Secure)
|
|
||||||
return secure
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) HealthCheck(ing *extensions.Ingress) *healthcheck.Upstream {
|
|
||||||
val, _ := e.annotations[healthCheck].Parse(ing)
|
|
||||||
return val.(*healthcheck.Upstream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool {
|
|
||||||
val, _ := e.annotations[sslPassthrough].Parse(ing)
|
|
||||||
return val.(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) Alias(ing *extensions.Ingress) string {
|
|
||||||
val, _ := e.annotations[serverAlias].Parse(ing)
|
|
||||||
return val.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) ClientBodyBufferSize(ing *extensions.Ingress) string {
|
|
||||||
val, _ := e.annotations[clientBodyBufferSize].Parse(ing)
|
|
||||||
return val.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig {
|
|
||||||
val, _ := e.annotations[sessionAffinity].Parse(ing)
|
|
||||||
return val.(*sessionaffinity.AffinityConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) CertificateAuth(ing *extensions.Ingress) *authtls.AuthSSLConfig {
|
|
||||||
val, err := e.annotations[certificateAuth].Parse(ing)
|
|
||||||
if errors.IsMissingAnnotations(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("error parsing certificate auth: %v", err)
|
|
||||||
}
|
|
||||||
secure := val.(*authtls.AuthSSLConfig)
|
|
||||||
return secure
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *annotationExtractor) ServerSnippet(ing *extensions.Ingress) string {
|
|
||||||
val, _ := e.annotations[serverSnippet].Parse(ing)
|
|
||||||
return val.(string)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,319 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/pprof"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
|
||||||
"k8s.io/ingress-nginx/k8s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewIngressController returns a configured Ingress controller
|
|
||||||
func NewIngressController(backend ingress.Controller) *GenericController {
|
|
||||||
var (
|
|
||||||
flags = pflag.NewFlagSet("", pflag.ExitOnError)
|
|
||||||
|
|
||||||
apiserverHost = flags.String("apiserver-host", "", "The address of the Kubernetes Apiserver "+
|
|
||||||
"to connect to in the format of protocol://address:port, e.g., "+
|
|
||||||
"http://localhost:8080. If not specified, the assumption is that the binary runs inside a "+
|
|
||||||
"Kubernetes cluster and local discovery is attempted.")
|
|
||||||
kubeConfigFile = flags.String("kubeconfig", "", "Path to kubeconfig file with authorization and master location information.")
|
|
||||||
|
|
||||||
defaultSvc = flags.String("default-backend-service", "",
|
|
||||||
`Service used to serve a 404 page for the default backend. Takes the form
|
|
||||||
namespace/name. The controller uses the first node port of this Service for
|
|
||||||
the default backend.`)
|
|
||||||
|
|
||||||
ingressClass = flags.String("ingress-class", "",
|
|
||||||
`Name of the ingress class to route through this controller.`)
|
|
||||||
|
|
||||||
configMap = flags.String("configmap", "",
|
|
||||||
`Name of the ConfigMap that contains the custom configuration to use`)
|
|
||||||
|
|
||||||
publishSvc = flags.String("publish-service", "",
|
|
||||||
`Service fronting the ingress controllers. Takes the form
|
|
||||||
namespace/name. The controller will set the endpoint records on the
|
|
||||||
ingress objects to reflect those on the service.`)
|
|
||||||
|
|
||||||
tcpConfigMapName = flags.String("tcp-services-configmap", "",
|
|
||||||
`Name of the ConfigMap that contains the definition of the TCP services to expose.
|
|
||||||
The key in the map indicates the external port to be used. The value is the name of the
|
|
||||||
service with the format namespace/serviceName and the port of the service could be a
|
|
||||||
number of the name of the port.
|
|
||||||
The ports 80 and 443 are not allowed as external ports. This ports are reserved for the backend`)
|
|
||||||
|
|
||||||
udpConfigMapName = flags.String("udp-services-configmap", "",
|
|
||||||
`Name of the ConfigMap that contains the definition of the UDP services to expose.
|
|
||||||
The key in the map indicates the external port to be used. The value is the name of the
|
|
||||||
service with the format namespace/serviceName and the port of the service could be a
|
|
||||||
number of the name of the port.`)
|
|
||||||
|
|
||||||
resyncPeriod = flags.Duration("sync-period", 600*time.Second,
|
|
||||||
`Relist and confirm cloud resources this often. Default is 10 minutes`)
|
|
||||||
|
|
||||||
watchNamespace = flags.String("watch-namespace", apiv1.NamespaceAll,
|
|
||||||
`Namespace to watch for Ingress. Default is to watch all namespaces`)
|
|
||||||
|
|
||||||
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
|
|
||||||
|
|
||||||
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
|
|
||||||
|
|
||||||
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret
|
|
||||||
that contains a SSL certificate to be used as default for a HTTPS catch-all server`)
|
|
||||||
|
|
||||||
defHealthzURL = flags.String("health-check-path", "/healthz", `Defines
|
|
||||||
the URL to be used as health check inside in the default server in NGINX.`)
|
|
||||||
|
|
||||||
updateStatus = flags.Bool("update-status", true, `Indicates if the
|
|
||||||
ingress controller should update the Ingress status IP/hostname. Default is true`)
|
|
||||||
|
|
||||||
electionID = flags.String("election-id", "ingress-controller-leader", `Election id to use for status update.`)
|
|
||||||
|
|
||||||
forceIsolation = flags.Bool("force-namespace-isolation", false,
|
|
||||||
`Force namespace isolation. This flag is required to avoid the reference of secrets or
|
|
||||||
configmaps located in a different namespace than the specified in the flag --watch-namespace.`)
|
|
||||||
|
|
||||||
disableNodeList = flags.Bool("disable-node-list", false,
|
|
||||||
`Disable querying nodes. If --force-namespace-isolation is true, this should also be set.`)
|
|
||||||
|
|
||||||
updateStatusOnShutdown = flags.Bool("update-status-on-shutdown", true, `Indicates if the
|
|
||||||
ingress controller should update the Ingress status IP/hostname when the controller
|
|
||||||
is being stopped. Default is true`)
|
|
||||||
|
|
||||||
sortBackends = flags.Bool("sort-backends", false,
|
|
||||||
`Defines if backends and it's endpoints should be sorted`)
|
|
||||||
)
|
|
||||||
|
|
||||||
flags.AddGoFlagSet(flag.CommandLine)
|
|
||||||
backend.ConfigureFlags(flags)
|
|
||||||
flags.Parse(os.Args)
|
|
||||||
backend.OverrideFlags(flags)
|
|
||||||
|
|
||||||
flag.Set("logtostderr", "true")
|
|
||||||
|
|
||||||
glog.Info(backend.Info())
|
|
||||||
|
|
||||||
if *ingressClass != "" {
|
|
||||||
glog.Infof("Watching for ingress class: %s", *ingressClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *defaultSvc == "" {
|
|
||||||
glog.Fatalf("Please specify --default-backend-service")
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeClient, err := createApiserverClient(*apiserverHost, *kubeConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
handleFatalInitError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ns, name, err := k8s.ParseNameNS(*defaultSvc)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("invalid format for service %v: %v", *defaultSvc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = kubeClient.Core().Services(ns).Get(name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "cannot get services in the namespace") {
|
|
||||||
glog.Fatalf("✖ It seems the cluster it is running with Authorization enabled (like RBAC) and there is no permissions for the ingress controller. Please check the configuration")
|
|
||||||
}
|
|
||||||
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
|
|
||||||
}
|
|
||||||
glog.Infof("validated %v as the default backend", *defaultSvc)
|
|
||||||
|
|
||||||
if *publishSvc != "" {
|
|
||||||
ns, name, err := k8s.ParseNameNS(*publishSvc)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("invalid service format: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc, err := kubeClient.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("unexpected error getting information about service %v: %v", *publishSvc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(svc.Status.LoadBalancer.Ingress) == 0 {
|
|
||||||
if len(svc.Spec.ExternalIPs) > 0 {
|
|
||||||
glog.Infof("service %v validated as assigned with externalIP", *publishSvc)
|
|
||||||
} else {
|
|
||||||
// We could poll here, but we instead just exit and rely on k8s to restart us
|
|
||||||
glog.Fatalf("service %s does not (yet) have ingress points", *publishSvc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
glog.Infof("service %v validated as source of Ingress status", *publishSvc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *watchNamespace != "" {
|
|
||||||
_, err = kubeClient.CoreV1().Namespaces().Get(*watchNamespace, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("no watchNamespace with name %v found: %v", *watchNamespace, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resyncPeriod.Seconds() < 10 {
|
|
||||||
glog.Fatalf("resync period (%vs) is too low", resyncPeriod.Seconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to mkdir SSL directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &Configuration{
|
|
||||||
UpdateStatus: *updateStatus,
|
|
||||||
ElectionID: *electionID,
|
|
||||||
Client: kubeClient,
|
|
||||||
ResyncPeriod: *resyncPeriod,
|
|
||||||
DefaultService: *defaultSvc,
|
|
||||||
IngressClass: *ingressClass,
|
|
||||||
DefaultIngressClass: backend.DefaultIngressClass(),
|
|
||||||
Namespace: *watchNamespace,
|
|
||||||
ConfigMapName: *configMap,
|
|
||||||
TCPConfigMapName: *tcpConfigMapName,
|
|
||||||
UDPConfigMapName: *udpConfigMapName,
|
|
||||||
DefaultSSLCertificate: *defSSLCertificate,
|
|
||||||
DefaultHealthzURL: *defHealthzURL,
|
|
||||||
PublishService: *publishSvc,
|
|
||||||
Backend: backend,
|
|
||||||
ForceNamespaceIsolation: *forceIsolation,
|
|
||||||
DisableNodeList: *disableNodeList,
|
|
||||||
UpdateStatusOnShutdown: *updateStatusOnShutdown,
|
|
||||||
SortBackends: *sortBackends,
|
|
||||||
}
|
|
||||||
|
|
||||||
ic := newIngressController(config)
|
|
||||||
go registerHandlers(*profiling, *healthzPort, ic)
|
|
||||||
return ic
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerHandlers(enableProfiling bool, port int, ic *GenericController) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
// expose health check endpoint (/healthz)
|
|
||||||
healthz.InstallHandler(mux,
|
|
||||||
healthz.PingHealthz,
|
|
||||||
ic.cfg.Backend,
|
|
||||||
)
|
|
||||||
|
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
|
||||||
|
|
||||||
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
b, _ := json.Marshal(ic.Info())
|
|
||||||
w.Write(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if enableProfiling {
|
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
||||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
||||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
||||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
||||||
}
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%v", port),
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
glog.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// High enough QPS to fit all expected use cases. QPS=0 is not set here, because
|
|
||||||
// client code is overriding it.
|
|
||||||
defaultQPS = 1e6
|
|
||||||
// High enough Burst to fit all expected use cases. Burst=0 is not set here, because
|
|
||||||
// client code is overriding it.
|
|
||||||
defaultBurst = 1e6
|
|
||||||
)
|
|
||||||
|
|
||||||
// buildConfigFromFlags builds REST config based on master URL and kubeconfig path.
|
|
||||||
// If both of them are empty then in cluster config is used.
|
|
||||||
func buildConfigFromFlags(masterURL, kubeconfigPath string) (*rest.Config, error) {
|
|
||||||
if kubeconfigPath == "" && masterURL == "" {
|
|
||||||
kubeconfig, err := rest.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return kubeconfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
|
||||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
|
||||||
&clientcmd.ConfigOverrides{
|
|
||||||
ClusterInfo: clientcmdapi.Cluster{
|
|
||||||
Server: masterURL,
|
|
||||||
},
|
|
||||||
}).ClientConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// createApiserverClient creates new Kubernetes Apiserver client. When kubeconfig or apiserverHost param is empty
|
|
||||||
// the function assumes that it is running inside a Kubernetes cluster and attempts to
|
|
||||||
// discover the Apiserver. Otherwise, it connects to the Apiserver specified.
|
|
||||||
//
|
|
||||||
// apiserverHost param is in the format of protocol://address:port/pathPrefix, e.g.http://localhost:8001.
|
|
||||||
// kubeConfig location of kubeconfig file
|
|
||||||
func createApiserverClient(apiserverHost string, kubeConfig string) (*kubernetes.Clientset, error) {
|
|
||||||
cfg, err := buildConfigFromFlags(apiserverHost, kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.QPS = defaultQPS
|
|
||||||
cfg.Burst = defaultBurst
|
|
||||||
cfg.ContentType = "application/vnd.kubernetes.protobuf"
|
|
||||||
|
|
||||||
glog.Infof("Creating API client for %s", cfg.Host)
|
|
||||||
|
|
||||||
client, err := kubernetes.NewForConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := client.Discovery().ServerVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("Running in Kubernetes Cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
|
|
||||||
v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles fatal init error that prevents server from doing any work. Prints verbose error
|
|
||||||
* message and quits the server.
|
|
||||||
*/
|
|
||||||
func handleFatalInitError(err error) {
|
|
||||||
glog.Fatalf("Error while initializing connection to Kubernetes apiserver. "+
|
|
||||||
"This most likely means that the cluster is misconfigured (e.g., it has "+
|
|
||||||
"invalid apiserver certificates or service accounts configuration). Reason: %s\n"+
|
|
||||||
"Refer to the troubleshooting guide for more information: "+
|
|
||||||
"https://github.com/kubernetes/ingress/blob/master/docs/troubleshooting.md", err)
|
|
||||||
}
|
|
|
@ -1,236 +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 controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
fcache "k8s.io/client-go/tools/cache/testing"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/class"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cacheController struct {
|
|
||||||
Ingress cache.Controller
|
|
||||||
Endpoint cache.Controller
|
|
||||||
Service cache.Controller
|
|
||||||
Node cache.Controller
|
|
||||||
Secret cache.Controller
|
|
||||||
Configmap cache.Controller
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cacheController) Run(stopCh chan struct{}) {
|
|
||||||
go c.Ingress.Run(stopCh)
|
|
||||||
go c.Endpoint.Run(stopCh)
|
|
||||||
go c.Service.Run(stopCh)
|
|
||||||
go c.Node.Run(stopCh)
|
|
||||||
go c.Secret.Run(stopCh)
|
|
||||||
go c.Configmap.Run(stopCh)
|
|
||||||
|
|
||||||
// Wait for all involved caches to be synced, before processing items from the queue is started
|
|
||||||
if !cache.WaitForCacheSync(stopCh,
|
|
||||||
c.Ingress.HasSynced,
|
|
||||||
c.Endpoint.HasSynced,
|
|
||||||
c.Service.HasSynced,
|
|
||||||
c.Node.HasSynced,
|
|
||||||
c.Secret.HasSynced,
|
|
||||||
c.Configmap.HasSynced,
|
|
||||||
) {
|
|
||||||
runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.StoreLister, *cacheController) {
|
|
||||||
// from here to the end of the method all the code is just boilerplate
|
|
||||||
// required to watch Ingress, Secrets, ConfigMaps and Endoints.
|
|
||||||
// This is used to detect new content, updates or removals and act accordingly
|
|
||||||
ingEventHandler := cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(obj interface{}) {
|
|
||||||
addIng := obj.(*extensions.Ingress)
|
|
||||||
if !class.IsValid(addIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
|
|
||||||
a, _ := parser.GetStringAnnotation(class.IngressKey, addIng)
|
|
||||||
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", addIng.Name, class.IngressKey, a)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ic.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
|
|
||||||
ic.syncQueue.Enqueue(obj)
|
|
||||||
},
|
|
||||||
DeleteFunc: func(obj interface{}) {
|
|
||||||
delIng, ok := obj.(*extensions.Ingress)
|
|
||||||
if !ok {
|
|
||||||
// If we reached here it means the ingress was deleted but its final state is unrecorded.
|
|
||||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
glog.Errorf("couldn't get object from tombstone %#v", obj)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delIng, ok = tombstone.Obj.(*extensions.Ingress)
|
|
||||||
if !ok {
|
|
||||||
glog.Errorf("Tombstone contained object that is not an Ingress: %#v", obj)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !class.IsValid(delIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
|
|
||||||
glog.Infof("ignoring delete for ingress %v based on annotation %v", delIng.Name, class.IngressKey)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ic.recorder.Eventf(delIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", delIng.Namespace, delIng.Name))
|
|
||||||
ic.syncQueue.Enqueue(obj)
|
|
||||||
},
|
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
|
||||||
oldIng := old.(*extensions.Ingress)
|
|
||||||
curIng := cur.(*extensions.Ingress)
|
|
||||||
validOld := class.IsValid(oldIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass)
|
|
||||||
validCur := class.IsValid(curIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass)
|
|
||||||
if !validOld && validCur {
|
|
||||||
glog.Infof("creating ingress %v based on annotation %v", curIng.Name, class.IngressKey)
|
|
||||||
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
|
||||||
} else if validOld && !validCur {
|
|
||||||
glog.Infof("removing ingress %v based on annotation %v", curIng.Name, class.IngressKey)
|
|
||||||
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
|
||||||
} else if validCur && !reflect.DeepEqual(old, cur) {
|
|
||||||
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
ic.syncQueue.Enqueue(cur)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
secrEventHandler := cache.ResourceEventHandlerFuncs{
|
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
|
||||||
if !reflect.DeepEqual(old, cur) {
|
|
||||||
sec := cur.(*apiv1.Secret)
|
|
||||||
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
|
|
||||||
ic.syncSecret(key)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DeleteFunc: func(obj interface{}) {
|
|
||||||
sec, ok := obj.(*apiv1.Secret)
|
|
||||||
if !ok {
|
|
||||||
// If we reached here it means the secret was deleted but its final state is unrecorded.
|
|
||||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
|
||||||
if !ok {
|
|
||||||
glog.Errorf("couldn't get object from tombstone %#v", obj)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sec, ok = tombstone.Obj.(*apiv1.Secret)
|
|
||||||
if !ok {
|
|
||||||
glog.Errorf("Tombstone contained object that is not a Secret: %#v", obj)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
|
|
||||||
ic.sslCertTracker.DeleteAll(key)
|
|
||||||
ic.syncQueue.Enqueue(key)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
eventHandler := cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(obj interface{}) {
|
|
||||||
ic.syncQueue.Enqueue(obj)
|
|
||||||
},
|
|
||||||
DeleteFunc: func(obj interface{}) {
|
|
||||||
ic.syncQueue.Enqueue(obj)
|
|
||||||
},
|
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
|
||||||
oep := old.(*apiv1.Endpoints)
|
|
||||||
ocur := cur.(*apiv1.Endpoints)
|
|
||||||
if !reflect.DeepEqual(ocur.Subsets, oep.Subsets) {
|
|
||||||
ic.syncQueue.Enqueue(cur)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
mapEventHandler := cache.ResourceEventHandlerFuncs{
|
|
||||||
AddFunc: func(obj interface{}) {
|
|
||||||
upCmap := obj.(*apiv1.ConfigMap)
|
|
||||||
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
|
|
||||||
if mapKey == ic.cfg.ConfigMapName {
|
|
||||||
glog.V(2).Infof("adding configmap %v to backend", mapKey)
|
|
||||||
ic.cfg.Backend.SetConfig(upCmap)
|
|
||||||
ic.setForceReload(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
|
||||||
if !reflect.DeepEqual(old, cur) {
|
|
||||||
upCmap := cur.(*apiv1.ConfigMap)
|
|
||||||
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
|
|
||||||
if mapKey == ic.cfg.ConfigMapName {
|
|
||||||
glog.V(2).Infof("updating configmap backend (%v)", mapKey)
|
|
||||||
ic.cfg.Backend.SetConfig(upCmap)
|
|
||||||
ic.setForceReload(true)
|
|
||||||
}
|
|
||||||
// updates to configuration configmaps can trigger an update
|
|
||||||
if mapKey == ic.cfg.ConfigMapName || mapKey == ic.cfg.TCPConfigMapName || mapKey == ic.cfg.UDPConfigMapName {
|
|
||||||
ic.recorder.Eventf(upCmap, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("ConfigMap %v", mapKey))
|
|
||||||
ic.syncQueue.Enqueue(cur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
watchNs := apiv1.NamespaceAll
|
|
||||||
if ic.cfg.ForceNamespaceIsolation && ic.cfg.Namespace != apiv1.NamespaceAll {
|
|
||||||
watchNs = ic.cfg.Namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
lister := &ingress.StoreLister{}
|
|
||||||
|
|
||||||
controller := &cacheController{}
|
|
||||||
|
|
||||||
lister.Ingress.Store, controller.Ingress = cache.NewInformer(
|
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.ExtensionsV1beta1().RESTClient(), "ingresses", ic.cfg.Namespace, fields.Everything()),
|
|
||||||
&extensions.Ingress{}, ic.cfg.ResyncPeriod, ingEventHandler)
|
|
||||||
|
|
||||||
lister.Endpoint.Store, controller.Endpoint = cache.NewInformer(
|
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "endpoints", ic.cfg.Namespace, fields.Everything()),
|
|
||||||
&apiv1.Endpoints{}, ic.cfg.ResyncPeriod, eventHandler)
|
|
||||||
|
|
||||||
lister.Secret.Store, controller.Secret = cache.NewInformer(
|
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "secrets", watchNs, fields.Everything()),
|
|
||||||
&apiv1.Secret{}, ic.cfg.ResyncPeriod, secrEventHandler)
|
|
||||||
|
|
||||||
lister.ConfigMap.Store, controller.Configmap = cache.NewInformer(
|
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "configmaps", watchNs, fields.Everything()),
|
|
||||||
&apiv1.ConfigMap{}, ic.cfg.ResyncPeriod, mapEventHandler)
|
|
||||||
|
|
||||||
lister.Service.Store, controller.Service = cache.NewInformer(
|
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "services", ic.cfg.Namespace, fields.Everything()),
|
|
||||||
&apiv1.Service{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
|
||||||
|
|
||||||
var nodeListerWatcher cache.ListerWatcher
|
|
||||||
if disableNodeLister {
|
|
||||||
nodeListerWatcher = fcache.NewFakeControllerSource()
|
|
||||||
} else {
|
|
||||||
nodeListerWatcher = cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "nodes", apiv1.NamespaceAll, fields.Everything())
|
|
||||||
}
|
|
||||||
lister.Node.Store, controller.Node = cache.NewInformer(
|
|
||||||
nodeListerWatcher,
|
|
||||||
&apiv1.Node{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
|
||||||
|
|
||||||
return lister, controller
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ns = "ingress_controller"
|
|
||||||
operation = "count"
|
|
||||||
reloadLabel = "reloads"
|
|
||||||
sslLabelExpire = "ssl_expire_time_seconds"
|
|
||||||
sslLabelHost = "host"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prometheus.MustRegister(reloadOperation)
|
|
||||||
prometheus.MustRegister(reloadOperationErrors)
|
|
||||||
prometheus.MustRegister(sslExpireTime)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
reloadOperation = prometheus.NewCounterVec(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: ns,
|
|
||||||
Name: "success",
|
|
||||||
Help: "Cumulative number of Ingress controller reload operations",
|
|
||||||
},
|
|
||||||
[]string{operation},
|
|
||||||
)
|
|
||||||
reloadOperationErrors = prometheus.NewCounterVec(
|
|
||||||
prometheus.CounterOpts{
|
|
||||||
Namespace: ns,
|
|
||||||
Name: "errors",
|
|
||||||
Help: "Cumulative number of Ingress controller errors during reload operations",
|
|
||||||
},
|
|
||||||
[]string{operation},
|
|
||||||
)
|
|
||||||
sslExpireTime = prometheus.NewGaugeVec(
|
|
||||||
prometheus.GaugeOpts{
|
|
||||||
Namespace: ns,
|
|
||||||
Name: sslLabelExpire,
|
|
||||||
Help: "Number of seconds since 1970 to the SSL Certificate expire. An example to check if this " +
|
|
||||||
"certificate will expire in 10 days is: \"ingress_controller_ssl_expire_time_seconds < (time() + (10 * 24 * 3600))\"",
|
|
||||||
},
|
|
||||||
[]string{sslLabelHost},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
func incReloadCount() {
|
|
||||||
reloadOperation.WithLabelValues(reloadLabel).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func incReloadErrorCount() {
|
|
||||||
reloadOperationErrors.WithLabelValues(reloadLabel).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setSSLExpireTime(servers []*ingress.Server) {
|
|
||||||
|
|
||||||
for _, s := range servers {
|
|
||||||
if s.Hostname != defServerName {
|
|
||||||
sslExpireTime.WithLabelValues(s.Hostname).Set(float64(s.SSLExpireTime.Unix()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"github.com/imdario/mergo"
|
|
||||||
|
|
||||||
api "k8s.io/api/core/v1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DeniedKeyName name of the key that contains the reason to deny a location
|
|
||||||
const DeniedKeyName = "Denied"
|
|
||||||
|
|
||||||
// newUpstream creates an upstream without servers.
|
|
||||||
func newUpstream(name string) *ingress.Backend {
|
|
||||||
return &ingress.Backend{
|
|
||||||
Name: name,
|
|
||||||
Endpoints: []ingress.Endpoint{},
|
|
||||||
Service: &api.Service{},
|
|
||||||
SessionAffinity: ingress.SessionAffinityConfig{
|
|
||||||
CookieSessionAffinity: ingress.CookieSessionAffinity{
|
|
||||||
Locations: make(map[string][]string),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}) {
|
|
||||||
if _, ok := anns[DeniedKeyName]; ok {
|
|
||||||
loc.Denied = anns[DeniedKeyName].(error)
|
|
||||||
}
|
|
||||||
delete(anns, DeniedKeyName)
|
|
||||||
err := mergo.Map(loc, anns)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error merging extracted annotations in location type: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/auth"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/authreq"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/ipwhitelist"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/proxy"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/ratelimit"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/redirect"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/rewrite"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeError struct{}
|
|
||||||
|
|
||||||
func (fe *fakeError) Error() string {
|
|
||||||
return "fakeError"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMergeLocationAnnotations(t *testing.T) {
|
|
||||||
// initial parameters
|
|
||||||
loc := ingress.Location{}
|
|
||||||
annotations := map[string]interface{}{
|
|
||||||
"Path": "/checkpath",
|
|
||||||
"IsDefBackend": true,
|
|
||||||
"Backend": "foo_backend",
|
|
||||||
"BasicDigestAuth": auth.BasicDigest{},
|
|
||||||
DeniedKeyName: &fakeError{},
|
|
||||||
"EnableCORS": true,
|
|
||||||
"ExternalAuth": authreq.External{},
|
|
||||||
"RateLimit": ratelimit.RateLimit{},
|
|
||||||
"Redirect": redirect.Redirect{},
|
|
||||||
"Rewrite": rewrite.Redirect{},
|
|
||||||
"Whitelist": ipwhitelist.SourceRange{},
|
|
||||||
"Proxy": proxy.Configuration{},
|
|
||||||
"UsePortInRedirects": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// create test table
|
|
||||||
type fooMergeLocationAnnotationsStruct struct {
|
|
||||||
fName string
|
|
||||||
er interface{}
|
|
||||||
}
|
|
||||||
fooTests := []fooMergeLocationAnnotationsStruct{}
|
|
||||||
for name, value := range annotations {
|
|
||||||
fva := fooMergeLocationAnnotationsStruct{name, value}
|
|
||||||
fooTests = append(fooTests, fva)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute test
|
|
||||||
mergeLocationAnnotations(&loc, annotations)
|
|
||||||
|
|
||||||
// check result
|
|
||||||
for _, foo := range fooTests {
|
|
||||||
fv := reflect.ValueOf(loc).FieldByName(foo.fName).Interface()
|
|
||||||
if !reflect.DeepEqual(fv, foo.er) {
|
|
||||||
t.Errorf("Returned %v but expected %v for the field %s", fv, foo.er, foo.fName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := annotations[DeniedKeyName]; ok {
|
|
||||||
t.Errorf("%s should be removed after mergeLocationAnnotations", DeniedKeyName)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +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 resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultBackend has a method that returns the backend
|
|
||||||
// that must be used as default
|
|
||||||
type DefaultBackend interface {
|
|
||||||
GetDefaultBackend() defaults.Backend
|
|
||||||
}
|
|
||||||
|
|
||||||
// Secret has a method that searches for secrets contenating
|
|
||||||
// the namespace and name using a the character /
|
|
||||||
type Secret interface {
|
|
||||||
GetSecret(string) (*apiv1.Secret, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthCertificate resolves a given secret name into an SSL certificate.
|
|
||||||
// The secret must contain 3 keys named:
|
|
||||||
// ca.crt: contains the certificate chain used for authentication
|
|
||||||
type AuthCertificate interface {
|
|
||||||
GetAuthCertificate(string) (*AuthSSLCert, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service has a method that searches for services contenating
|
|
||||||
// the namespace and name using a the character /
|
|
||||||
type Service interface {
|
|
||||||
GetService(string) (*apiv1.Service, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthSSLCert contains the necessary information to do certificate based
|
|
||||||
// authentication of an ingress location
|
|
||||||
type AuthSSLCert struct {
|
|
||||||
// Secret contains the name of the secret this was fetched from
|
|
||||||
Secret string `json:"secret"`
|
|
||||||
// CAFileName contains the path to the secrets 'ca.crt'
|
|
||||||
CAFileName string `json:"caFilename"`
|
|
||||||
// PemSHA contains the SHA1 hash of the 'ca.crt' or combinations of (tls.crt, tls.key, tls.crt) depending on certs in secret
|
|
||||||
PemSHA string `json:"pemSha"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two AuthSSLCert types
|
|
||||||
func (asslc1 *AuthSSLCert) Equal(assl2 *AuthSSLCert) bool {
|
|
||||||
if asslc1.Secret != assl2.Secret {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if asslc1.CAFileName != assl2.CAFileName {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if asslc1.PemSHA != assl2.PemSHA {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,399 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 status
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
pool "gopkg.in/go-playground/pool.v3"
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
|
||||||
"k8s.io/client-go/tools/leaderelection"
|
|
||||||
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
|
||||||
"k8s.io/client-go/tools/record"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/class"
|
|
||||||
"k8s.io/ingress-nginx/ingress/store"
|
|
||||||
"k8s.io/ingress-nginx/k8s"
|
|
||||||
ingress_strings "k8s.io/ingress-nginx/strings"
|
|
||||||
"k8s.io/ingress-nginx/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
updateInterval = 60 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sync ...
|
|
||||||
type Sync interface {
|
|
||||||
Run(stopCh <-chan struct{})
|
|
||||||
Shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config ...
|
|
||||||
type Config struct {
|
|
||||||
Client clientset.Interface
|
|
||||||
|
|
||||||
PublishService string
|
|
||||||
|
|
||||||
ElectionID string
|
|
||||||
|
|
||||||
UpdateStatusOnShutdown bool
|
|
||||||
|
|
||||||
IngressLister store.IngressLister
|
|
||||||
|
|
||||||
DefaultIngressClass string
|
|
||||||
IngressClass string
|
|
||||||
|
|
||||||
// CustomIngressStatus allows to set custom values in Ingress status
|
|
||||||
CustomIngressStatus func(*extensions.Ingress) []apiv1.LoadBalancerIngress
|
|
||||||
}
|
|
||||||
|
|
||||||
// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
|
|
||||||
// in all the defined rules. To simplify the process leader election is used so the update
|
|
||||||
// is executed only in one node (Ingress controllers can be scaled to more than one)
|
|
||||||
// If the controller is running with the flag --publish-service (with a valid service)
|
|
||||||
// the IP address behind the service is used, if not the source is the IP/s of the node/s
|
|
||||||
type statusSync struct {
|
|
||||||
Config
|
|
||||||
// pod contains runtime information about this pod
|
|
||||||
pod *k8s.PodInfo
|
|
||||||
|
|
||||||
elector *leaderelection.LeaderElector
|
|
||||||
// workqueue used to keep in sync the status IP/s
|
|
||||||
// in the Ingress rules
|
|
||||||
syncQueue *task.Queue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts the loop to keep the status in sync
|
|
||||||
func (s statusSync) Run(stopCh <-chan struct{}) {
|
|
||||||
go s.elector.Run()
|
|
||||||
go wait.Forever(s.update, updateInterval)
|
|
||||||
go s.syncQueue.Run(time.Second, stopCh)
|
|
||||||
<-stopCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statusSync) update() {
|
|
||||||
// send a dummy object to the queue to force a sync
|
|
||||||
s.syncQueue.Enqueue("sync status")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown stop the sync. In case the instance is the leader it will remove the current IP
|
|
||||||
// if there is no other instances running.
|
|
||||||
func (s statusSync) Shutdown() {
|
|
||||||
go s.syncQueue.Shutdown()
|
|
||||||
// remove IP from Ingress
|
|
||||||
if !s.elector.IsLeader() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.UpdateStatusOnShutdown {
|
|
||||||
glog.Warningf("skipping update of status of Ingress rules")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("updating status of Ingress rules (remove)")
|
|
||||||
|
|
||||||
addrs, err := s.runningAddresses()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("error obtaining running IPs: %v", addrs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(addrs) > 1 {
|
|
||||||
// leave the job to the next leader
|
|
||||||
glog.Infof("leaving status update for next leader (%v)", len(addrs))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.isRunningMultiplePods() {
|
|
||||||
glog.V(2).Infof("skipping Ingress status update (multiple pods running - another one will be elected as master)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("removing address from ingress status (%v)", addrs)
|
|
||||||
s.updateStatus([]apiv1.LoadBalancerIngress{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statusSync) sync(key interface{}) error {
|
|
||||||
if s.syncQueue.IsShuttingDown() {
|
|
||||||
glog.V(2).Infof("skipping Ingress status update (shutting down in progress)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.elector.IsLeader() {
|
|
||||||
glog.V(2).Infof("skipping Ingress status update (I am not the current leader)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := s.runningAddresses()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.updateStatus(sliceToStatus(addrs))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s statusSync) keyfunc(input interface{}) (interface{}, error) {
|
|
||||||
return input, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStatusSyncer returns a new Sync instance
|
|
||||||
func NewStatusSyncer(config Config) Sync {
|
|
||||||
pod, err := k8s.GetPodDetails(config.Client)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("unexpected error obtaining pod information: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
st := statusSync{
|
|
||||||
pod: pod,
|
|
||||||
|
|
||||||
Config: config,
|
|
||||||
}
|
|
||||||
st.syncQueue = task.NewCustomTaskQueue(st.sync, st.keyfunc)
|
|
||||||
|
|
||||||
// we need to use the defined ingress class to allow multiple leaders
|
|
||||||
// in order to update information about ingress status
|
|
||||||
electionID := fmt.Sprintf("%v-%v", config.ElectionID, config.DefaultIngressClass)
|
|
||||||
if config.IngressClass != "" {
|
|
||||||
electionID = fmt.Sprintf("%v-%v", config.ElectionID, config.IngressClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbacks := leaderelection.LeaderCallbacks{
|
|
||||||
OnStartedLeading: func(stop <-chan struct{}) {
|
|
||||||
glog.V(2).Infof("I am the new status update leader")
|
|
||||||
},
|
|
||||||
OnStoppedLeading: func() {
|
|
||||||
glog.V(2).Infof("I am not status update leader anymore")
|
|
||||||
},
|
|
||||||
OnNewLeader: func(identity string) {
|
|
||||||
glog.Infof("new leader elected: %v", identity)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcaster := record.NewBroadcaster()
|
|
||||||
hostname, _ := os.Hostname()
|
|
||||||
|
|
||||||
recorder := broadcaster.NewRecorder(scheme.Scheme, apiv1.EventSource{
|
|
||||||
Component: "ingress-leader-elector",
|
|
||||||
Host: hostname,
|
|
||||||
})
|
|
||||||
|
|
||||||
lock := resourcelock.ConfigMapLock{
|
|
||||||
ConfigMapMeta: metav1.ObjectMeta{Namespace: pod.Namespace, Name: electionID},
|
|
||||||
Client: config.Client.CoreV1(),
|
|
||||||
LockConfig: resourcelock.ResourceLockConfig{
|
|
||||||
Identity: pod.Name,
|
|
||||||
EventRecorder: recorder,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ttl := 30 * time.Second
|
|
||||||
le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{
|
|
||||||
Lock: &lock,
|
|
||||||
LeaseDuration: ttl,
|
|
||||||
RenewDeadline: ttl / 2,
|
|
||||||
RetryPeriod: ttl / 4,
|
|
||||||
Callbacks: callbacks,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("unexpected error starting leader election: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
st.elector = le
|
|
||||||
return st
|
|
||||||
}
|
|
||||||
|
|
||||||
// runningAddresses returns a list of IP addresses and/or FQDN where the
|
|
||||||
// ingress controller is currently running
|
|
||||||
func (s *statusSync) runningAddresses() ([]string, error) {
|
|
||||||
if s.PublishService != "" {
|
|
||||||
ns, name, _ := k8s.ParseNameNS(s.PublishService)
|
|
||||||
svc, err := s.Client.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs := []string{}
|
|
||||||
for _, ip := range svc.Status.LoadBalancer.Ingress {
|
|
||||||
if ip.IP == "" {
|
|
||||||
addrs = append(addrs, ip.Hostname)
|
|
||||||
} else {
|
|
||||||
addrs = append(addrs, ip.IP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ip := range svc.Spec.ExternalIPs {
|
|
||||||
addrs = append(addrs, ip)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get information about all the pods running the ingress controller
|
|
||||||
pods, err := s.Client.CoreV1().Pods(s.pod.Namespace).List(metav1.ListOptions{
|
|
||||||
LabelSelector: labels.SelectorFromSet(s.pod.Labels).String(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs := []string{}
|
|
||||||
for _, pod := range pods.Items {
|
|
||||||
name := k8s.GetNodeIP(s.Client, pod.Spec.NodeName)
|
|
||||||
if !ingress_strings.StringInSlice(name, addrs) {
|
|
||||||
addrs = append(addrs, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addrs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *statusSync) isRunningMultiplePods() bool {
|
|
||||||
pods, err := s.Client.CoreV1().Pods(s.pod.Namespace).List(metav1.ListOptions{
|
|
||||||
LabelSelector: labels.SelectorFromSet(s.pod.Labels).String(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(pods.Items) > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// sliceToStatus converts a slice of IP and/or hostnames to LoadBalancerIngress
|
|
||||||
func sliceToStatus(endpoints []string) []apiv1.LoadBalancerIngress {
|
|
||||||
lbi := []apiv1.LoadBalancerIngress{}
|
|
||||||
for _, ep := range endpoints {
|
|
||||||
if net.ParseIP(ep) == nil {
|
|
||||||
lbi = append(lbi, apiv1.LoadBalancerIngress{Hostname: ep})
|
|
||||||
} else {
|
|
||||||
lbi = append(lbi, apiv1.LoadBalancerIngress{IP: ep})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(lbi, func(a, b int) bool {
|
|
||||||
return lbi[a].IP < lbi[b].IP
|
|
||||||
})
|
|
||||||
|
|
||||||
return lbi
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateStatus changes the status information of Ingress rules
|
|
||||||
// If the backend function CustomIngressStatus returns a value different
|
|
||||||
// of nil then it uses the returned value or the newIngressPoint values
|
|
||||||
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
|
||||||
ings := s.IngressLister.List()
|
|
||||||
|
|
||||||
p := pool.NewLimited(10)
|
|
||||||
defer p.Close()
|
|
||||||
|
|
||||||
batch := p.Batch()
|
|
||||||
|
|
||||||
for _, cur := range ings {
|
|
||||||
ing := cur.(*extensions.Ingress)
|
|
||||||
|
|
||||||
if !class.IsValid(ing, s.Config.IngressClass, s.Config.DefaultIngressClass) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.Queue(runUpdate(ing, newIngressPoint, s.Client, s.CustomIngressStatus))
|
|
||||||
}
|
|
||||||
|
|
||||||
batch.QueueComplete()
|
|
||||||
batch.WaitAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runUpdate(ing *extensions.Ingress, status []apiv1.LoadBalancerIngress,
|
|
||||||
client clientset.Interface,
|
|
||||||
statusFunc func(*extensions.Ingress) []apiv1.LoadBalancerIngress) pool.WorkFunc {
|
|
||||||
return func(wu pool.WorkUnit) (interface{}, error) {
|
|
||||||
if wu.IsCancelled() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs := status
|
|
||||||
ca := statusFunc(ing)
|
|
||||||
if ca != nil {
|
|
||||||
addrs = ca
|
|
||||||
}
|
|
||||||
sort.SliceStable(addrs, lessLoadBalancerIngress(addrs))
|
|
||||||
|
|
||||||
curIPs := ing.Status.LoadBalancer.Ingress
|
|
||||||
sort.SliceStable(curIPs, lessLoadBalancerIngress(curIPs))
|
|
||||||
|
|
||||||
if ingressSliceEqual(addrs, curIPs) {
|
|
||||||
glog.V(3).Infof("skipping update of Ingress %v/%v (no change)", ing.Namespace, ing.Name)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ingClient := client.Extensions().Ingresses(ing.Namespace)
|
|
||||||
|
|
||||||
currIng, err := ingClient.Get(ing.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, fmt.Sprintf("unexpected error searching Ingress %v/%v", ing.Namespace, ing.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("updating Ingress %v/%v status to %v", currIng.Namespace, currIng.Name, addrs)
|
|
||||||
currIng.Status.LoadBalancer.Ingress = addrs
|
|
||||||
_, err = ingClient.UpdateStatus(currIng)
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("error updating ingress rule: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lessLoadBalancerIngress(addrs []apiv1.LoadBalancerIngress) func(int, int) bool {
|
|
||||||
return func(a, b int) bool {
|
|
||||||
switch strings.Compare(addrs[a].Hostname, addrs[b].Hostname) {
|
|
||||||
case -1:
|
|
||||||
return true
|
|
||||||
case 1:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return addrs[a].IP < addrs[b].IP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ingressSliceEqual(lhs, rhs []apiv1.LoadBalancerIngress) bool {
|
|
||||||
if len(lhs) != len(rhs) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range lhs {
|
|
||||||
if lhs[i].IP != rhs[i].IP {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs[i].Hostname != rhs[i].Hostname {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,463 +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 status
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
testclient "k8s.io/client-go/kubernetes/fake"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/class"
|
|
||||||
"k8s.io/ingress-nginx/ingress/store"
|
|
||||||
"k8s.io/ingress-nginx/k8s"
|
|
||||||
"k8s.io/ingress-nginx/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildLoadBalancerIngressByIP() []apiv1.LoadBalancerIngress {
|
|
||||||
return []apiv1.LoadBalancerIngress{
|
|
||||||
{
|
|
||||||
IP: "10.0.0.1",
|
|
||||||
Hostname: "foo1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "10.0.0.2",
|
|
||||||
Hostname: "foo2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "10.0.0.3",
|
|
||||||
Hostname: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
IP: "",
|
|
||||||
Hostname: "foo4",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSimpleClientSet() *testclient.Clientset {
|
|
||||||
return testclient.NewSimpleClientset(
|
|
||||||
&apiv1.PodList{Items: []apiv1.Pod{
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo1",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
Labels: map[string]string{
|
|
||||||
"lable_sig": "foo_pod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: apiv1.PodSpec{
|
|
||||||
NodeName: "foo_node_2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo2",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
Labels: map[string]string{
|
|
||||||
"lable_sig": "foo_no",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo3",
|
|
||||||
Namespace: api.NamespaceSystem,
|
|
||||||
Labels: map[string]string{
|
|
||||||
"lable_sig": "foo_pod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Spec: apiv1.PodSpec{
|
|
||||||
NodeName: "foo_node_2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
&apiv1.ServiceList{Items: []apiv1.Service{
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Status: apiv1.ServiceStatus{
|
|
||||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
|
||||||
Ingress: buildLoadBalancerIngressByIP(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_non_exist",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
&apiv1.NodeList{Items: []apiv1.Node{
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_node_1",
|
|
||||||
},
|
|
||||||
Status: apiv1.NodeStatus{
|
|
||||||
Addresses: []apiv1.NodeAddress{
|
|
||||||
{
|
|
||||||
Type: apiv1.NodeInternalIP,
|
|
||||||
Address: "10.0.0.1",
|
|
||||||
}, {
|
|
||||||
Type: apiv1.NodeExternalIP,
|
|
||||||
Address: "10.0.0.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_node_2",
|
|
||||||
},
|
|
||||||
Status: apiv1.NodeStatus{
|
|
||||||
Addresses: []apiv1.NodeAddress{
|
|
||||||
{
|
|
||||||
Type: apiv1.NodeInternalIP,
|
|
||||||
Address: "11.0.0.1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: apiv1.NodeExternalIP,
|
|
||||||
Address: "11.0.0.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
&apiv1.EndpointsList{Items: []apiv1.Endpoints{
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "ingress-controller-leader",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
SelfLink: "/api/v1/namespaces/default/endpoints/ingress-controller-leader",
|
|
||||||
},
|
|
||||||
}}},
|
|
||||||
&extensions.IngressList{Items: buildExtensionsIngresses()},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fakeSynFn(interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildExtensionsIngresses() []extensions.Ingress {
|
|
||||||
return []extensions.Ingress{
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_ingress_1",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Status: extensions.IngressStatus{
|
|
||||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
|
||||||
Ingress: []apiv1.LoadBalancerIngress{
|
|
||||||
{
|
|
||||||
IP: "10.0.0.1",
|
|
||||||
Hostname: "foo1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_ingress_different_class",
|
|
||||||
Namespace: api.NamespaceDefault,
|
|
||||||
Annotations: map[string]string{
|
|
||||||
class.IngressKey: "no-nginx",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Status: extensions.IngressStatus{
|
|
||||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
|
||||||
Ingress: []apiv1.LoadBalancerIngress{
|
|
||||||
{
|
|
||||||
IP: "0.0.0.0",
|
|
||||||
Hostname: "foo.bar.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_ingress_2",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Status: extensions.IngressStatus{
|
|
||||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
|
||||||
Ingress: []apiv1.LoadBalancerIngress{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildIngressListener() store.IngressLister {
|
|
||||||
s := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
|
||||||
s.Add(&extensions.Ingress{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_ingress_non_01",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
}})
|
|
||||||
s.Add(&extensions.Ingress{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "foo_ingress_1",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Status: extensions.IngressStatus{
|
|
||||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
|
||||||
Ingress: buildLoadBalancerIngressByIP(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return store.IngressLister{Store: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildStatusSync() statusSync {
|
|
||||||
return statusSync{
|
|
||||||
pod: &k8s.PodInfo{
|
|
||||||
Name: "foo_base_pod",
|
|
||||||
Namespace: apiv1.NamespaceDefault,
|
|
||||||
Labels: map[string]string{
|
|
||||||
"lable_sig": "foo_pod",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
syncQueue: task.NewTaskQueue(fakeSynFn),
|
|
||||||
Config: Config{
|
|
||||||
Client: buildSimpleClientSet(),
|
|
||||||
PublishService: apiv1.NamespaceDefault + "/" + "foo",
|
|
||||||
IngressLister: buildIngressListener(),
|
|
||||||
CustomIngressStatus: func(*extensions.Ingress) []apiv1.LoadBalancerIngress {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatusActions(t *testing.T) {
|
|
||||||
// make sure election can be created
|
|
||||||
os.Setenv("POD_NAME", "foo1")
|
|
||||||
os.Setenv("POD_NAMESPACE", apiv1.NamespaceDefault)
|
|
||||||
c := Config{
|
|
||||||
Client: buildSimpleClientSet(),
|
|
||||||
PublishService: "",
|
|
||||||
IngressLister: buildIngressListener(),
|
|
||||||
DefaultIngressClass: "nginx",
|
|
||||||
IngressClass: "",
|
|
||||||
UpdateStatusOnShutdown: true,
|
|
||||||
CustomIngressStatus: func(*extensions.Ingress) []apiv1.LoadBalancerIngress {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// create object
|
|
||||||
fkSync := NewStatusSyncer(c)
|
|
||||||
if fkSync == nil {
|
|
||||||
t.Fatalf("expected a valid Sync")
|
|
||||||
}
|
|
||||||
|
|
||||||
fk := fkSync.(statusSync)
|
|
||||||
|
|
||||||
ns := make(chan struct{})
|
|
||||||
// start it and wait for the election and syn actions
|
|
||||||
go fk.Run(ns)
|
|
||||||
// wait for the election
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
// execute sync
|
|
||||||
fk.sync("just-test")
|
|
||||||
// PublishService is empty, so the running address is: ["11.0.0.2"]
|
|
||||||
// after updated, the ingress's ip should only be "11.0.0.2"
|
|
||||||
newIPs := []apiv1.LoadBalancerIngress{{
|
|
||||||
IP: "11.0.0.2",
|
|
||||||
}}
|
|
||||||
fooIngress1, err1 := fk.Client.Extensions().Ingresses(apiv1.NamespaceDefault).Get("foo_ingress_1", metav1.GetOptions{})
|
|
||||||
if err1 != nil {
|
|
||||||
t.Fatalf("unexpected error")
|
|
||||||
}
|
|
||||||
fooIngress1CurIPs := fooIngress1.Status.LoadBalancer.Ingress
|
|
||||||
if !ingressSliceEqual(fooIngress1CurIPs, newIPs) {
|
|
||||||
t.Fatalf("returned %v but expected %v", fooIngress1CurIPs, newIPs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute shutdown
|
|
||||||
fk.Shutdown()
|
|
||||||
// ingress should be empty
|
|
||||||
newIPs2 := []apiv1.LoadBalancerIngress{}
|
|
||||||
fooIngress2, err2 := fk.Client.Extensions().Ingresses(apiv1.NamespaceDefault).Get("foo_ingress_1", metav1.GetOptions{})
|
|
||||||
if err2 != nil {
|
|
||||||
t.Fatalf("unexpected error")
|
|
||||||
}
|
|
||||||
fooIngress2CurIPs := fooIngress2.Status.LoadBalancer.Ingress
|
|
||||||
if !ingressSliceEqual(fooIngress2CurIPs, newIPs2) {
|
|
||||||
t.Fatalf("returned %v but expected %v", fooIngress2CurIPs, newIPs2)
|
|
||||||
}
|
|
||||||
|
|
||||||
oic, err := fk.Client.Extensions().Ingresses(api.NamespaceDefault).Get("foo_ingress_different_class", metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error")
|
|
||||||
}
|
|
||||||
if oic.Status.LoadBalancer.Ingress[0].IP != "0.0.0.0" && oic.Status.LoadBalancer.Ingress[0].Hostname != "foo.bar.com" {
|
|
||||||
t.Fatalf("invalid ingress status for rule with different class")
|
|
||||||
}
|
|
||||||
|
|
||||||
// end test
|
|
||||||
ns <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallback(t *testing.T) {
|
|
||||||
buildStatusSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyfunc(t *testing.T) {
|
|
||||||
fk := buildStatusSync()
|
|
||||||
i := "foo_base_pod"
|
|
||||||
r, err := fk.keyfunc(i)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error")
|
|
||||||
}
|
|
||||||
if r != i {
|
|
||||||
t.Errorf("returned %v but expected %v", r, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunningAddresessWithPublishService(t *testing.T) {
|
|
||||||
fk := buildStatusSync()
|
|
||||||
|
|
||||||
r, _ := fk.runningAddresses()
|
|
||||||
if r == nil {
|
|
||||||
t.Fatalf("returned nil but expected valid []string")
|
|
||||||
}
|
|
||||||
rl := len(r)
|
|
||||||
if len(r) != 4 {
|
|
||||||
t.Errorf("returned %v but expected %v", rl, 4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRunningAddresessWithPods(t *testing.T) {
|
|
||||||
fk := buildStatusSync()
|
|
||||||
fk.PublishService = ""
|
|
||||||
|
|
||||||
r, _ := fk.runningAddresses()
|
|
||||||
if r == nil {
|
|
||||||
t.Fatalf("returned nil but expected valid []string")
|
|
||||||
}
|
|
||||||
rl := len(r)
|
|
||||||
if len(r) != 1 {
|
|
||||||
t.Fatalf("returned %v but expected %v", rl, 1)
|
|
||||||
}
|
|
||||||
rv := r[0]
|
|
||||||
if rv != "11.0.0.2" {
|
|
||||||
t.Errorf("returned %v but expected %v", rv, "11.0.0.2")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: this test requires a refactoring
|
|
||||||
func TestUpdateStatus(t *testing.T) {
|
|
||||||
fk := buildStatusSync()
|
|
||||||
newIPs := buildLoadBalancerIngressByIP()
|
|
||||||
fk.updateStatus(newIPs)
|
|
||||||
|
|
||||||
fooIngress1, err1 := fk.Client.Extensions().Ingresses(apiv1.NamespaceDefault).Get("foo_ingress_1", metav1.GetOptions{})
|
|
||||||
if err1 != nil {
|
|
||||||
t.Fatalf("unexpected error")
|
|
||||||
}
|
|
||||||
fooIngress1CurIPs := fooIngress1.Status.LoadBalancer.Ingress
|
|
||||||
if !ingressSliceEqual(fooIngress1CurIPs, newIPs) {
|
|
||||||
t.Fatalf("returned %v but expected %v", fooIngress1CurIPs, newIPs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fooIngress2, err2 := fk.Client.Extensions().Ingresses(apiv1.NamespaceDefault).Get("foo_ingress_2", metav1.GetOptions{})
|
|
||||||
if err2 != nil {
|
|
||||||
t.Fatalf("unexpected error")
|
|
||||||
}
|
|
||||||
fooIngress2CurIPs := fooIngress2.Status.LoadBalancer.Ingress
|
|
||||||
if !ingressSliceEqual(fooIngress2CurIPs, []apiv1.LoadBalancerIngress{}) {
|
|
||||||
t.Fatalf("returned %v but expected %v", fooIngress2CurIPs, []apiv1.LoadBalancerIngress{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
func TestSliceToStatus(t *testing.T) {
|
|
||||||
fkEndpoints := []string{
|
|
||||||
"10.0.0.1",
|
|
||||||
"2001:db8::68",
|
|
||||||
"opensource-k8s-ingress",
|
|
||||||
}
|
|
||||||
|
|
||||||
r := sliceToStatus(fkEndpoints)
|
|
||||||
|
|
||||||
if r == nil {
|
|
||||||
t.Fatalf("returned nil but expected a valid []apiv1.LoadBalancerIngress")
|
|
||||||
}
|
|
||||||
rl := len(r)
|
|
||||||
if rl != 3 {
|
|
||||||
t.Fatalf("returned %v but expected %v", rl, 3)
|
|
||||||
}
|
|
||||||
re1 := r[0]
|
|
||||||
if re1.Hostname != "opensource-k8s-ingress" {
|
|
||||||
t.Fatalf("returned %v but expected %v", re1, apiv1.LoadBalancerIngress{Hostname: "opensource-k8s-ingress"})
|
|
||||||
}
|
|
||||||
re2 := r[1]
|
|
||||||
if re2.IP != "10.0.0.1" {
|
|
||||||
t.Fatalf("returned %v but expected %v", re2, apiv1.LoadBalancerIngress{IP: "10.0.0.1"})
|
|
||||||
}
|
|
||||||
re3 := r[2]
|
|
||||||
if re3.IP != "2001:db8::68" {
|
|
||||||
t.Fatalf("returned %v but expected %v", re3, apiv1.LoadBalancerIngress{IP: "2001:db8::68"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIngressSliceEqual(t *testing.T) {
|
|
||||||
fk1 := buildLoadBalancerIngressByIP()
|
|
||||||
fk2 := append(buildLoadBalancerIngressByIP(), apiv1.LoadBalancerIngress{
|
|
||||||
IP: "10.0.0.5",
|
|
||||||
Hostname: "foo5",
|
|
||||||
})
|
|
||||||
fk3 := buildLoadBalancerIngressByIP()
|
|
||||||
fk3[0].Hostname = "foo_no_01"
|
|
||||||
fk4 := buildLoadBalancerIngressByIP()
|
|
||||||
fk4[2].IP = "11.0.0.3"
|
|
||||||
|
|
||||||
fooTests := []struct {
|
|
||||||
lhs []apiv1.LoadBalancerIngress
|
|
||||||
rhs []apiv1.LoadBalancerIngress
|
|
||||||
er bool
|
|
||||||
}{
|
|
||||||
{fk1, fk1, true},
|
|
||||||
{fk2, fk1, false},
|
|
||||||
{fk3, fk1, false},
|
|
||||||
{fk4, fk1, false},
|
|
||||||
{fk1, nil, false},
|
|
||||||
{nil, nil, true},
|
|
||||||
{[]apiv1.LoadBalancerIngress{}, []apiv1.LoadBalancerIngress{}, true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fooTest := range fooTests {
|
|
||||||
r := ingressSliceEqual(fooTest.lhs, fooTest.rhs)
|
|
||||||
if r != fooTest.er {
|
|
||||||
t.Errorf("returned %v but expected %v", r, fooTest.er)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
376
ingress/types.go
376
ingress/types.go
|
@ -1,376 +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 ingress
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/auth"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/authreq"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/authtls"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/ipwhitelist"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/proxy"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/ratelimit"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/redirect"
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/rewrite"
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
|
||||||
"k8s.io/ingress-nginx/ingress/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultSSLDirectory defines the location where the SSL certificates will be generated
|
|
||||||
// This directory contains all the SSL certificates that are specified in Ingress rules.
|
|
||||||
// The name of each file is <namespace>-<secret name>.pem. The content is the concatenated
|
|
||||||
// certificate and key.
|
|
||||||
DefaultSSLDirectory = "/ingress-controller/ssl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Controller holds the methods to handle an Ingress backend
|
|
||||||
// TODO (#18): Make sure this is sufficiently supportive of other backends.
|
|
||||||
type Controller interface {
|
|
||||||
// HealthzChecker returns is a named healthz check that returns the ingress
|
|
||||||
// controller status
|
|
||||||
healthz.HealthzChecker
|
|
||||||
|
|
||||||
// OnUpdate callback invoked from the sync queue https://k8s.io/ingress/core/blob/master/pkg/ingress/controller/controller.go#L387
|
|
||||||
// when an update occurs. This is executed frequently because Ingress
|
|
||||||
// controllers watches changes in:
|
|
||||||
// - Ingresses: main work
|
|
||||||
// - Secrets: referenced from Ingress rules with TLS configured
|
|
||||||
// - ConfigMaps: where the controller reads custom configuration
|
|
||||||
// - Services: referenced from Ingress rules and required to obtain
|
|
||||||
// information about ports and annotations
|
|
||||||
// - Endpoints: referenced from Services and what the backend uses
|
|
||||||
// to route traffic
|
|
||||||
// Any update to services, endpoints, secrets (only those referenced from Ingress)
|
|
||||||
// and ingress trigger the execution.
|
|
||||||
// Notifications of type Add, Update and Delete:
|
|
||||||
// https://github.com/kubernetes/kubernetes/blob/master/pkg/client/cache/controller.go#L164
|
|
||||||
//
|
|
||||||
// Configuration returns the translation from Ingress rules containing
|
|
||||||
// information about all the upstreams (service endpoints ) "virtual"
|
|
||||||
// servers (FQDN) and all the locations inside each server. Each
|
|
||||||
// location contains information about all the annotations were configured
|
|
||||||
// https://k8s.io/ingress/core/blob/master/pkg/ingress/types.go#L83
|
|
||||||
// The backend returns an error if was not possible to update the configuration.
|
|
||||||
//
|
|
||||||
OnUpdate(Configuration) error
|
|
||||||
// ConfigMap content of --configmap
|
|
||||||
SetConfig(*apiv1.ConfigMap)
|
|
||||||
// SetListers allows the access of store listers present in the generic controller
|
|
||||||
// This avoid the use of the kubernetes client.
|
|
||||||
SetListers(*StoreLister)
|
|
||||||
// BackendDefaults returns the minimum settings required to configure the
|
|
||||||
// communication to endpoints
|
|
||||||
BackendDefaults() defaults.Backend
|
|
||||||
// Info returns information about the ingress controller
|
|
||||||
Info() *BackendInfo
|
|
||||||
// ConfigureFlags allow to configure more flags before the parsing of
|
|
||||||
// command line arguments
|
|
||||||
ConfigureFlags(*pflag.FlagSet)
|
|
||||||
// OverrideFlags allow the customization of the flags in the backend
|
|
||||||
OverrideFlags(*pflag.FlagSet)
|
|
||||||
// DefaultIngressClass just return the default ingress class
|
|
||||||
DefaultIngressClass() string
|
|
||||||
// UpdateIngressStatus custom callback used to update the status in an Ingress rule
|
|
||||||
// This allows custom implementations
|
|
||||||
// If the function returns nil the standard functions will be executed.
|
|
||||||
UpdateIngressStatus(*extensions.Ingress) []apiv1.LoadBalancerIngress
|
|
||||||
// DefaultEndpoint returns the Endpoint to use as default when the
|
|
||||||
// referenced service does not exists. This should return the content
|
|
||||||
// of to the default backend
|
|
||||||
DefaultEndpoint() Endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreLister returns the configured stores for ingresses, services,
|
|
||||||
// endpoints, secrets and configmaps.
|
|
||||||
type StoreLister struct {
|
|
||||||
Ingress store.IngressLister
|
|
||||||
Service store.ServiceLister
|
|
||||||
Node store.NodeLister
|
|
||||||
Endpoint store.EndpointLister
|
|
||||||
Secret store.SecretLister
|
|
||||||
ConfigMap store.ConfigMapLister
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackendInfo returns information about the backend.
|
|
||||||
// This fields contains information that helps to track issues or to
|
|
||||||
// map the running ingress controller to source code
|
|
||||||
type BackendInfo struct {
|
|
||||||
// Name returns the name of the backend implementation
|
|
||||||
Name string `json:"name"`
|
|
||||||
// Release returns the running version (semver)
|
|
||||||
Release string `json:"release"`
|
|
||||||
// Build returns information about the git commit
|
|
||||||
Build string `json:"build"`
|
|
||||||
// Repository return information about the git repository
|
|
||||||
Repository string `json:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration holds the definition of all the parts required to describe all
|
|
||||||
// ingresses reachable by the ingress controller (using a filter by namespace)
|
|
||||||
type Configuration struct {
|
|
||||||
// Backends are a list of backends used by all the Ingress rules in the
|
|
||||||
// ingress controller. This list includes the default backend
|
|
||||||
Backends []*Backend `json:"backends,omitEmpty"`
|
|
||||||
// Servers
|
|
||||||
Servers []*Server `json:"servers,omitEmpty"`
|
|
||||||
// TCPEndpoints contain endpoints for tcp streams handled by this backend
|
|
||||||
// +optional
|
|
||||||
TCPEndpoints []L4Service `json:"tcpEndpoints,omitempty"`
|
|
||||||
// UDPEndpoints contain endpoints for udp streams handled by this backend
|
|
||||||
// +optional
|
|
||||||
UDPEndpoints []L4Service `json:"udpEndpoints,omitempty"`
|
|
||||||
// PassthroughBackend contains the backends used for SSL passthrough.
|
|
||||||
// It contains information about the associated Server Name Indication (SNI).
|
|
||||||
// +optional
|
|
||||||
PassthroughBackends []*SSLPassthroughBackend `json:"passthroughBackends,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backend describes one or more remote server/s (endpoints) associated with a service
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
type Backend struct {
|
|
||||||
// Name represents an unique apiv1.Service name formatted as <namespace>-<name>-<port>
|
|
||||||
Name string `json:"name"`
|
|
||||||
Service *apiv1.Service `json:"service,omitempty"`
|
|
||||||
Port intstr.IntOrString `json:"port"`
|
|
||||||
// This indicates if the communication protocol between the backend and the endpoint is HTTP or HTTPS
|
|
||||||
// Allowing the use of HTTPS
|
|
||||||
// The endpoint/s must provide a TLS connection.
|
|
||||||
// The certificate used in the endpoint cannot be a self signed certificate
|
|
||||||
Secure bool `json:"secure"`
|
|
||||||
// SecureCACert has the filename and SHA1 of the certificate authorities used to validate
|
|
||||||
// a secured connection to the backend
|
|
||||||
SecureCACert resolver.AuthSSLCert `json:"secureCACert"`
|
|
||||||
// SSLPassthrough indicates that Ingress controller will delegate TLS termination to the endpoints.
|
|
||||||
SSLPassthrough bool `json:"sslPassthrough"`
|
|
||||||
// Endpoints contains the list of endpoints currently running
|
|
||||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
|
||||||
// StickySessionAffinitySession contains the StickyConfig object with stickness configuration
|
|
||||||
SessionAffinity SessionAffinityConfig `json:"sessionAffinityConfig"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionAffinityConfig describes different affinity configurations for new sessions.
|
|
||||||
// Once a session is mapped to a backend based on some affinity setting, it
|
|
||||||
// retains that mapping till the backend goes down, or the ingress controller
|
|
||||||
// restarts. Exactly one of these values will be set on the upstream, since multiple
|
|
||||||
// affinity values are incompatible. Once set, the backend makes no guarantees
|
|
||||||
// about honoring updates.
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
type SessionAffinityConfig struct {
|
|
||||||
AffinityType string `json:"name"`
|
|
||||||
CookieSessionAffinity CookieSessionAffinity `json:"cookieSessionAffinity"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieSessionAffinity defines the structure used in Affinity configured by Cookies.
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
type CookieSessionAffinity struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
Locations map[string][]string `json:"locations,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoint describes a kubernetes endpoint in a backend
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
type Endpoint struct {
|
|
||||||
// Address IP address of the endpoint
|
|
||||||
Address string `json:"address"`
|
|
||||||
// Port number of the TCP port
|
|
||||||
Port string `json:"port"`
|
|
||||||
// MaxFails returns the number of unsuccessful attempts to communicate
|
|
||||||
// allowed before this should be considered dow.
|
|
||||||
// Setting 0 indicates that the check is performed by a Kubernetes probe
|
|
||||||
MaxFails int `json:"maxFails"`
|
|
||||||
// FailTimeout returns the time in seconds during which the specified number
|
|
||||||
// of unsuccessful attempts to communicate with the server should happen
|
|
||||||
// to consider the endpoint unavailable
|
|
||||||
FailTimeout int `json:"failTimeout"`
|
|
||||||
// Target returns a reference to the object providing the endpoint
|
|
||||||
Target *apiv1.ObjectReference `json:"target,omipempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server describes a website
|
|
||||||
type Server struct {
|
|
||||||
// Hostname returns the FQDN of the server
|
|
||||||
Hostname string `json:"hostname"`
|
|
||||||
// SSLPassthrough indicates if the TLS termination is realized in
|
|
||||||
// the server or in the remote endpoint
|
|
||||||
SSLPassthrough bool `json:"sslPassthrough"`
|
|
||||||
// SSLCertificate path to the SSL certificate on disk
|
|
||||||
SSLCertificate string `json:"sslCertificate"`
|
|
||||||
// SSLFullChainCertificate path to the SSL certificate on disk
|
|
||||||
// This certificate contains the full chain (ca + intermediates + cert)
|
|
||||||
SSLFullChainCertificate string `json:"sslFullChainCertificate"`
|
|
||||||
// SSLExpireTime has the expire date of this certificate
|
|
||||||
SSLExpireTime time.Time `json:"sslExpireTime"`
|
|
||||||
// SSLPemChecksum returns the checksum of the certificate file on disk.
|
|
||||||
// There is no restriction in the hash generator. This checksim can be
|
|
||||||
// used to determine if the secret changed without the use of file
|
|
||||||
// system notifications
|
|
||||||
SSLPemChecksum string `json:"sslPemChecksum"`
|
|
||||||
// Locations list of URIs configured in the server.
|
|
||||||
Locations []*Location `json:"locations,omitempty"`
|
|
||||||
// Alias return the alias of the server name
|
|
||||||
Alias string `json:"alias,omitempty"`
|
|
||||||
// RedirectFromToWWW returns if a redirect to/from prefix www is required
|
|
||||||
RedirectFromToWWW bool `json:"redirectFromToWWW,omitempty"`
|
|
||||||
// CertificateAuth indicates the this server requires mutual authentication
|
|
||||||
// +optional
|
|
||||||
CertificateAuth authtls.AuthSSLConfig `json:"certificateAuth"`
|
|
||||||
|
|
||||||
// ServerSnippet returns the snippet of server
|
|
||||||
// +optional
|
|
||||||
ServerSnippet string `json:"serverSnippet"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Location describes an URI inside a server.
|
|
||||||
// Also contains additional information about annotations in the Ingress.
|
|
||||||
//
|
|
||||||
// Important:
|
|
||||||
// The implementation of annotations is optional
|
|
||||||
//
|
|
||||||
// In some cases when more than one annotations is defined a particular order in the execution
|
|
||||||
// is required.
|
|
||||||
// The chain in the execution order of annotations should be:
|
|
||||||
// - Whitelist
|
|
||||||
// - RateLimit
|
|
||||||
// - BasicDigestAuth
|
|
||||||
// - ExternalAuth
|
|
||||||
// - Redirect
|
|
||||||
type Location struct {
|
|
||||||
// Path is an extended POSIX regex as defined by IEEE Std 1003.1,
|
|
||||||
// (i.e this follows the egrep/unix syntax, not the perl syntax)
|
|
||||||
// matched against the path of an incoming request. Currently it can
|
|
||||||
// contain characters disallowed from the conventional "path"
|
|
||||||
// part of a URL as defined by RFC 3986. Paths must begin with
|
|
||||||
// a '/'. If unspecified, the path defaults to a catch all sending
|
|
||||||
// traffic to the backend.
|
|
||||||
Path string `json:"path"`
|
|
||||||
// IsDefBackend indicates if service specified in the Ingress
|
|
||||||
// contains active endpoints or not. Returning true means the location
|
|
||||||
// uses the default backend.
|
|
||||||
IsDefBackend bool `json:"isDefBackend"`
|
|
||||||
// Ingress returns the ingress from which this location was generated
|
|
||||||
Ingress *extensions.Ingress `json:"ingress"`
|
|
||||||
// Backend describes the name of the backend to use.
|
|
||||||
Backend string `json:"backend"`
|
|
||||||
// Service describes the referenced services from the ingress
|
|
||||||
Service *apiv1.Service `json:"service,omitempty"`
|
|
||||||
// Port describes to which port from the service
|
|
||||||
Port intstr.IntOrString `json:"port"`
|
|
||||||
// Overwrite the Host header passed into the backend. Defaults to
|
|
||||||
// vhost of the incoming request.
|
|
||||||
// +optional
|
|
||||||
UpstreamVhost string `json:"upstream-vhost"`
|
|
||||||
// BasicDigestAuth returns authentication configuration for
|
|
||||||
// an Ingress rule.
|
|
||||||
// +optional
|
|
||||||
BasicDigestAuth auth.BasicDigest `json:"basicDigestAuth,omitempty"`
|
|
||||||
// Denied returns an error when this location cannot not be allowed
|
|
||||||
// Requesting a denied location should return HTTP code 403.
|
|
||||||
Denied error `json:"denied,omitempty"`
|
|
||||||
// EnableCORS indicates if path must support CORS
|
|
||||||
// +optional
|
|
||||||
EnableCORS bool `json:"enableCors,omitempty"`
|
|
||||||
// ExternalAuth indicates the access to this location requires
|
|
||||||
// authentication using an external provider
|
|
||||||
// +optional
|
|
||||||
ExternalAuth authreq.External `json:"externalAuth,omitempty"`
|
|
||||||
// RateLimit describes a limit in the number of connections per IP
|
|
||||||
// address or connections per second.
|
|
||||||
// The Redirect annotation precedes RateLimit
|
|
||||||
// +optional
|
|
||||||
RateLimit ratelimit.RateLimit `json:"rateLimit,omitempty"`
|
|
||||||
// Redirect describes a temporal o permanent redirection this location.
|
|
||||||
// +optional
|
|
||||||
Redirect redirect.Redirect `json:"redirect,omitempty"`
|
|
||||||
// Rewrite describes the redirection this location.
|
|
||||||
// +optional
|
|
||||||
Rewrite rewrite.Redirect `json:"rewrite,omitempty"`
|
|
||||||
// Whitelist indicates only connections from certain client
|
|
||||||
// addresses or networks are allowed.
|
|
||||||
// +optional
|
|
||||||
Whitelist ipwhitelist.SourceRange `json:"whitelist,omitempty"`
|
|
||||||
// Proxy contains information about timeouts and buffer sizes
|
|
||||||
// to be used in connections against endpoints
|
|
||||||
// +optional
|
|
||||||
Proxy proxy.Configuration `json:"proxy,omitempty"`
|
|
||||||
// UsePortInRedirects indicates if redirects must specify the port
|
|
||||||
// +optional
|
|
||||||
UsePortInRedirects bool `json:"usePortInRedirects"`
|
|
||||||
// VtsFilterKey contains the vts filter key on the location level
|
|
||||||
// https://github.com/vozlt/nginx-module-vts#vhost_traffic_status_filter_by_set_key
|
|
||||||
// +optional
|
|
||||||
VtsFilterKey string `json:"vtsFilterKey,omitempty"`
|
|
||||||
// ConfigurationSnippet contains additional configuration for the backend
|
|
||||||
// to be considered in the configuration of the location
|
|
||||||
ConfigurationSnippet string `json:"configurationSnippet"`
|
|
||||||
// ClientBodyBufferSize allows for the configuration of the client body
|
|
||||||
// buffer size for a specific location.
|
|
||||||
// +optional
|
|
||||||
ClientBodyBufferSize string `json:"clientBodyBufferSize,omitempty"`
|
|
||||||
// DefaultBackend allows the use of a custom default backend for this location.
|
|
||||||
// +optional
|
|
||||||
DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SSLPassthroughBackend describes a SSL upstream server configured
|
|
||||||
// as passthrough (no TLS termination in the ingress controller)
|
|
||||||
// The endpoints must provide the TLS termination exposing the required SSL certificate.
|
|
||||||
// The ingress controller only pipes the underlying TCP connection
|
|
||||||
type SSLPassthroughBackend struct {
|
|
||||||
Service *apiv1.Service `json:"service,omitEmpty"`
|
|
||||||
Port intstr.IntOrString `json:"port"`
|
|
||||||
// Backend describes the endpoints to use.
|
|
||||||
Backend string `json:"namespace,omitempty"`
|
|
||||||
// Hostname returns the FQDN of the server
|
|
||||||
Hostname string `json:"hostname"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// L4Service describes a L4 Ingress service.
|
|
||||||
type L4Service struct {
|
|
||||||
// Port external port to expose
|
|
||||||
Port int `json:"port"`
|
|
||||||
// Backend of the service
|
|
||||||
Backend L4Backend `json:"backend"`
|
|
||||||
// Endpoints active endpoints of the service
|
|
||||||
Endpoints []Endpoint `json:"endpoins,omitEmpty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// L4Backend describes the kubernetes service behind L4 Ingress service
|
|
||||||
type L4Backend struct {
|
|
||||||
Port intstr.IntOrString `json:"port"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Namespace string `json:"namespace"`
|
|
||||||
Protocol apiv1.Protocol `json:"protocol"`
|
|
||||||
// +optional
|
|
||||||
ProxyProtocol ProxyProtocol `json:"proxyProtocol"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProxyProtocol describes the proxy protocol configuration
|
|
||||||
type ProxyProtocol struct {
|
|
||||||
Decode bool `json:"decode"`
|
|
||||||
Encode bool `json:"encode"`
|
|
||||||
}
|
|
41
pkg/ingress/annotations/alias/main.go
Normal file
41
pkg/ingress/annotations/alias/main.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
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 alias
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/server-alias"
|
||||||
|
)
|
||||||
|
|
||||||
|
type alias struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new Alias annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return alias{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(annotation, ing)
|
||||||
|
}
|
171
pkg/ingress/annotations/auth/main.go
Normal file
171
pkg/ingress/annotations/auth/main.go
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
api "k8s.io/api/core/v1"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/file"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
authType = "ingress.kubernetes.io/auth-type"
|
||||||
|
authSecret = "ingress.kubernetes.io/auth-secret"
|
||||||
|
authRealm = "ingress.kubernetes.io/auth-realm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
authTypeRegex = regexp.MustCompile(`basic|digest`)
|
||||||
|
// AuthDirectory default directory used to store files
|
||||||
|
// to authenticate request
|
||||||
|
AuthDirectory = "/etc/ingress-controller/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasicDigest returns authentication configuration for an Ingress rule
|
||||||
|
type BasicDigest struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Realm string `json:"realm"`
|
||||||
|
File string `json:"file"`
|
||||||
|
Secured bool `json:"secured"`
|
||||||
|
FileSHA string `json:"fileSha"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two BasicDigest types
|
||||||
|
func (bd1 *BasicDigest) Equal(bd2 *BasicDigest) bool {
|
||||||
|
if bd1 == bd2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if bd1 == nil || bd2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bd1.Type != bd2.Type {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bd1.Realm != bd2.Realm {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bd1.File != bd2.File {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bd1.Secured != bd2.Secured {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bd1.FileSHA != bd2.FileSHA {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type auth struct {
|
||||||
|
secretResolver resolver.Secret
|
||||||
|
authDirectory string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new authentication annotation parser
|
||||||
|
func NewParser(authDirectory string, sr resolver.Secret) parser.IngressAnnotation {
|
||||||
|
os.MkdirAll(authDirectory, 0755)
|
||||||
|
|
||||||
|
currPath := authDirectory
|
||||||
|
for currPath != "/" {
|
||||||
|
currPath = path.Dir(currPath)
|
||||||
|
err := os.Chmod(currPath, 0755)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth{sr, authDirectory}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress
|
||||||
|
// 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) {
|
||||||
|
at, err := parser.GetStringAnnotation(authType, ing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !authTypeRegex.MatchString(at) {
|
||||||
|
return nil, ing_errors.NewLocationDenied("invalid authentication type")
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := parser.GetStringAnnotation(authSecret, ing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "error reading secret name from annotation"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
|
||||||
|
secret, err := a.secretResolver.GetSecret(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrapf(err, "unexpected error reading secret %v", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realm, _ := parser.GetStringAnnotation(authRealm, ing)
|
||||||
|
|
||||||
|
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
|
||||||
|
err = dumpSecret(passFile, secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BasicDigest{
|
||||||
|
Type: at,
|
||||||
|
Realm: realm,
|
||||||
|
File: passFile,
|
||||||
|
Secured: true,
|
||||||
|
FileSHA: file.SHA1(passFile),
|
||||||
|
}, 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 {
|
||||||
|
val, ok := secret.Data["auth"]
|
||||||
|
if !ok {
|
||||||
|
return ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Errorf("the secret %v does not contain a key with value auth", secret.Name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check permissions required
|
||||||
|
err := ioutil.WriteFile(filename, val, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "unexpected error creating password file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
183
pkg/ingress/annotations/authreq/main.go
Normal file
183
pkg/ingress/annotations/authreq/main.go
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 authreq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// external URL that provides the authentication
|
||||||
|
authURL = "ingress.kubernetes.io/auth-url"
|
||||||
|
authSigninURL = "ingress.kubernetes.io/auth-signin"
|
||||||
|
authMethod = "ingress.kubernetes.io/auth-method"
|
||||||
|
authBody = "ingress.kubernetes.io/auth-send-body"
|
||||||
|
authHeaders = "ingress.kubernetes.io/auth-response-headers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// External returns external authentication configuration for an Ingress rule
|
||||||
|
type External 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"`
|
||||||
|
SendBody bool `json:"sendBody"`
|
||||||
|
ResponseHeaders []string `json:"responseHeaders,omitEmpty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two External types
|
||||||
|
func (e1 *External) Equal(e2 *External) bool {
|
||||||
|
if e1 == e2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if e1 == nil || e2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e1.URL != e2.URL {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e1.Host != e2.Host {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e1.SigninURL != e2.SigninURL {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e1.Method != e2.Method {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e1.SendBody != e2.SendBody {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if e1.Method != e2.Method {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep1 := range e1.ResponseHeaders {
|
||||||
|
found := false
|
||||||
|
for _, ep2 := range e2.ResponseHeaders {
|
||||||
|
if ep1 == ep2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
|
||||||
|
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func validMethod(method string) bool {
|
||||||
|
if len(method) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range methods {
|
||||||
|
if method == m {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func validHeader(header string) bool {
|
||||||
|
return headerRegexp.Match([]byte(header))
|
||||||
|
}
|
||||||
|
|
||||||
|
type authReq struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new authentication request annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return authReq{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
// rule used to use an external URL as source for authentication
|
||||||
|
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
str, err := parser.GetStringAnnotation(authURL, ing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
signin, _ := parser.GetStringAnnotation(authSigninURL, ing)
|
||||||
|
|
||||||
|
ur, err := url.Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ur.Scheme == "" {
|
||||||
|
return nil, ing_errors.NewLocationDenied("url scheme is empty")
|
||||||
|
}
|
||||||
|
if ur.Host == "" {
|
||||||
|
return nil, ing_errors.NewLocationDenied("url host is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(ur.Host, "..") {
|
||||||
|
return nil, ing_errors.NewLocationDenied("invalid url host")
|
||||||
|
}
|
||||||
|
|
||||||
|
m, _ := parser.GetStringAnnotation(authMethod, ing)
|
||||||
|
if len(m) != 0 && !validMethod(m) {
|
||||||
|
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := []string{}
|
||||||
|
hstr, _ := parser.GetStringAnnotation(authHeaders, ing)
|
||||||
|
if len(hstr) != 0 {
|
||||||
|
|
||||||
|
harr := strings.Split(hstr, ",")
|
||||||
|
for _, header := range harr {
|
||||||
|
header = strings.TrimSpace(header)
|
||||||
|
if len(header) > 0 {
|
||||||
|
if !validHeader(header) {
|
||||||
|
return nil, ing_errors.NewLocationDenied("invalid headers list")
|
||||||
|
}
|
||||||
|
h = append(h, header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb, _ := parser.GetBoolAnnotation(authBody, ing)
|
||||||
|
|
||||||
|
return &External{
|
||||||
|
URL: str,
|
||||||
|
Host: ur.Hostname(),
|
||||||
|
SigninURL: signin,
|
||||||
|
Method: m,
|
||||||
|
SendBody: sb,
|
||||||
|
ResponseHeaders: h,
|
||||||
|
}, nil
|
||||||
|
}
|
131
pkg/ingress/annotations/authtls/main.go
Normal file
131
pkg/ingress/annotations/authtls/main.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 authtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
"k8s.io/ingress-nginx/pkg/k8s"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// name of the secret
|
||||||
|
annotationAuthTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||||
|
annotationAuthVerifyClient = "ingress.kubernetes.io/auth-tls-verify-client"
|
||||||
|
annotationAuthTLSDepth = "ingress.kubernetes.io/auth-tls-verify-depth"
|
||||||
|
annotationAuthTLSErrorPage = "ingress.kubernetes.io/auth-tls-error-page"
|
||||||
|
defaultAuthTLSDepth = 1
|
||||||
|
defaultAuthVerifyClient = "on"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthSSLConfig contains the AuthSSLCert used for muthual autentication
|
||||||
|
// and the configured ValidationDepth
|
||||||
|
type AuthSSLConfig struct {
|
||||||
|
resolver.AuthSSLCert
|
||||||
|
VerifyClient string `json:"verify_client"`
|
||||||
|
ValidationDepth int `json:"validationDepth"`
|
||||||
|
ErrorPage string `json:"errorPage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two AuthSSLConfig types
|
||||||
|
func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool {
|
||||||
|
if assl1 == assl2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if assl1 == nil || assl2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !(&assl1.AuthSSLCert).Equal(&assl2.AuthSSLCert) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if assl1.VerifyClient != assl2.VerifyClient {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if assl1.ValidationDepth != assl2.ValidationDepth {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if assl1.ErrorPage != assl2.ErrorPage {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new TLS authentication annotation parser
|
||||||
|
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
|
||||||
|
return authTLS{resolver}
|
||||||
|
}
|
||||||
|
|
||||||
|
type authTLS struct {
|
||||||
|
certResolver resolver.AuthCertificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
|
||||||
|
if err != nil {
|
||||||
|
return &AuthSSLConfig{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tlsauthsecret == "" {
|
||||||
|
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = k8s.ParseNameNS(tlsauthsecret)
|
||||||
|
if err != nil {
|
||||||
|
return &AuthSSLConfig{}, ing_errors.NewLocationDenied(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsVerifyClient, err := parser.GetStringAnnotation(annotationAuthVerifyClient, ing)
|
||||||
|
if err != nil || !authVerifyClientRegex.MatchString(tlsVerifyClient) {
|
||||||
|
tlsVerifyClient = defaultAuthVerifyClient
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsdepth, err := parser.GetIntAnnotation(annotationAuthTLSDepth, ing)
|
||||||
|
if err != nil || tlsdepth == 0 {
|
||||||
|
tlsdepth = defaultAuthTLSDepth
|
||||||
|
}
|
||||||
|
|
||||||
|
authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret)
|
||||||
|
if err != nil {
|
||||||
|
return &AuthSSLConfig{}, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "error obtaining certificate"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorpage, err := parser.GetStringAnnotation(annotationAuthTLSErrorPage, ing)
|
||||||
|
if err != nil || errorpage == "" {
|
||||||
|
errorpage = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AuthSSLConfig{
|
||||||
|
AuthSSLCert: *authCert,
|
||||||
|
VerifyClient: tlsVerifyClient,
|
||||||
|
ValidationDepth: tlsdepth,
|
||||||
|
ErrorPage: errorpage,
|
||||||
|
}, nil
|
||||||
|
}
|
55
pkg/ingress/annotations/class/main.go
Normal file
55
pkg/ingress/annotations/class/main.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 class
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IngressKey picks a specific "class" for the Ingress.
|
||||||
|
// The controller only processes Ingresses with this annotation either
|
||||||
|
// unset, or set to either the configured value or the empty string.
|
||||||
|
IngressKey = "kubernetes.io/ingress.class"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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, controller, defClass string) bool {
|
||||||
|
ingress, err := parser.GetStringAnnotation(IngressKey, ing)
|
||||||
|
if err != nil && !errors.IsMissingAnnotations(err) {
|
||||||
|
glog.Warningf("unexpected error reading ingress annotation: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have 2 valid combinations
|
||||||
|
// 1 - ingress with default class | blank annotation on ingress
|
||||||
|
// 2 - ingress with specific class | same annotation on ingress
|
||||||
|
//
|
||||||
|
// and 2 invalid combinations
|
||||||
|
// 3 - ingress with default class | fixed annotation on ingress
|
||||||
|
// 4 - ingress with specific class | different annotation on ingress
|
||||||
|
if ingress == "" && controller == defClass {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return ingress == controller
|
||||||
|
}
|
41
pkg/ingress/annotations/clientbodybuffersize/main.go
Normal file
41
pkg/ingress/annotations/clientbodybuffersize/main.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
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 clientbodybuffersize
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/client-body-buffer-size"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientBodyBufferSize struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new clientBodyBufferSize annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return clientBodyBufferSize{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress rule
|
||||||
|
// used to add an client-body-buffer-size to the provided locations
|
||||||
|
func (a clientBodyBufferSize) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
return parser.GetStringAnnotation(annotation, ing)
|
||||||
|
}
|
41
pkg/ingress/annotations/cors/main.go
Normal file
41
pkg/ingress/annotations/cors/main.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
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 cors
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/enable-cors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cors struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new CORS annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return cors{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress
|
||||||
|
// rule used to indicate if the location/s should allows CORS
|
||||||
|
func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
return parser.GetBoolAnnotation(annotation, ing)
|
||||||
|
}
|
57
pkg/ingress/annotations/defaultbackend/main.go
Normal file
57
pkg/ingress/annotations/defaultbackend/main.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 defaultbackend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultBackend = "ingress.kubernetes.io/default-backend"
|
||||||
|
)
|
||||||
|
|
||||||
|
type backend struct {
|
||||||
|
serviceResolver resolver.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new default backend annotation parser
|
||||||
|
func NewParser(sr resolver.Service) parser.IngressAnnotation {
|
||||||
|
return backend{sr}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress to use
|
||||||
|
// a custom default backend
|
||||||
|
func (db backend) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
s, err := parser.GetStringAnnotation(defaultBackend, ing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
|
||||||
|
svc, err := db.serviceResolver.GetService(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "unexpected error reading service %v", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc, nil
|
||||||
|
}
|
66
pkg/ingress/annotations/healthcheck/main.go
Normal file
66
pkg/ingress/annotations/healthcheck/main.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
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 healthcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
upsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
|
||||||
|
upsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Upstream returns the URL and method to use check the status of
|
||||||
|
// the upstream server/s
|
||||||
|
type Upstream struct {
|
||||||
|
MaxFails int `json:"maxFails"`
|
||||||
|
FailTimeout int `json:"failTimeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type healthCheck struct {
|
||||||
|
backendResolver resolver.DefaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new health check annotation parser
|
||||||
|
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
|
return healthCheck{br}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
// rule used to configure upstream check parameters
|
||||||
|
func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
|
if ing.GetAnnotations() == nil {
|
||||||
|
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
|
||||||
|
if err != nil {
|
||||||
|
mf = defBackend.UpstreamMaxFails
|
||||||
|
}
|
||||||
|
|
||||||
|
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
|
||||||
|
if err != nil {
|
||||||
|
ft = defBackend.UpstreamFailTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Upstream{mf, ft}, nil
|
||||||
|
}
|
92
pkg/ingress/annotations/healthcheck/main_test.go
Normal file
92
pkg/ingress/annotations/healthcheck/main_test.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
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 healthcheck
|
||||||
|
|
||||||
|
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/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{UpstreamFailTimeout: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIngressHealthCheck(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[upsMaxFails] = "2"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
hzi, _ := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
nginxHz, ok := hzi.(*Upstream)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Upstream type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxHz.MaxFails != 2 {
|
||||||
|
t.Errorf("expected 2 as max-fails but returned %v", nginxHz.MaxFails)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nginxHz.FailTimeout != 1 {
|
||||||
|
t.Errorf("expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
|
||||||
|
}
|
||||||
|
}
|
113
pkg/ingress/annotations/ipwhitelist/main.go
Normal file
113
pkg/ingress/annotations/ipwhitelist/main.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
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 ipwhitelist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
"k8s.io/ingress-nginx/pkg/net"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
whitelist = "ingress.kubernetes.io/whitelist-source-range"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SourceRange returns the CIDR
|
||||||
|
type SourceRange struct {
|
||||||
|
CIDR []string `json:"cidr,omitEmpty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two SourceRange types
|
||||||
|
func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool {
|
||||||
|
if sr1 == sr2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if sr1 == nil || sr2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sr1.CIDR) != len(sr2.CIDR) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s1l := range sr1.CIDR {
|
||||||
|
found := false
|
||||||
|
for _, sl2 := range sr2.CIDR {
|
||||||
|
if s1l == sl2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipwhitelist struct {
|
||||||
|
backendResolver resolver.DefaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new whitelist annotation parser
|
||||||
|
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
|
return ipwhitelist{br}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
// 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) {
|
||||||
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
|
sort.Strings(defBackend.WhitelistSourceRange)
|
||||||
|
|
||||||
|
val, err := parser.GetStringAnnotation(whitelist, ing)
|
||||||
|
// A missing annotation is not a problem, just use the default
|
||||||
|
if err == ing_errors.ErrMissingAnnotations {
|
||||||
|
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values := strings.Split(val, ",")
|
||||||
|
ipnets, ips, err := net.ParseIPNets(values...)
|
||||||
|
if err != nil && len(ips) == 0 {
|
||||||
|
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "the annotation does not contain a valid IP address or network"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrs := []string{}
|
||||||
|
for k := range ipnets {
|
||||||
|
cidrs = append(cidrs, k)
|
||||||
|
}
|
||||||
|
for k := range ips {
|
||||||
|
cidrs = append(cidrs, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(cidrs)
|
||||||
|
|
||||||
|
return &SourceRange{cidrs}, nil
|
||||||
|
}
|
199
pkg/ingress/annotations/ipwhitelist/main_test.go
Normal file
199
pkg/ingress/annotations/ipwhitelist/main_test.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
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 ipwhitelist
|
||||||
|
|
||||||
|
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/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
defaults.Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return m.Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAnnotations(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
tests := map[string]struct {
|
||||||
|
net string
|
||||||
|
expectCidr []string
|
||||||
|
expectErr bool
|
||||||
|
errOut string
|
||||||
|
}{
|
||||||
|
"test parse a valid net": {
|
||||||
|
net: "10.0.0.0/24",
|
||||||
|
expectCidr: []string{"10.0.0.0/24"},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
"test parse a invalid net": {
|
||||||
|
net: "ww",
|
||||||
|
expectErr: true,
|
||||||
|
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ww",
|
||||||
|
},
|
||||||
|
"test parse a empty net": {
|
||||||
|
net: "",
|
||||||
|
expectErr: true,
|
||||||
|
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ",
|
||||||
|
},
|
||||||
|
"test parse multiple valid cidr": {
|
||||||
|
net: "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24",
|
||||||
|
expectCidr: []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, test := range tests {
|
||||||
|
data := map[string]string{}
|
||||||
|
data[whitelist] = test.net
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
p := NewParser(mockBackend{})
|
||||||
|
i, err := p.Parse(ing)
|
||||||
|
if err != nil && !test.expectErr {
|
||||||
|
t.Errorf("%v:unexpected error: %v", testName, err)
|
||||||
|
}
|
||||||
|
if test.expectErr {
|
||||||
|
if err.Error() != test.errOut {
|
||||||
|
t.Errorf("%v:expected error: %v but %v return", testName, test.errOut, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !test.expectErr {
|
||||||
|
sr, ok := i.(*SourceRange)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%v:expected a SourceRange type", testName)
|
||||||
|
}
|
||||||
|
if !strsEquals(sr.CIDR, test.expectCidr) {
|
||||||
|
t.Errorf("%v:expected %v CIDR but %v returned", testName, test.expectCidr, sr.CIDR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that when we have a whitelist set on the Backend that is used when we
|
||||||
|
// don't have the annotation
|
||||||
|
func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
mockBackend := mockBackend{}
|
||||||
|
mockBackend.Backend.WhitelistSourceRange = []string{"4.4.4.0/24", "1.2.3.4/32"}
|
||||||
|
tests := map[string]struct {
|
||||||
|
net string
|
||||||
|
expectCidr []string
|
||||||
|
expectErr bool
|
||||||
|
errOut string
|
||||||
|
}{
|
||||||
|
"test parse a valid net": {
|
||||||
|
net: "10.0.0.0/24",
|
||||||
|
expectCidr: []string{"10.0.0.0/24"},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
"test parse a invalid net": {
|
||||||
|
net: "ww",
|
||||||
|
expectErr: true,
|
||||||
|
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ww",
|
||||||
|
},
|
||||||
|
"test parse a empty net": {
|
||||||
|
net: "",
|
||||||
|
expectErr: true,
|
||||||
|
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ",
|
||||||
|
},
|
||||||
|
"test parse multiple valid cidr": {
|
||||||
|
net: "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24",
|
||||||
|
expectCidr: []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"},
|
||||||
|
expectErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, test := range tests {
|
||||||
|
data := map[string]string{}
|
||||||
|
data[whitelist] = test.net
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
p := NewParser(mockBackend)
|
||||||
|
i, err := p.Parse(ing)
|
||||||
|
if err != nil && !test.expectErr {
|
||||||
|
t.Errorf("%v:unexpected error: %v", testName, err)
|
||||||
|
}
|
||||||
|
if test.expectErr {
|
||||||
|
if err.Error() != test.errOut {
|
||||||
|
t.Errorf("%v:expected error: %v but %v return", testName, test.errOut, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !test.expectErr {
|
||||||
|
sr, ok := i.(*SourceRange)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%v:expected a SourceRange type", testName)
|
||||||
|
}
|
||||||
|
if !strsEquals(sr.CIDR, test.expectCidr) {
|
||||||
|
t.Errorf("%v:expected %v CIDR but %v returned", testName, test.expectCidr, sr.CIDR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strsEquals(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
102
pkg/ingress/annotations/parser/main.go
Normal file
102
pkg/ingress/annotations/parser/main.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IngressAnnotation has a method to parse annotations located in Ingress
|
||||||
|
type IngressAnnotation interface {
|
||||||
|
Parse(ing *extensions.Ingress) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ingAnnotations map[string]string
|
||||||
|
|
||||||
|
func (a ingAnnotations) parseBool(name string) (bool, error) {
|
||||||
|
val, ok := a[name]
|
||||||
|
if ok {
|
||||||
|
b, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.NewInvalidAnnotationContent(name, val)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
return false, errors.ErrMissingAnnotations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ingAnnotations) parseString(name string) (string, error) {
|
||||||
|
val, ok := a[name]
|
||||||
|
if ok {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return "", errors.ErrMissingAnnotations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ingAnnotations) parseInt(name string) (int, error) {
|
||||||
|
val, ok := a[name]
|
||||||
|
if ok {
|
||||||
|
i, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.NewInvalidAnnotationContent(name, val)
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
return 0, errors.ErrMissingAnnotations
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAnnotation(name string, ing *extensions.Ingress) error {
|
||||||
|
if ing == nil || len(ing.GetAnnotations()) == 0 {
|
||||||
|
return errors.ErrMissingAnnotations
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
return errors.ErrInvalidAnnotationName
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBoolAnnotation extracts a boolean from an Ingress annotation
|
||||||
|
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
|
||||||
|
err := checkAnnotation(name, ing)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return ingAnnotations(ing.GetAnnotations()).parseBool(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStringAnnotation extracts a string from an Ingress annotation
|
||||||
|
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
|
||||||
|
err := checkAnnotation(name, ing)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ingAnnotations(ing.GetAnnotations()).parseString(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIntAnnotation extracts an int from an Ingress annotation
|
||||||
|
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
|
||||||
|
err := checkAnnotation(name, ing)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
|
||||||
|
}
|
48
pkg/ingress/annotations/portinredirect/main.go
Normal file
48
pkg/ingress/annotations/portinredirect/main.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
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 portinredirect
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/use-port-in-redirects"
|
||||||
|
)
|
||||||
|
|
||||||
|
type portInRedirect struct {
|
||||||
|
backendResolver resolver.DefaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new port in redirect annotation parser
|
||||||
|
func NewParser(db resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
|
return portInRedirect{db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
up, err := parser.GetBoolAnnotation(annotation, ing)
|
||||||
|
if err != nil {
|
||||||
|
return a.backendResolver.GetDefaultBackend().UsePortInRedirects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return up, nil
|
||||||
|
}
|
121
pkg/ingress/annotations/portinredirect/main_test.go
Normal file
121
pkg/ingress/annotations/portinredirect/main_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
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 portinredirect
|
||||||
|
|
||||||
|
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/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
usePortInRedirects bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{UsePortInRedirects: m.usePortInRedirects}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPortInRedirect(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
title string
|
||||||
|
usePort *bool
|
||||||
|
def bool
|
||||||
|
exp bool
|
||||||
|
}{
|
||||||
|
{"false - default false", newFalse(), false, false},
|
||||||
|
{"false - default true", newFalse(), true, false},
|
||||||
|
{"no annotation - default false", nil, false, false},
|
||||||
|
{"no annotation - default true", nil, true, true},
|
||||||
|
{"true - default true", newTrue(), true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
if test.usePort != nil {
|
||||||
|
data[annotation] = fmt.Sprintf("%v", *test.usePort)
|
||||||
|
}
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{test.def}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error parsing a valid")
|
||||||
|
}
|
||||||
|
p, ok := i.(bool)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a bool type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p != test.exp {
|
||||||
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.exp, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTrue() *bool {
|
||||||
|
b := true
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFalse() *bool {
|
||||||
|
b := false
|
||||||
|
return &b
|
||||||
|
}
|
160
pkg/ingress/annotations/proxy/main.go
Normal file
160
pkg/ingress/annotations/proxy/main.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
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 proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bodySize = "ingress.kubernetes.io/proxy-body-size"
|
||||||
|
connect = "ingress.kubernetes.io/proxy-connect-timeout"
|
||||||
|
send = "ingress.kubernetes.io/proxy-send-timeout"
|
||||||
|
read = "ingress.kubernetes.io/proxy-read-timeout"
|
||||||
|
bufferSize = "ingress.kubernetes.io/proxy-buffer-size"
|
||||||
|
cookiePath = "ingress.kubernetes.io/proxy-cookie-path"
|
||||||
|
cookieDomain = "ingress.kubernetes.io/proxy-cookie-domain"
|
||||||
|
nextUpstream = "ingress.kubernetes.io/proxy-next-upstream"
|
||||||
|
passParams = "ingress.kubernetes.io/proxy-pass-params"
|
||||||
|
requestBuffering = "ingress.kubernetes.io/proxy-request-buffering"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration returns the proxy timeout to use in the upstream server/s
|
||||||
|
type Configuration struct {
|
||||||
|
BodySize string `json:"bodySize"`
|
||||||
|
ConnectTimeout int `json:"connectTimeout"`
|
||||||
|
SendTimeout int `json:"sendTimeout"`
|
||||||
|
ReadTimeout int `json:"readTimeout"`
|
||||||
|
BufferSize string `json:"bufferSize"`
|
||||||
|
CookieDomain string `json:"cookieDomain"`
|
||||||
|
CookiePath string `json:"cookiePath"`
|
||||||
|
NextUpstream string `json:"nextUpstream"`
|
||||||
|
PassParams string `json:"passParams"`
|
||||||
|
RequestBuffering string `json:"requestBuffering"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two Configuration types
|
||||||
|
func (l1 *Configuration) Equal(l2 *Configuration) bool {
|
||||||
|
if l1 == l2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if l1 == nil || l2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.BodySize != l2.BodySize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.ConnectTimeout != l2.ConnectTimeout {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.SendTimeout != l2.SendTimeout {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.ReadTimeout != l2.ReadTimeout {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.BufferSize != l2.BufferSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.CookieDomain != l2.CookieDomain {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.CookiePath != l2.CookiePath {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.NextUpstream != l2.NextUpstream {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if l1.PassParams != l2.PassParams {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if l1.RequestBuffering != l2.RequestBuffering {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxy struct {
|
||||||
|
backendResolver resolver.DefaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new reverse proxy configuration annotation parser
|
||||||
|
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
|
return proxy{br}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
// rule used to configure upstream check parameters
|
||||||
|
func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
|
ct, err := parser.GetIntAnnotation(connect, ing)
|
||||||
|
if err != nil {
|
||||||
|
ct = defBackend.ProxyConnectTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
st, err := parser.GetIntAnnotation(send, ing)
|
||||||
|
if err != nil {
|
||||||
|
st = defBackend.ProxySendTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
rt, err := parser.GetIntAnnotation(read, ing)
|
||||||
|
if err != nil {
|
||||||
|
rt = defBackend.ProxyReadTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
bufs, err := parser.GetStringAnnotation(bufferSize, ing)
|
||||||
|
if err != nil || bufs == "" {
|
||||||
|
bufs = defBackend.ProxyBufferSize
|
||||||
|
}
|
||||||
|
|
||||||
|
cp, err := parser.GetStringAnnotation(cookiePath, ing)
|
||||||
|
if err != nil || cp == "" {
|
||||||
|
cp = defBackend.ProxyCookiePath
|
||||||
|
}
|
||||||
|
|
||||||
|
cd, err := parser.GetStringAnnotation(cookieDomain, ing)
|
||||||
|
if err != nil || cd == "" {
|
||||||
|
cd = defBackend.ProxyCookieDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := parser.GetStringAnnotation(bodySize, ing)
|
||||||
|
if err != nil || bs == "" {
|
||||||
|
bs = defBackend.ProxyBodySize
|
||||||
|
}
|
||||||
|
|
||||||
|
nu, err := parser.GetStringAnnotation(nextUpstream, ing)
|
||||||
|
if err != nil || nu == "" {
|
||||||
|
nu = defBackend.ProxyNextUpstream
|
||||||
|
}
|
||||||
|
|
||||||
|
pp, err := parser.GetStringAnnotation(passParams, ing)
|
||||||
|
if err != nil || pp == "" {
|
||||||
|
pp = defBackend.ProxyPassParams
|
||||||
|
}
|
||||||
|
|
||||||
|
rb, err := parser.GetStringAnnotation(requestBuffering, ing)
|
||||||
|
if err != nil || rb == "" {
|
||||||
|
rb = defBackend.ProxyRequestBuffering
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Configuration{bs, ct, st, rt, bufs, cd, cp, nu, pp, rb}, nil
|
||||||
|
}
|
168
pkg/ingress/annotations/proxy/main_test.go
Normal file
168
pkg/ingress/annotations/proxy/main_test.go
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
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 proxy
|
||||||
|
|
||||||
|
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/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{
|
||||||
|
UpstreamFailTimeout: 1,
|
||||||
|
ProxyConnectTimeout: 10,
|
||||||
|
ProxySendTimeout: 15,
|
||||||
|
ProxyReadTimeout: 20,
|
||||||
|
ProxyBufferSize: "10k",
|
||||||
|
ProxyBodySize: "3k",
|
||||||
|
ProxyNextUpstream: "error",
|
||||||
|
ProxyPassParams: "nocanon keepalive=On",
|
||||||
|
ProxyRequestBuffering: "on",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxy(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[connect] = "1"
|
||||||
|
data[send] = "2"
|
||||||
|
data[read] = "3"
|
||||||
|
data[bufferSize] = "1k"
|
||||||
|
data[bodySize] = "2k"
|
||||||
|
data[nextUpstream] = "off"
|
||||||
|
data[passParams] = "smax=5 max=10"
|
||||||
|
data[requestBuffering] = "off"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error parsing a valid")
|
||||||
|
}
|
||||||
|
p, ok := i.(*Configuration)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected a Configuration type")
|
||||||
|
}
|
||||||
|
if p.ConnectTimeout != 1 {
|
||||||
|
t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
|
||||||
|
}
|
||||||
|
if p.SendTimeout != 2 {
|
||||||
|
t.Errorf("expected 2 as send-timeout but returned %v", p.SendTimeout)
|
||||||
|
}
|
||||||
|
if p.ReadTimeout != 3 {
|
||||||
|
t.Errorf("expected 3 as read-timeout but returned %v", p.ReadTimeout)
|
||||||
|
}
|
||||||
|
if p.BufferSize != "1k" {
|
||||||
|
t.Errorf("expected 1k as buffer-size but returned %v", p.BufferSize)
|
||||||
|
}
|
||||||
|
if p.BodySize != "2k" {
|
||||||
|
t.Errorf("expected 2k as body-size but returned %v", p.BodySize)
|
||||||
|
}
|
||||||
|
if p.NextUpstream != "off" {
|
||||||
|
t.Errorf("expected off as next-upstream but returned %v", p.NextUpstream)
|
||||||
|
}
|
||||||
|
if p.PassParams != "smax=5 max=10" {
|
||||||
|
t.Errorf("expected \"smax=5 max=10\" as pass-params but returned \"%v\"", p.PassParams)
|
||||||
|
}
|
||||||
|
if p.RequestBuffering != "off" {
|
||||||
|
t.Errorf("expected off as request-buffering but returned %v", p.RequestBuffering)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyWithNoAnnotation(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error parsing a valid")
|
||||||
|
}
|
||||||
|
p, ok := i.(*Configuration)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected a Configuration type")
|
||||||
|
}
|
||||||
|
if p.ConnectTimeout != 10 {
|
||||||
|
t.Errorf("expected 10 as connect-timeout but returned %v", p.ConnectTimeout)
|
||||||
|
}
|
||||||
|
if p.SendTimeout != 15 {
|
||||||
|
t.Errorf("expected 15 as send-timeout but returned %v", p.SendTimeout)
|
||||||
|
}
|
||||||
|
if p.ReadTimeout != 20 {
|
||||||
|
t.Errorf("expected 20 as read-timeout but returned %v", p.ReadTimeout)
|
||||||
|
}
|
||||||
|
if p.BufferSize != "10k" {
|
||||||
|
t.Errorf("expected 10k as buffer-size but returned %v", p.BufferSize)
|
||||||
|
}
|
||||||
|
if p.BodySize != "3k" {
|
||||||
|
t.Errorf("expected 3k as body-size but returned %v", p.BodySize)
|
||||||
|
}
|
||||||
|
if p.NextUpstream != "error" {
|
||||||
|
t.Errorf("expected error as next-upstream but returned %v", p.NextUpstream)
|
||||||
|
}
|
||||||
|
if p.PassParams != "nocanon keepalive=On" {
|
||||||
|
t.Errorf("expected \"nocanon keepalive=On\" as pass-params but returned \"%v\"", p.PassParams)
|
||||||
|
}
|
||||||
|
if p.RequestBuffering != "on" {
|
||||||
|
t.Errorf("expected on as request-buffering but returned %v", p.RequestBuffering)
|
||||||
|
}
|
||||||
|
}
|
255
pkg/ingress/annotations/ratelimit/main.go
Normal file
255
pkg/ingress/annotations/ratelimit/main.go
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
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 ratelimit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
"k8s.io/ingress-nginx/pkg/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
limitIP = "ingress.kubernetes.io/limit-connections"
|
||||||
|
limitRPS = "ingress.kubernetes.io/limit-rps"
|
||||||
|
limitRPM = "ingress.kubernetes.io/limit-rpm"
|
||||||
|
limitRATE = "ingress.kubernetes.io/limit-rate"
|
||||||
|
limitRATEAFTER = "ingress.kubernetes.io/limit-rate-after"
|
||||||
|
limitWhitelist = "ingress.kubernetes.io/limit-whitelist"
|
||||||
|
|
||||||
|
// allow 5 times the specified limit as burst
|
||||||
|
defBurst = 5
|
||||||
|
|
||||||
|
// 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states
|
||||||
|
// default is 5MB
|
||||||
|
defSharedSize = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// RateLimit returns rate limit configuration for an Ingress rule limiting the
|
||||||
|
// number of connections per IP address and/or connections per second.
|
||||||
|
// If you both annotations are specified in a single Ingress rule, RPS limits
|
||||||
|
// takes precedence
|
||||||
|
type RateLimit struct {
|
||||||
|
// Connections indicates a limit with the number of connections per IP address
|
||||||
|
Connections Zone `json:"connections"`
|
||||||
|
// RPS indicates a limit with the number of connections per second
|
||||||
|
RPS Zone `json:"rps"`
|
||||||
|
|
||||||
|
RPM Zone `json:"rpm"`
|
||||||
|
|
||||||
|
LimitRate int `json:"limit-rate"`
|
||||||
|
|
||||||
|
LimitRateAfter int `json:"limit-rate-after"`
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
ID string `json:"id"`
|
||||||
|
|
||||||
|
Whitelist []string `json:"whitelist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two RateLimit types
|
||||||
|
func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
|
||||||
|
if rt1 == rt2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if rt1 == nil || rt2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !(&rt1.Connections).Equal(&rt2.Connections) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !(&rt1.RPM).Equal(&rt2.RPM) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !(&rt1.RPS).Equal(&rt2.RPS) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rt1.LimitRate != rt2.LimitRate {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rt1.LimitRateAfter != rt2.LimitRateAfter {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rt1.ID != rt2.ID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if rt1.Name != rt2.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(rt1.Whitelist) != len(rt2.Whitelist) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r1l := range rt1.Whitelist {
|
||||||
|
found := false
|
||||||
|
for _, rl2 := range rt2.Whitelist {
|
||||||
|
if r1l == rl2 {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zone returns information about the NGINX rate limit (limit_req_zone)
|
||||||
|
// http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
|
||||||
|
type Zone struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Burst int `json:"burst"`
|
||||||
|
// SharedSize amount of shared memory for the zone
|
||||||
|
SharedSize int `json:"sharedSize"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two Zone types
|
||||||
|
func (z1 *Zone) Equal(z2 *Zone) bool {
|
||||||
|
if z1 == z2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if z1 == nil || z2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if z1.Name != z2.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if z1.Limit != z2.Limit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if z1.Burst != z2.Burst {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if z1.SharedSize != z2.SharedSize {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type ratelimit struct {
|
||||||
|
backendResolver resolver.DefaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new ratelimit annotation parser
|
||||||
|
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
|
return ratelimit{br}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
// rule used to rewrite the defined paths
|
||||||
|
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
|
lr, err := parser.GetIntAnnotation(limitRATE, ing)
|
||||||
|
if err != nil {
|
||||||
|
lr = defBackend.LimitRate
|
||||||
|
}
|
||||||
|
lra, err := parser.GetIntAnnotation(limitRATEAFTER, ing)
|
||||||
|
if err != nil {
|
||||||
|
lra = defBackend.LimitRateAfter
|
||||||
|
}
|
||||||
|
|
||||||
|
rpm, _ := parser.GetIntAnnotation(limitRPM, ing)
|
||||||
|
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
|
||||||
|
conn, _ := parser.GetIntAnnotation(limitIP, ing)
|
||||||
|
|
||||||
|
val, _ := parser.GetStringAnnotation(limitWhitelist, ing)
|
||||||
|
|
||||||
|
cidrs, err := parseCIDRs(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rpm == 0 && rps == 0 && conn == 0 {
|
||||||
|
return &RateLimit{
|
||||||
|
Connections: Zone{},
|
||||||
|
RPS: Zone{},
|
||||||
|
RPM: Zone{},
|
||||||
|
LimitRate: lr,
|
||||||
|
LimitRateAfter: lra,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
|
||||||
|
|
||||||
|
return &RateLimit{
|
||||||
|
Connections: Zone{
|
||||||
|
Name: fmt.Sprintf("%v_conn", zoneName),
|
||||||
|
Limit: conn,
|
||||||
|
Burst: conn * defBurst,
|
||||||
|
SharedSize: defSharedSize,
|
||||||
|
},
|
||||||
|
RPS: Zone{
|
||||||
|
Name: fmt.Sprintf("%v_rps", zoneName),
|
||||||
|
Limit: rps,
|
||||||
|
Burst: rps * defBurst,
|
||||||
|
SharedSize: defSharedSize,
|
||||||
|
},
|
||||||
|
RPM: Zone{
|
||||||
|
Name: fmt.Sprintf("%v_rpm", zoneName),
|
||||||
|
Limit: rpm,
|
||||||
|
Burst: rpm * defBurst,
|
||||||
|
SharedSize: defSharedSize,
|
||||||
|
},
|
||||||
|
LimitRate: lr,
|
||||||
|
LimitRateAfter: lra,
|
||||||
|
Name: zoneName,
|
||||||
|
ID: encode(zoneName),
|
||||||
|
Whitelist: cidrs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCIDRs(s string) ([]string, error) {
|
||||||
|
if s == "" {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
values := strings.Split(s, ",")
|
||||||
|
|
||||||
|
ipnets, ips, err := net.ParseIPNets(values...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cidrs := []string{}
|
||||||
|
for k := range ipnets {
|
||||||
|
cidrs = append(cidrs, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range ips {
|
||||||
|
cidrs = append(cidrs, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(cidrs)
|
||||||
|
|
||||||
|
return cidrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(s string) string {
|
||||||
|
str := base64.URLEncoding.EncodeToString([]byte(s))
|
||||||
|
return strings.Replace(str, "=", "", -1)
|
||||||
|
}
|
129
pkg/ingress/annotations/ratelimit/main_test.go
Normal file
129
pkg/ingress/annotations/ratelimit/main_test.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 ratelimit
|
||||||
|
|
||||||
|
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/apimachinery/pkg/util/intstr"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{
|
||||||
|
LimitRateAfter: 0,
|
||||||
|
LimitRate: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithoutAnnotations(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("unexpected error with ingress without annotations")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBadRateLimiting(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[limitIP] = "0"
|
||||||
|
data[limitRPS] = "0"
|
||||||
|
data[limitRPM] = "0"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error with invalid limits (0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
data = map[string]string{}
|
||||||
|
data[limitIP] = "5"
|
||||||
|
data[limitRPS] = "100"
|
||||||
|
data[limitRPM] = "10"
|
||||||
|
data[limitRATEAFTER] = "100"
|
||||||
|
data[limitRATE] = "10"
|
||||||
|
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
rateLimit, ok := i.(*RateLimit)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a RateLimit type")
|
||||||
|
}
|
||||||
|
if rateLimit.Connections.Limit != 5 {
|
||||||
|
t.Errorf("expected 5 in limit by ip but %v was returend", rateLimit.Connections)
|
||||||
|
}
|
||||||
|
if rateLimit.RPS.Limit != 100 {
|
||||||
|
t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS)
|
||||||
|
}
|
||||||
|
if rateLimit.RPM.Limit != 10 {
|
||||||
|
t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM)
|
||||||
|
}
|
||||||
|
if rateLimit.LimitRateAfter != 100 {
|
||||||
|
t.Errorf("expected 100 in limit by limitrateafter but %v was returend", rateLimit.LimitRateAfter)
|
||||||
|
}
|
||||||
|
if rateLimit.LimitRate != 10 {
|
||||||
|
t.Errorf("expected 10 in limit by limitrate but %v was returend", rateLimit.LimitRate)
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,8 +23,8 @@ import (
|
||||||
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress-nginx/ingress/errors"
|
"k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
114
pkg/ingress/annotations/rewrite/main.go
Normal file
114
pkg/ingress/annotations/rewrite/main.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
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 rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rewriteTo = "ingress.kubernetes.io/rewrite-target"
|
||||||
|
addBaseURL = "ingress.kubernetes.io/add-base-url"
|
||||||
|
baseURLScheme = "ingress.kubernetes.io/base-url-scheme"
|
||||||
|
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
|
||||||
|
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
|
||||||
|
appRoot = "ingress.kubernetes.io/app-root"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redirect describes the per location redirect config
|
||||||
|
type Redirect struct {
|
||||||
|
// Target URI where the traffic must be redirected
|
||||||
|
Target string `json:"target"`
|
||||||
|
// AddBaseURL indicates if is required to add a base tag in the head
|
||||||
|
// of the responses from the upstream servers
|
||||||
|
AddBaseURL bool `json:"addBaseUrl"`
|
||||||
|
// BaseURLScheme override for the scheme passed to the base tag
|
||||||
|
BaseURLScheme string `json:"baseUrlScheme"`
|
||||||
|
// SSLRedirect indicates if the location section is accessible SSL only
|
||||||
|
SSLRedirect bool `json:"sslRedirect"`
|
||||||
|
// ForceSSLRedirect indicates if the location section is accessible SSL only
|
||||||
|
ForceSSLRedirect bool `json:"forceSSLRedirect"`
|
||||||
|
// AppRoot defines the Application Root that the Controller must redirect if it's not in '/' context
|
||||||
|
AppRoot string `json:"appRoot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal tests for equality between two Redirect types
|
||||||
|
func (r1 *Redirect) Equal(r2 *Redirect) bool {
|
||||||
|
if r1 == r2 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if r1 == nil || r2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.Target != r2.Target {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.AddBaseURL != r2.AddBaseURL {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.BaseURLScheme != r2.BaseURLScheme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.SSLRedirect != r2.SSLRedirect {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.ForceSSLRedirect != r2.ForceSSLRedirect {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r1.AppRoot != r2.AppRoot {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type rewrite struct {
|
||||||
|
backendResolver resolver.DefaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new reqrite annotation parser
|
||||||
|
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
||||||
|
return rewrite{br}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
|
// rule used to rewrite the defined paths
|
||||||
|
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
||||||
|
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
||||||
|
if err != nil {
|
||||||
|
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
|
||||||
|
}
|
||||||
|
fSslRe, err := parser.GetBoolAnnotation(forceSSLRedirect, ing)
|
||||||
|
if err != nil {
|
||||||
|
fSslRe = a.backendResolver.GetDefaultBackend().ForceSSLRedirect
|
||||||
|
}
|
||||||
|
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
||||||
|
bus, _ := parser.GetStringAnnotation(baseURLScheme, ing)
|
||||||
|
ar, _ := parser.GetStringAnnotation(appRoot, ing)
|
||||||
|
return &Redirect{
|
||||||
|
Target: rt,
|
||||||
|
AddBaseURL: abu,
|
||||||
|
BaseURLScheme: bus,
|
||||||
|
SSLRedirect: sslRe,
|
||||||
|
ForceSSLRedirect: fSslRe,
|
||||||
|
AppRoot: ar,
|
||||||
|
}, nil
|
||||||
|
}
|
178
pkg/ingress/annotations/rewrite/main_test.go
Normal file
178
pkg/ingress/annotations/rewrite/main_test.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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 rewrite
|
||||||
|
|
||||||
|
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/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defRoute = "/demo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
redirect bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{SSLRedirect: m.redirect}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithoutAnnotations(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error with ingress without annotations: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedirect(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[rewriteTo] = defRoute
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error with ingress: %v", err)
|
||||||
|
}
|
||||||
|
redirect, ok := i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
|
if redirect.Target != defRoute {
|
||||||
|
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSSLRedirect(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[rewriteTo] = defRoute
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, _ := NewParser(mockBackend{true}).Parse(ing)
|
||||||
|
redirect, ok := i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
|
if !redirect.SSLRedirect {
|
||||||
|
t.Errorf("Expected true but returned false")
|
||||||
|
}
|
||||||
|
|
||||||
|
data[sslRedirect] = "false"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, _ = NewParser(mockBackend{false}).Parse(ing)
|
||||||
|
redirect, ok = i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
|
if redirect.SSLRedirect {
|
||||||
|
t.Errorf("Expected false but returned true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForceSSLRedirect(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[rewriteTo] = defRoute
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, _ := NewParser(mockBackend{true}).Parse(ing)
|
||||||
|
redirect, ok := i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
|
if redirect.ForceSSLRedirect {
|
||||||
|
t.Errorf("Expected false but returned true")
|
||||||
|
}
|
||||||
|
|
||||||
|
data[forceSSLRedirect] = "true"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, _ = NewParser(mockBackend{false}).Parse(ing)
|
||||||
|
redirect, ok = i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
|
if !redirect.ForceSSLRedirect {
|
||||||
|
t.Errorf("Expected true but returned false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestAppRoot(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[appRoot] = "/app1"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
i, _ := NewParser(mockBackend{true}).Parse(ing)
|
||||||
|
redirect, ok := i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a App Context")
|
||||||
|
}
|
||||||
|
if redirect.AppRoot != "/app1" {
|
||||||
|
t.Errorf("Unexpected value got in AppRoot")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
78
pkg/ingress/annotations/secureupstream/main.go
Normal file
78
pkg/ingress/annotations/secureupstream/main.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
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 secureupstream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
secureUpstream = "ingress.kubernetes.io/secure-backends"
|
||||||
|
secureVerifyCASecret = "ingress.kubernetes.io/secure-verify-ca-secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Secure describes SSL backend configuration
|
||||||
|
type Secure struct {
|
||||||
|
Secure bool `json:"secure"`
|
||||||
|
CACert resolver.AuthSSLCert `json:"caCert"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type su struct {
|
||||||
|
certResolver resolver.AuthCertificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new secure upstream annotation parser
|
||||||
|
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
|
||||||
|
return su{
|
||||||
|
certResolver: resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
s, _ := parser.GetBoolAnnotation(secureUpstream, ing)
|
||||||
|
ca, _ := parser.GetStringAnnotation(secureVerifyCASecret, ing)
|
||||||
|
secure := &Secure{
|
||||||
|
Secure: s,
|
||||||
|
CACert: resolver.AuthSSLCert{},
|
||||||
|
}
|
||||||
|
if !s && 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.certResolver.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 &Secure{
|
||||||
|
Secure: s,
|
||||||
|
CACert: *caCert,
|
||||||
|
}, nil
|
||||||
|
}
|
121
pkg/ingress/annotations/secureupstream/main_test.go
Normal file
121
pkg/ingress/annotations/secureupstream/main_test.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
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 secureupstream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
api "k8s.io/api/core/v1"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: meta_v1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: api.NamespaceDefault,
|
||||||
|
},
|
||||||
|
Spec: extensions.IngressSpec{
|
||||||
|
Backend: &extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
},
|
||||||
|
Rules: []extensions.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo.bar.com",
|
||||||
|
IngressRuleValue: extensions.IngressRuleValue{
|
||||||
|
HTTP: &extensions.HTTPIngressRuleValue{
|
||||||
|
Paths: []extensions.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/foo",
|
||||||
|
Backend: defaultBackend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockCfg struct {
|
||||||
|
certs map[string]resolver.AuthSSLCert
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg mockCfg) GetAuthCertificate(secret string) (*resolver.AuthSSLCert, error) {
|
||||||
|
if cert, ok := cfg.certs[secret]; ok {
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("secret not found: %v", secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnotations(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
data := map[string]string{}
|
||||||
|
data[secureUpstream] = "true"
|
||||||
|
data[secureVerifyCASecret] = "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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecretNotFound(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
data := map[string]string{}
|
||||||
|
data[secureUpstream] = "true"
|
||||||
|
data[secureVerifyCASecret] = "secure-verify-ca"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
_, err := NewParser(mockCfg{}).Parse(ing)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected secret not found error on ingress")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSecretOnNonSecure(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
data := map[string]string{}
|
||||||
|
data[secureUpstream] = "false"
|
||||||
|
data[secureVerifyCASecret] = "secure-verify-ca"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
_, err := NewParser(mockCfg{
|
||||||
|
certs: map[string]resolver.AuthSSLCert{
|
||||||
|
"default/secure-verify-ca": {},
|
||||||
|
},
|
||||||
|
}).Parse(ing)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected CA secret on non secure backend error on ingress")
|
||||||
|
}
|
||||||
|
}
|
42
pkg/ingress/annotations/serversnippet/main.go
Normal file
42
pkg/ingress/annotations/serversnippet/main.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
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 serversnippet
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/server-snippet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverSnippet struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new server snippet annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return serverSnippet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return parser.GetStringAnnotation(annotation, ing)
|
||||||
|
}
|
38
pkg/ingress/annotations/serviceupstream/main.go
Normal file
38
pkg/ingress/annotations/serviceupstream/main.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
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 serviceupstream
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotationServiceUpstream = "ingress.kubernetes.io/service-upstream"
|
||||||
|
)
|
||||||
|
|
||||||
|
type serviceUpstream struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new serviceUpstream annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return serviceUpstream{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s serviceUpstream) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
return parser.GetBoolAnnotation(annotationServiceUpstream, ing)
|
||||||
|
}
|
115
pkg/ingress/annotations/sessionaffinity/main.go
Normal file
115
pkg/ingress/annotations/sessionaffinity/main.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
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 sessionaffinity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotationAffinityType = "ingress.kubernetes.io/affinity"
|
||||||
|
// If a cookie with this name exists,
|
||||||
|
// its value is used as an index into the list of available backends.
|
||||||
|
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||||
|
defaultAffinityCookieName = "INGRESSCOOKIE"
|
||||||
|
// This is the algorithm used by nginx to generate a value for the session cookie, if
|
||||||
|
// one isn't supplied and affinity is set to "cookie".
|
||||||
|
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
|
||||||
|
defaultAffinityCookieHash = "md5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// AffinityConfig describes the per ingress session affinity config
|
||||||
|
type AffinityConfig struct {
|
||||||
|
// The type of affinity that will be used
|
||||||
|
AffinityType string `json:"type"`
|
||||||
|
CookieConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieConfig describes the Config of cookie type affinity
|
||||||
|
type CookieConfig struct {
|
||||||
|
// The name of the cookie that will be used in case of cookie affinity type.
|
||||||
|
Name string `json:"name"`
|
||||||
|
// The hash that will be used to encode the cookie in case of cookie affinity type
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieAffinityParse gets the annotation values related to Cookie Affinity
|
||||||
|
// It also sets default values when no value or incorrect value is found
|
||||||
|
func CookieAffinityParse(ing *extensions.Ingress) *CookieConfig {
|
||||||
|
|
||||||
|
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
|
||||||
|
|
||||||
|
if err != nil || sn == "" {
|
||||||
|
glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, annotationAffinityCookieName, defaultAffinityCookieName)
|
||||||
|
sn = defaultAffinityCookieName
|
||||||
|
}
|
||||||
|
|
||||||
|
sh, err := parser.GetStringAnnotation(annotationAffinityCookieHash, ing)
|
||||||
|
|
||||||
|
if err != nil || !affinityCookieHashRegex.MatchString(sh) {
|
||||||
|
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Setting it to default %v", ing.Name, annotationAffinityCookieHash, defaultAffinityCookieHash)
|
||||||
|
sh = defaultAffinityCookieHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CookieConfig{
|
||||||
|
Name: sn,
|
||||||
|
Hash: sh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new Affinity annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return affinity{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
cookieAffinityConfig := &CookieConfig{}
|
||||||
|
// Check the type of affinity that will be used
|
||||||
|
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
|
||||||
|
if err != nil {
|
||||||
|
at = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch at {
|
||||||
|
case "cookie":
|
||||||
|
cookieAffinityConfig = CookieAffinityParse(ing)
|
||||||
|
|
||||||
|
default:
|
||||||
|
glog.V(3).Infof("No default affinity was found for Ingress %v", ing.Name)
|
||||||
|
|
||||||
|
}
|
||||||
|
return &AffinityConfig{
|
||||||
|
AffinityType: at,
|
||||||
|
CookieConfig: *cookieAffinityConfig,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
42
pkg/ingress/annotations/snippet/main.go
Normal file
42
pkg/ingress/annotations/snippet/main.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
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 snippet
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/configuration-snippet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type snippet struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new CORS annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return snippet{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return parser.GetStringAnnotation(annotation, ing)
|
||||||
|
}
|
46
pkg/ingress/annotations/sslpassthrough/main.go
Normal file
46
pkg/ingress/annotations/sslpassthrough/main.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
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 sslpassthrough
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
passthrough = "ingress.kubernetes.io/ssl-passthrough"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sslpt struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new SSL passthrough annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return sslpt{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if ing.GetAnnotations() == nil {
|
||||||
|
return false, ing_errors.ErrMissingAnnotations
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser.GetBoolAnnotation(passthrough, ing)
|
||||||
|
}
|
42
pkg/ingress/annotations/upstreamvhost/main.go
Normal file
42
pkg/ingress/annotations/upstreamvhost/main.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
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 upstreamvhost
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/upstream-vhost"
|
||||||
|
)
|
||||||
|
|
||||||
|
type upstreamVhost struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new upstream VHost annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return upstreamVhost{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
return parser.GetStringAnnotation(annotation, ing)
|
||||||
|
}
|
42
pkg/ingress/annotations/vtsfilterkey/main.go
Normal file
42
pkg/ingress/annotations/vtsfilterkey/main.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
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 vtsfilterkey
|
||||||
|
|
||||||
|
import (
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
annotation = "ingress.kubernetes.io/vts-filter-key"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vtsFilterKey struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new vts filter key annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return vtsFilterKey{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 vtsFilterKey) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
return parser.GetStringAnnotation(annotation, ing)
|
||||||
|
}
|
191
pkg/ingress/controller/annotations.go
Normal file
191
pkg/ingress/controller/annotations.go
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
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 controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/alias"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authtls"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/clientbodybuffersize"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/cors"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/defaultbackend"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/portinredirect"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/redirect"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/secureupstream"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/serversnippet"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/serviceupstream"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/sessionaffinity"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/snippet"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/sslpassthrough"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/upstreamvhost"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/vtsfilterkey"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type extractorConfig interface {
|
||||||
|
resolver.AuthCertificate
|
||||||
|
resolver.DefaultBackend
|
||||||
|
resolver.Secret
|
||||||
|
resolver.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
type annotationExtractor struct {
|
||||||
|
secretResolver resolver.Secret
|
||||||
|
annotations map[string]parser.IngressAnnotation
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
||||||
|
return annotationExtractor{
|
||||||
|
cfg,
|
||||||
|
map[string]parser.IngressAnnotation{
|
||||||
|
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
||||||
|
"ExternalAuth": authreq.NewParser(),
|
||||||
|
"CertificateAuth": authtls.NewParser(cfg),
|
||||||
|
"EnableCORS": cors.NewParser(),
|
||||||
|
"HealthCheck": healthcheck.NewParser(cfg),
|
||||||
|
"Whitelist": ipwhitelist.NewParser(cfg),
|
||||||
|
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||||
|
"Proxy": proxy.NewParser(cfg),
|
||||||
|
"RateLimit": ratelimit.NewParser(cfg),
|
||||||
|
"Redirect": redirect.NewParser(),
|
||||||
|
"Rewrite": rewrite.NewParser(cfg),
|
||||||
|
"SecureUpstream": secureupstream.NewParser(cfg),
|
||||||
|
"ServiceUpstream": serviceupstream.NewParser(),
|
||||||
|
"SessionAffinity": sessionaffinity.NewParser(),
|
||||||
|
"SSLPassthrough": sslpassthrough.NewParser(),
|
||||||
|
"ConfigurationSnippet": snippet.NewParser(),
|
||||||
|
"Alias": alias.NewParser(),
|
||||||
|
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
|
||||||
|
"DefaultBackend": defaultbackend.NewParser(cfg),
|
||||||
|
"UpstreamVhost": upstreamvhost.NewParser(),
|
||||||
|
"VtsFilterKey": vtsfilterkey.NewParser(),
|
||||||
|
"ServerSnippet": serversnippet.NewParser(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interface{} {
|
||||||
|
anns := make(map[string]interface{})
|
||||||
|
for name, annotationParser := range e.annotations {
|
||||||
|
val, err := annotationParser.Parse(ing)
|
||||||
|
glog.V(5).Infof("annotation %v in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), val)
|
||||||
|
if err != nil {
|
||||||
|
if errors.IsMissingAnnotations(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.IsLocationDenied(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, alreadyDenied := anns[DeniedKeyName]
|
||||||
|
if !alreadyDenied {
|
||||||
|
anns[DeniedKeyName] = err
|
||||||
|
glog.Errorf("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(5).Infof("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != nil {
|
||||||
|
anns[name] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return anns
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
secureUpstream = "SecureUpstream"
|
||||||
|
healthCheck = "HealthCheck"
|
||||||
|
sslPassthrough = "SSLPassthrough"
|
||||||
|
sessionAffinity = "SessionAffinity"
|
||||||
|
serviceUpstream = "ServiceUpstream"
|
||||||
|
serverAlias = "Alias"
|
||||||
|
clientBodyBufferSize = "ClientBodyBufferSize"
|
||||||
|
certificateAuth = "CertificateAuth"
|
||||||
|
serverSnippet = "ServerSnippet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool {
|
||||||
|
val, _ := e.annotations[serviceUpstream].Parse(ing)
|
||||||
|
return val.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) *secureupstream.Secure {
|
||||||
|
val, err := e.annotations[secureUpstream].Parse(ing)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("error parsing secure upstream: %v", err)
|
||||||
|
}
|
||||||
|
secure := val.(*secureupstream.Secure)
|
||||||
|
return secure
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) HealthCheck(ing *extensions.Ingress) *healthcheck.Upstream {
|
||||||
|
val, _ := e.annotations[healthCheck].Parse(ing)
|
||||||
|
return val.(*healthcheck.Upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool {
|
||||||
|
val, _ := e.annotations[sslPassthrough].Parse(ing)
|
||||||
|
return val.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) Alias(ing *extensions.Ingress) string {
|
||||||
|
val, _ := e.annotations[serverAlias].Parse(ing)
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) ClientBodyBufferSize(ing *extensions.Ingress) string {
|
||||||
|
val, _ := e.annotations[clientBodyBufferSize].Parse(ing)
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig {
|
||||||
|
val, _ := e.annotations[sessionAffinity].Parse(ing)
|
||||||
|
return val.(*sessionaffinity.AffinityConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) CertificateAuth(ing *extensions.Ingress) *authtls.AuthSSLConfig {
|
||||||
|
val, err := e.annotations[certificateAuth].Parse(ing)
|
||||||
|
if errors.IsMissingAnnotations(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("error parsing certificate auth: %v", err)
|
||||||
|
}
|
||||||
|
secure := val.(*authtls.AuthSSLConfig)
|
||||||
|
return secure
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) ServerSnippet(ing *extensions.Ingress) string {
|
||||||
|
val, _ := e.annotations[serverSnippet].Parse(ing)
|
||||||
|
return val.(string)
|
||||||
|
}
|
|
@ -24,8 +24,8 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress/defaults"
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
"k8s.io/ingress-nginx/ingress/resolver"
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
|
@ -27,10 +27,10 @@ import (
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/class"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
|
||||||
"k8s.io/ingress-nginx/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress-nginx/net/ssl"
|
"k8s.io/ingress-nginx/pkg/net/ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// syncSecret keeps in sync Secrets used by Ingress rules with the files on
|
// syncSecret keeps in sync Secrets used by Ingress rules with the files on
|
|
@ -28,9 +28,9 @@ import (
|
||||||
cache_client "k8s.io/client-go/tools/cache"
|
cache_client "k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/util/flowcontrol"
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/ingress/store"
|
"k8s.io/ingress-nginx/pkg/ingress/store"
|
||||||
"k8s.io/ingress-nginx/task"
|
"k8s.io/ingress-nginx/pkg/task"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
)
|
)
|
||||||
|
|
1262
pkg/ingress/controller/controller.go
Normal file
1262
pkg/ingress/controller/controller.go
Normal file
File diff suppressed because it is too large
Load diff
319
pkg/ingress/controller/launch.go
Normal file
319
pkg/ingress/controller/launch.go
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
|
"k8s.io/ingress-nginx/pkg/k8s"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewIngressController returns a configured Ingress controller
|
||||||
|
func NewIngressController(backend ingress.Controller) *GenericController {
|
||||||
|
var (
|
||||||
|
flags = pflag.NewFlagSet("", pflag.ExitOnError)
|
||||||
|
|
||||||
|
apiserverHost = flags.String("apiserver-host", "", "The address of the Kubernetes Apiserver "+
|
||||||
|
"to connect to in the format of protocol://address:port, e.g., "+
|
||||||
|
"http://localhost:8080. If not specified, the assumption is that the binary runs inside a "+
|
||||||
|
"Kubernetes cluster and local discovery is attempted.")
|
||||||
|
kubeConfigFile = flags.String("kubeconfig", "", "Path to kubeconfig file with authorization and master location information.")
|
||||||
|
|
||||||
|
defaultSvc = flags.String("default-backend-service", "",
|
||||||
|
`Service used to serve a 404 page for the default backend. Takes the form
|
||||||
|
namespace/name. The controller uses the first node port of this Service for
|
||||||
|
the default backend.`)
|
||||||
|
|
||||||
|
ingressClass = flags.String("ingress-class", "",
|
||||||
|
`Name of the ingress class to route through this controller.`)
|
||||||
|
|
||||||
|
configMap = flags.String("configmap", "",
|
||||||
|
`Name of the ConfigMap that contains the custom configuration to use`)
|
||||||
|
|
||||||
|
publishSvc = flags.String("publish-service", "",
|
||||||
|
`Service fronting the ingress controllers. Takes the form
|
||||||
|
namespace/name. The controller will set the endpoint records on the
|
||||||
|
ingress objects to reflect those on the service.`)
|
||||||
|
|
||||||
|
tcpConfigMapName = flags.String("tcp-services-configmap", "",
|
||||||
|
`Name of the ConfigMap that contains the definition of the TCP services to expose.
|
||||||
|
The key in the map indicates the external port to be used. The value is the name of the
|
||||||
|
service with the format namespace/serviceName and the port of the service could be a
|
||||||
|
number of the name of the port.
|
||||||
|
The ports 80 and 443 are not allowed as external ports. This ports are reserved for the backend`)
|
||||||
|
|
||||||
|
udpConfigMapName = flags.String("udp-services-configmap", "",
|
||||||
|
`Name of the ConfigMap that contains the definition of the UDP services to expose.
|
||||||
|
The key in the map indicates the external port to be used. The value is the name of the
|
||||||
|
service with the format namespace/serviceName and the port of the service could be a
|
||||||
|
number of the name of the port.`)
|
||||||
|
|
||||||
|
resyncPeriod = flags.Duration("sync-period", 600*time.Second,
|
||||||
|
`Relist and confirm cloud resources this often. Default is 10 minutes`)
|
||||||
|
|
||||||
|
watchNamespace = flags.String("watch-namespace", apiv1.NamespaceAll,
|
||||||
|
`Namespace to watch for Ingress. Default is to watch all namespaces`)
|
||||||
|
|
||||||
|
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
|
||||||
|
|
||||||
|
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
|
||||||
|
|
||||||
|
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret
|
||||||
|
that contains a SSL certificate to be used as default for a HTTPS catch-all server`)
|
||||||
|
|
||||||
|
defHealthzURL = flags.String("health-check-path", "/healthz", `Defines
|
||||||
|
the URL to be used as health check inside in the default server in NGINX.`)
|
||||||
|
|
||||||
|
updateStatus = flags.Bool("update-status", true, `Indicates if the
|
||||||
|
ingress controller should update the Ingress status IP/hostname. Default is true`)
|
||||||
|
|
||||||
|
electionID = flags.String("election-id", "ingress-controller-leader", `Election id to use for status update.`)
|
||||||
|
|
||||||
|
forceIsolation = flags.Bool("force-namespace-isolation", false,
|
||||||
|
`Force namespace isolation. This flag is required to avoid the reference of secrets or
|
||||||
|
configmaps located in a different namespace than the specified in the flag --watch-namespace.`)
|
||||||
|
|
||||||
|
disableNodeList = flags.Bool("disable-node-list", false,
|
||||||
|
`Disable querying nodes. If --force-namespace-isolation is true, this should also be set.`)
|
||||||
|
|
||||||
|
updateStatusOnShutdown = flags.Bool("update-status-on-shutdown", true, `Indicates if the
|
||||||
|
ingress controller should update the Ingress status IP/hostname when the controller
|
||||||
|
is being stopped. Default is true`)
|
||||||
|
|
||||||
|
sortBackends = flags.Bool("sort-backends", false,
|
||||||
|
`Defines if backends and it's endpoints should be sorted`)
|
||||||
|
)
|
||||||
|
|
||||||
|
flags.AddGoFlagSet(flag.CommandLine)
|
||||||
|
backend.ConfigureFlags(flags)
|
||||||
|
flags.Parse(os.Args)
|
||||||
|
backend.OverrideFlags(flags)
|
||||||
|
|
||||||
|
flag.Set("logtostderr", "true")
|
||||||
|
|
||||||
|
glog.Info(backend.Info())
|
||||||
|
|
||||||
|
if *ingressClass != "" {
|
||||||
|
glog.Infof("Watching for ingress class: %s", *ingressClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *defaultSvc == "" {
|
||||||
|
glog.Fatalf("Please specify --default-backend-service")
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient, err := createApiserverClient(*apiserverHost, *kubeConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
handleFatalInitError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ns, name, err := k8s.ParseNameNS(*defaultSvc)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("invalid format for service %v: %v", *defaultSvc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = kubeClient.Core().Services(ns).Get(name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "cannot get services in the namespace") {
|
||||||
|
glog.Fatalf("✖ It seems the cluster it is running with Authorization enabled (like RBAC) and there is no permissions for the ingress controller. Please check the configuration")
|
||||||
|
}
|
||||||
|
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
|
||||||
|
}
|
||||||
|
glog.Infof("validated %v as the default backend", *defaultSvc)
|
||||||
|
|
||||||
|
if *publishSvc != "" {
|
||||||
|
ns, name, err := k8s.ParseNameNS(*publishSvc)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("invalid service format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, err := kubeClient.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("unexpected error getting information about service %v: %v", *publishSvc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(svc.Status.LoadBalancer.Ingress) == 0 {
|
||||||
|
if len(svc.Spec.ExternalIPs) > 0 {
|
||||||
|
glog.Infof("service %v validated as assigned with externalIP", *publishSvc)
|
||||||
|
} else {
|
||||||
|
// We could poll here, but we instead just exit and rely on k8s to restart us
|
||||||
|
glog.Fatalf("service %s does not (yet) have ingress points", *publishSvc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
glog.Infof("service %v validated as source of Ingress status", *publishSvc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *watchNamespace != "" {
|
||||||
|
_, err = kubeClient.CoreV1().Namespaces().Get(*watchNamespace, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("no watchNamespace with name %v found: %v", *watchNamespace, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resyncPeriod.Seconds() < 10 {
|
||||||
|
glog.Fatalf("resync period (%vs) is too low", resyncPeriod.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to mkdir SSL directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &Configuration{
|
||||||
|
UpdateStatus: *updateStatus,
|
||||||
|
ElectionID: *electionID,
|
||||||
|
Client: kubeClient,
|
||||||
|
ResyncPeriod: *resyncPeriod,
|
||||||
|
DefaultService: *defaultSvc,
|
||||||
|
IngressClass: *ingressClass,
|
||||||
|
DefaultIngressClass: backend.DefaultIngressClass(),
|
||||||
|
Namespace: *watchNamespace,
|
||||||
|
ConfigMapName: *configMap,
|
||||||
|
TCPConfigMapName: *tcpConfigMapName,
|
||||||
|
UDPConfigMapName: *udpConfigMapName,
|
||||||
|
DefaultSSLCertificate: *defSSLCertificate,
|
||||||
|
DefaultHealthzURL: *defHealthzURL,
|
||||||
|
PublishService: *publishSvc,
|
||||||
|
Backend: backend,
|
||||||
|
ForceNamespaceIsolation: *forceIsolation,
|
||||||
|
DisableNodeList: *disableNodeList,
|
||||||
|
UpdateStatusOnShutdown: *updateStatusOnShutdown,
|
||||||
|
SortBackends: *sortBackends,
|
||||||
|
}
|
||||||
|
|
||||||
|
ic := newIngressController(config)
|
||||||
|
go registerHandlers(*profiling, *healthzPort, ic)
|
||||||
|
return ic
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerHandlers(enableProfiling bool, port int, ic *GenericController) {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
// expose health check endpoint (/healthz)
|
||||||
|
healthz.InstallHandler(mux,
|
||||||
|
healthz.PingHealthz,
|
||||||
|
ic.cfg.Backend,
|
||||||
|
)
|
||||||
|
|
||||||
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
|
|
||||||
|
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
b, _ := json.Marshal(ic.Info())
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if enableProfiling {
|
||||||
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%v", port),
|
||||||
|
Handler: mux,
|
||||||
|
}
|
||||||
|
glog.Fatal(server.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// High enough QPS to fit all expected use cases. QPS=0 is not set here, because
|
||||||
|
// client code is overriding it.
|
||||||
|
defaultQPS = 1e6
|
||||||
|
// High enough Burst to fit all expected use cases. Burst=0 is not set here, because
|
||||||
|
// client code is overriding it.
|
||||||
|
defaultBurst = 1e6
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildConfigFromFlags builds REST config based on master URL and kubeconfig path.
|
||||||
|
// If both of them are empty then in cluster config is used.
|
||||||
|
func buildConfigFromFlags(masterURL, kubeconfigPath string) (*rest.Config, error) {
|
||||||
|
if kubeconfigPath == "" && masterURL == "" {
|
||||||
|
kubeconfig, err := rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return kubeconfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||||
|
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
||||||
|
&clientcmd.ConfigOverrides{
|
||||||
|
ClusterInfo: clientcmdapi.Cluster{
|
||||||
|
Server: masterURL,
|
||||||
|
},
|
||||||
|
}).ClientConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// createApiserverClient creates new Kubernetes Apiserver client. When kubeconfig or apiserverHost param is empty
|
||||||
|
// the function assumes that it is running inside a Kubernetes cluster and attempts to
|
||||||
|
// discover the Apiserver. Otherwise, it connects to the Apiserver specified.
|
||||||
|
//
|
||||||
|
// apiserverHost param is in the format of protocol://address:port/pathPrefix, e.g.http://localhost:8001.
|
||||||
|
// kubeConfig location of kubeconfig file
|
||||||
|
func createApiserverClient(apiserverHost string, kubeConfig string) (*kubernetes.Clientset, error) {
|
||||||
|
cfg, err := buildConfigFromFlags(apiserverHost, kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.QPS = defaultQPS
|
||||||
|
cfg.Burst = defaultBurst
|
||||||
|
cfg.ContentType = "application/vnd.kubernetes.protobuf"
|
||||||
|
|
||||||
|
glog.Infof("Creating API client for %s", cfg.Host)
|
||||||
|
|
||||||
|
client, err := kubernetes.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Discovery().ServerVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("Running in Kubernetes Cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
|
||||||
|
v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles fatal init error that prevents server from doing any work. Prints verbose error
|
||||||
|
* message and quits the server.
|
||||||
|
*/
|
||||||
|
func handleFatalInitError(err error) {
|
||||||
|
glog.Fatalf("Error while initializing connection to Kubernetes apiserver. "+
|
||||||
|
"This most likely means that the cluster is misconfigured (e.g., it has "+
|
||||||
|
"invalid apiserver certificates or service accounts configuration). Reason: %s\n"+
|
||||||
|
"Refer to the troubleshooting guide for more information: "+
|
||||||
|
"https://github.com/kubernetes/ingress/blob/master/docs/troubleshooting.md", err)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue