Update dependencies

This commit is contained in:
Manuel de Brito Fontes 2017-10-06 17:33:32 -03:00
parent bf5616c65b
commit d6d374b28d
13962 changed files with 48226 additions and 3618880 deletions

72
Gopkg.lock generated
View file

@ -31,6 +31,23 @@
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
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]]
name = "github.com/emicklei/go-restful"
packages = [".","log"]
@ -81,7 +98,7 @@
[[projects]]
name = "github.com/gogo/protobuf"
packages = ["proto","sortkeys"]
packages = ["gogoproto","proto","protoc-gen-gogo/descriptor","sortkeys"]
revision = "100ba4e885062801d56799d78530b73b178a78f3"
version = "v0.4"
@ -157,6 +174,12 @@
packages = ["."]
revision = "5b9ff866471762aa2ab2dced63c9fb6f53921342"
[[projects]]
name = "github.com/kr/pty"
packages = ["."]
revision = "95d05c1eef33a45bd58676b6ce28d105839b8d0b"
version = "v1.0.1"
[[projects]]
branch = "master"
name = "github.com/kylelemons/godebug"
@ -188,10 +211,17 @@
revision = "d0303fe809921458f417bcf828397a65db30a7e4"
[[projects]]
branch = "master"
name = "github.com/ncabatoff/process-exporter"
packages = [".","proc"]
revision = "ae9193ff5f4d34dd62dc2fa194453ea73d7e64ee"
version = "0.1.0"
revision = "5917bc766b95a1fa3c2ae85340f4de02a6b7e15e"
source = "github.com/aledbf/process-exporter"
[[projects]]
name = "github.com/opencontainers/go-digest"
packages = ["."]
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
branch = "master"
@ -268,7 +298,7 @@
[[projects]]
branch = "master"
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"
[[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"]
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]]
name = "gopkg.in/fsnotify.v1"
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"]
revision = "81aa34336d28aadc3a8e8da7dfd9258c5157e5e4"
[[projects]]
branch = "master"
name = "k8s.io/apiextensions-apiserver"
packages = ["pkg/features"]
revision = "a5bbfd114a9b122acd741c61d88c84812375d9e1"
[[projects]]
branch = "master"
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"
[[projects]]
branch = "master"
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"
[[projects]]
branch = "master"
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"
[[projects]]
@ -339,13 +387,19 @@
[[projects]]
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"
version = "v1.8.0"
[[projects]]
branch = "master"
name = "k8s.io/utils"
packages = ["exec"]
revision = "4fe312863be2155a7b68acd2aff1c9221b24e68c"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "e17f9cc4f1307ae1c3c913743c5561b3c1836d1a04d5c49c8b98727ef33b9646"
inputs-digest = "4511b8e7ec7b35d8752f919dfe51b39a07852a98de8d765ce7f2512032450cde"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -20,6 +20,13 @@
# name = "github.com/x/y"
# version = "2.4.0"
[[override]]
name = "github.com/docker/distribution"
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
[[constraint]]
name = "github.com/opencontainers/go-digest"
branch = "master"
[[constraint]]
branch = "master"
@ -47,7 +54,8 @@
[[constraint]]
name = "github.com/ncabatoff/process-exporter"
version = "0.1.0"
source = "github.com/aledbf/process-exporter"
branch = "master"
[[constraint]]
branch = "master"

View file

@ -22,12 +22,14 @@ import (
"syscall"
"time"
"k8s.io/ingress-nginx/pkg/nginx/controller"
"github.com/golang/glog"
)
func main() {
// start a new nginx controller
ngx := newNGINXController()
ngx := controller.NewNGINXController()
go handleSigterm(ngx)
// start the controller
@ -40,7 +42,7 @@ func main() {
}
}
func handleSigterm(ngx *NGINXController) {
func handleSigterm(ngx *controller.NGINXController) {
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGTERM)
<-signalChan

View file

@ -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,
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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 processors 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 directives 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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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)
}

View file

@ -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
}

View file

@ -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()))
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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"`
}

View 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)
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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)
}

View 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
}

View 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
}

View 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)
}
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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
}

View 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
}

View 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)
}
}

View 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)
}

View 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)
}
}

View file

@ -23,8 +23,8 @@ import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/ingress/annotations/parser"
"k8s.io/ingress-nginx/ingress/errors"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/errors"
)
const (

View 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
}

View 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")
}
}

View 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
}

View 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")
}
}

View 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)
}

View 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)
}

View 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
}

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View file

@ -24,8 +24,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/ingress/defaults"
"k8s.io/ingress-nginx/ingress/resolver"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (

View file

@ -27,10 +27,10 @@ import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/ingress"
"k8s.io/ingress-nginx/ingress/annotations/class"
"k8s.io/ingress-nginx/ingress/annotations/parser"
"k8s.io/ingress-nginx/net/ssl"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/net/ssl"
)
// syncSecret keeps in sync Secrets used by Ingress rules with the files on

View file

@ -28,9 +28,9 @@ import (
cache_client "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/ingress-nginx/ingress"
"k8s.io/ingress-nginx/ingress/store"
"k8s.io/ingress-nginx/task"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/store"
"k8s.io/ingress-nginx/pkg/task"
"k8s.io/kubernetes/pkg/api"
)

File diff suppressed because it is too large Load diff

View 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