Merge branch 'master' into server-alias
This commit is contained in:
commit
47e4dd59a8
157 changed files with 26072 additions and 489 deletions
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package ipwhitelist
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
|
|
@ -26,7 +25,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
"k8s.io/ingress/core/pkg/ingress/errors"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
|
|
@ -73,157 +71,118 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
}
|
||||
|
||||
func TestParseAnnotations(t *testing.T) {
|
||||
// TODO: convert test cases to tables
|
||||
ing := buildIngress()
|
||||
|
||||
testNet := "10.0.0.0/24"
|
||||
enet := []string{testNet}
|
||||
|
||||
data := map[string]string{}
|
||||
data[whitelist] = testNet
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
expected := &SourceRange{
|
||||
CIDR: enet,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
p := NewParser(mockBackend{})
|
||||
|
||||
i, err := p.Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
sr, ok := i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(sr, expected) {
|
||||
t.Errorf("expected %v but returned %s", sr, expected)
|
||||
}
|
||||
|
||||
data[whitelist] = "www"
|
||||
_, err = p.Parse(ing)
|
||||
if err == nil {
|
||||
t.Errorf("expected error parsing an invalid cidr")
|
||||
}
|
||||
|
||||
if !errors.IsLocationDenied(err) {
|
||||
t.Errorf("expected LocationDenied error: %+v", err)
|
||||
}
|
||||
|
||||
delete(data, whitelist)
|
||||
i, err = p.Parse(ing)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error when no annotation present: %v", err)
|
||||
}
|
||||
|
||||
sr, ok = i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
if !strsEquals(sr.CIDR, []string{}) {
|
||||
t.Errorf("expected empty CIDR but %v returned", sr.CIDR)
|
||||
}
|
||||
|
||||
i, _ = p.Parse(&extensions.Ingress{})
|
||||
sr, ok = i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
if !strsEquals(sr.CIDR, []string{}) {
|
||||
t.Errorf("expected empty CIDR but %v returned", sr.CIDR)
|
||||
}
|
||||
|
||||
data[whitelist] = "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24"
|
||||
i, _ = p.Parse(ing)
|
||||
sr, ok = i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
ecidr := []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"}
|
||||
if !strsEquals(sr.CIDR, ecidr) {
|
||||
t.Errorf("Expected %v CIDR but %v returned", ecidr, sr.CIDR)
|
||||
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) {
|
||||
// TODO: convert test cases to tables
|
||||
ing := buildIngress()
|
||||
|
||||
mockBackend := mockBackend{}
|
||||
mockBackend.Backend.WhitelistSourceRange = []string{"4.4.4.0/24", "1.2.3.4/32"}
|
||||
testNet := "10.0.0.0/24"
|
||||
enet := []string{testNet}
|
||||
|
||||
data := map[string]string{}
|
||||
data[whitelist] = testNet
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
expected := &SourceRange{
|
||||
CIDR: enet,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
p := NewParser(mockBackend)
|
||||
|
||||
i, err := p.Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
sr, ok := i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(sr, expected) {
|
||||
t.Errorf("expected %v but returned %s", sr, expected)
|
||||
}
|
||||
|
||||
data[whitelist] = "www"
|
||||
_, err = p.Parse(ing)
|
||||
if err == nil {
|
||||
t.Errorf("expected error parsing an invalid cidr")
|
||||
}
|
||||
if !errors.IsLocationDenied(err) {
|
||||
t.Errorf("expected LocationDenied error: %+v", err)
|
||||
}
|
||||
|
||||
delete(data, whitelist)
|
||||
i, err = p.Parse(ing)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error when no annotation present: %v", err)
|
||||
}
|
||||
|
||||
sr, ok = i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
if !strsEquals(sr.CIDR, mockBackend.WhitelistSourceRange) {
|
||||
t.Errorf("expected fallback CIDR but %v returned", sr.CIDR)
|
||||
}
|
||||
|
||||
i, _ = p.Parse(&extensions.Ingress{})
|
||||
sr, ok = i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
if !strsEquals(sr.CIDR, mockBackend.WhitelistSourceRange) {
|
||||
t.Errorf("expected fallback CIDR but %v returned", sr.CIDR)
|
||||
}
|
||||
|
||||
data[whitelist] = "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24"
|
||||
i, _ = p.Parse(ing)
|
||||
sr, ok = i.(*SourceRange)
|
||||
if !ok {
|
||||
t.Errorf("expected a SourceRange type")
|
||||
}
|
||||
ecidr := []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"}
|
||||
if !strsEquals(sr.CIDR, ecidr) {
|
||||
t.Errorf("Expected %v CIDR but %v returned", ecidr, sr.CIDR)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,15 @@ import (
|
|||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
limitIP = "ingress.kubernetes.io/limit-connections"
|
||||
limitRPS = "ingress.kubernetes.io/limit-rps"
|
||||
limitRPM = "ingress.kubernetes.io/limit-rpm"
|
||||
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"
|
||||
|
||||
// allow 5 times the specified limit as burst
|
||||
defBurst = 5
|
||||
|
|
@ -48,6 +51,10 @@ type RateLimit struct {
|
|||
RPS Zone `json:"rps"`
|
||||
|
||||
RPM Zone `json:"rpm"`
|
||||
|
||||
LimitRate int `json:"limit-rate"`
|
||||
|
||||
LimitRateAfter int `json:"limit-rate-after"`
|
||||
}
|
||||
|
||||
// Equal tests for equality between two RateLimit types
|
||||
|
|
@ -67,6 +74,12 @@ func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
|
|||
if !(&rt1.RPS).Equal(&rt2.RPS) {
|
||||
return false
|
||||
}
|
||||
if rt1.LimitRate != rt2.LimitRate {
|
||||
return false
|
||||
}
|
||||
if rt1.LimitRateAfter != rt2.LimitRateAfter {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -106,16 +119,26 @@ func (z1 *Zone) Equal(z2 *Zone) bool {
|
|||
}
|
||||
|
||||
type ratelimit struct {
|
||||
backendResolver resolver.DefaultBackend
|
||||
}
|
||||
|
||||
// NewParser creates a new ratelimit annotation parser
|
||||
func NewParser() parser.IngressAnnotation {
|
||||
return ratelimit{}
|
||||
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)
|
||||
|
|
@ -123,9 +146,11 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|||
|
||||
if rpm == 0 && rps == 0 && conn == 0 {
|
||||
return &RateLimit{
|
||||
Connections: Zone{},
|
||||
RPS: Zone{},
|
||||
RPM: Zone{},
|
||||
Connections: Zone{},
|
||||
RPS: Zone{},
|
||||
RPM: Zone{},
|
||||
LimitRate: lr,
|
||||
LimitRateAfter: lra,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -150,5 +175,7 @@ func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|||
Burst: rpm * defBurst,
|
||||
SharedSize: defSharedSize,
|
||||
},
|
||||
LimitRate: lr,
|
||||
LimitRateAfter: lra,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
)
|
||||
|
||||
func buildIngress() *extensions.Ingress {
|
||||
|
|
@ -61,9 +62,19 @@ func buildIngress() *extensions.Ingress {
|
|||
}
|
||||
}
|
||||
|
||||
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().Parse(ing)
|
||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Error("unexpected error with ingress without annotations")
|
||||
}
|
||||
|
|
@ -78,7 +89,7 @@ func TestBadRateLimiting(t *testing.T) {
|
|||
data[limitRPM] = "0"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
_, err := NewParser().Parse(ing)
|
||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with invalid limits (0)")
|
||||
}
|
||||
|
|
@ -87,9 +98,12 @@ func TestBadRateLimiting(t *testing.T) {
|
|||
data[limitIP] = "5"
|
||||
data[limitRPS] = "100"
|
||||
data[limitRPM] = "10"
|
||||
data[limitRATEAFTER] = "100"
|
||||
data[limitRATE] = "10"
|
||||
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err := NewParser().Parse(ing)
|
||||
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -106,4 +120,10 @@ func TestBadRateLimiting(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
|||
"Whitelist": ipwhitelist.NewParser(cfg),
|
||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||
"Proxy": proxy.NewParser(cfg),
|
||||
"RateLimit": ratelimit.NewParser(),
|
||||
"RateLimit": ratelimit.NewParser(cfg),
|
||||
"Redirect": rewrite.NewParser(cfg),
|
||||
"SecureUpstream": secureupstream.NewParser(cfg),
|
||||
"ServiceUpstream": serviceupstream.NewParser(),
|
||||
|
|
|
|||
|
|
@ -34,14 +34,13 @@ import (
|
|||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
unversionedcore "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/ingress/core/pkg/file"
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/class"
|
||||
|
|
@ -151,7 +150,7 @@ func newIngressController(config *Configuration) *GenericController {
|
|||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(glog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&unversionedcore.EventSinkImpl{
|
||||
Interface: config.Client.Core().Events(config.Namespace),
|
||||
Interface: config.Client.CoreV1().Events(config.Namespace),
|
||||
})
|
||||
|
||||
ic := GenericController{
|
||||
|
|
@ -274,27 +273,27 @@ func newIngressController(config *Configuration) *GenericController {
|
|||
}
|
||||
|
||||
ic.ingLister.Store, ic.ingController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Extensions().RESTClient(), "ingresses", ic.cfg.Namespace, fields.Everything()),
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.ExtensionsV1beta1().RESTClient(), "ingresses", ic.cfg.Namespace, fields.Everything()),
|
||||
&extensions.Ingress{}, ic.cfg.ResyncPeriod, ingEventHandler)
|
||||
|
||||
ic.endpLister.Store, ic.endpController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "endpoints", ic.cfg.Namespace, fields.Everything()),
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "endpoints", ic.cfg.Namespace, fields.Everything()),
|
||||
&api.Endpoints{}, ic.cfg.ResyncPeriod, eventHandler)
|
||||
|
||||
ic.secrLister.Store, ic.secrController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "secrets", watchNs, fields.Everything()),
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "secrets", watchNs, fields.Everything()),
|
||||
&api.Secret{}, ic.cfg.ResyncPeriod, secrEventHandler)
|
||||
|
||||
ic.mapLister.Store, ic.mapController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "configmaps", watchNs, fields.Everything()),
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "configmaps", watchNs, fields.Everything()),
|
||||
&api.ConfigMap{}, ic.cfg.ResyncPeriod, mapEventHandler)
|
||||
|
||||
ic.svcLister.Store, ic.svcController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "services", ic.cfg.Namespace, fields.Everything()),
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "services", ic.cfg.Namespace, fields.Everything()),
|
||||
&api.Service{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
||||
|
||||
ic.nodeLister.Store, ic.nodeController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "nodes", api.NamespaceAll, fields.Everything()),
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "nodes", api.NamespaceAll, fields.Everything()),
|
||||
&api.Node{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
||||
|
||||
if config.UpdateStatus {
|
||||
|
|
@ -340,6 +339,11 @@ func (ic GenericController) GetDefaultBackend() defaults.Backend {
|
|||
return ic.cfg.Backend.BackendDefaults()
|
||||
}
|
||||
|
||||
// GetRecorder returns the event recorder
|
||||
func (ic GenericController) GetRecoder() record.EventRecorder {
|
||||
return ic.recorder
|
||||
}
|
||||
|
||||
// GetSecret searches for a secret in the local secrets Store
|
||||
func (ic GenericController) GetSecret(name string) (*api.Secret, error) {
|
||||
s, exists, err := ic.secrLister.Store.GetByKey(name)
|
||||
|
|
@ -582,6 +586,7 @@ func (ic *GenericController) getDefaultUpstream() *ingress.Backend {
|
|||
endps = []ingress.Endpoint{newDefaultServer()}
|
||||
}
|
||||
|
||||
upstream.Service = svc
|
||||
upstream.Endpoints = append(upstream.Endpoints, endps...)
|
||||
return upstream
|
||||
}
|
||||
|
|
@ -845,6 +850,8 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
|||
|
||||
glog.V(3).Infof("creating upstream %v", name)
|
||||
upstreams[name] = newUpstream(name)
|
||||
upstreams[name].Port = path.Backend.ServicePort
|
||||
|
||||
if !upstreams[name].Secure {
|
||||
upstreams[name].Secure = secUpstream.Secure
|
||||
}
|
||||
|
|
@ -881,12 +888,12 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
|||
continue
|
||||
}
|
||||
|
||||
if exists {
|
||||
upstreams[name].Service = s.(*api.Service)
|
||||
} else {
|
||||
if !exists {
|
||||
glog.Warningf("service %v does not exists", svcKey)
|
||||
continue
|
||||
}
|
||||
upstreams[name].Port = path.Backend.ServicePort
|
||||
|
||||
upstreams[name].Service = s.(*api.Service)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1011,6 +1018,7 @@ func (ic *GenericController) createServers(data []interface{},
|
|||
}
|
||||
|
||||
// initialize the default server
|
||||
du := ic.getDefaultUpstream()
|
||||
servers[defServerName] = &ingress.Server{
|
||||
Hostname: defServerName,
|
||||
SSLCertificate: defaultPemFileName,
|
||||
|
|
@ -1019,8 +1027,9 @@ func (ic *GenericController) createServers(data []interface{},
|
|||
{
|
||||
Path: rootLocation,
|
||||
IsDefBackend: true,
|
||||
Backend: ic.getDefaultUpstream().Name,
|
||||
Backend: du.Name,
|
||||
Proxy: ngxProxy,
|
||||
Service: du.Service,
|
||||
},
|
||||
}}
|
||||
|
||||
|
|
@ -1033,12 +1042,13 @@ func (ic *GenericController) createServers(data []interface{},
|
|||
|
||||
// check if ssl passthrough is configured
|
||||
sslpt := ic.annotations.SSLPassthrough(ing)
|
||||
dun := ic.getDefaultUpstream().Name
|
||||
du := ic.getDefaultUpstream()
|
||||
un := du.Name
|
||||
if ing.Spec.Backend != nil {
|
||||
// replace default backend
|
||||
defUpstream := fmt.Sprintf("%v-%v-%v", ing.GetNamespace(), ing.Spec.Backend.ServiceName, ing.Spec.Backend.ServicePort.String())
|
||||
if backendUpstream, ok := upstreams[defUpstream]; ok {
|
||||
dun = backendUpstream.Name
|
||||
un = backendUpstream.Name
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1058,8 +1068,9 @@ func (ic *GenericController) createServers(data []interface{},
|
|||
{
|
||||
Path: rootLocation,
|
||||
IsDefBackend: true,
|
||||
Backend: dun,
|
||||
Backend: un,
|
||||
Proxy: ngxProxy,
|
||||
Service: &api.Service{},
|
||||
},
|
||||
}, SSLPassthrough: sslpt}
|
||||
}
|
||||
|
|
@ -1120,8 +1131,9 @@ func (ic *GenericController) createServers(data []interface{},
|
|||
}
|
||||
|
||||
cert := bc.(*ingress.SSLCert)
|
||||
if !isHostValid(host, cert) {
|
||||
glog.Warningf("ssl certificate %v does not contain a common name for host %v", key, host)
|
||||
err = cert.Certificate.VerifyHostname(host)
|
||||
if err != nil {
|
||||
glog.Warningf("ssl certificate %v does not contain a Common Name or Subject Alternative Name for host %v", key, host)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ limitations under the License.
|
|||
package controller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/imdario/mergo"
|
||||
|
||||
|
|
@ -47,80 +44,6 @@ func newUpstream(name string) *ingress.Backend {
|
|||
}
|
||||
}
|
||||
|
||||
func isHostValid(host string, cert *ingress.SSLCert) bool {
|
||||
if cert == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
lowered := toLowerCaseASCII(host)
|
||||
for _, cn := range cert.CN {
|
||||
if matchHostnames(toLowerCaseASCII(cn), lowered) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func matchHostnames(pattern, host string) bool {
|
||||
host = strings.TrimSuffix(host, ".")
|
||||
pattern = strings.TrimSuffix(pattern, ".")
|
||||
|
||||
if len(pattern) == 0 || len(host) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
patternParts := strings.Split(pattern, ".")
|
||||
hostParts := strings.Split(host, ".")
|
||||
|
||||
if len(patternParts) != len(hostParts) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, patternPart := range patternParts {
|
||||
if i == 0 && patternPart == "*" {
|
||||
continue
|
||||
}
|
||||
if patternPart != hostParts[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// toLowerCaseASCII returns a lower-case version of in. See RFC 6125 6.4.1. We use
|
||||
// an explicitly ASCII function to avoid any sharp corners resulting from
|
||||
// performing Unicode operations on DNS labels.
|
||||
func toLowerCaseASCII(in string) string {
|
||||
// If the string is already lower-case then there's nothing to do.
|
||||
isAlreadyLowerCase := true
|
||||
for _, c := range in {
|
||||
if c == utf8.RuneError {
|
||||
// If we get a UTF-8 error then there might be
|
||||
// upper-case ASCII bytes in the invalid sequence.
|
||||
isAlreadyLowerCase = false
|
||||
break
|
||||
}
|
||||
if 'A' <= c && c <= 'Z' {
|
||||
isAlreadyLowerCase = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isAlreadyLowerCase {
|
||||
return in
|
||||
}
|
||||
|
||||
out := []byte(in)
|
||||
for i, c := range out {
|
||||
if 'A' <= c && c <= 'Z' {
|
||||
out[i] += 'a' - 'A'
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}) {
|
||||
if _, ok := anns[DeniedKeyName]; ok {
|
||||
loc.Denied = anns[DeniedKeyName].(error)
|
||||
|
|
|
|||
|
|
@ -36,57 +36,6 @@ func (fe *fakeError) Error() string {
|
|||
return "fakeError"
|
||||
}
|
||||
|
||||
func TestIsHostValid(t *testing.T) {
|
||||
fkCert := &ingress.SSLCert{
|
||||
CAFileName: "foo",
|
||||
PemFileName: "foo.cr",
|
||||
PemSHA: "perha",
|
||||
CN: []string{
|
||||
"*.cluster.local", "default.local",
|
||||
},
|
||||
}
|
||||
|
||||
fooTests := []struct {
|
||||
cr *ingress.SSLCert
|
||||
host string
|
||||
er bool
|
||||
}{
|
||||
{nil, "foo1.cluster.local", false},
|
||||
{fkCert, "foo1.cluster.local", true},
|
||||
{fkCert, "default.local", true},
|
||||
{fkCert, "foo2.cluster.local.t", false},
|
||||
{fkCert, "", false},
|
||||
}
|
||||
|
||||
for _, foo := range fooTests {
|
||||
r := isHostValid(foo.host, foo.cr)
|
||||
if r != foo.er {
|
||||
t.Errorf("Returned %v but expected %v for foo=%v", r, foo.er, foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchHostnames(t *testing.T) {
|
||||
fooTests := []struct {
|
||||
pattern string
|
||||
host string
|
||||
er bool
|
||||
}{
|
||||
{"*.cluster.local.", "foo1.cluster.local.", true},
|
||||
{"foo1.cluster.local.", "foo2.cluster.local.", false},
|
||||
{"cluster.local.", "foo1.cluster.local.", false},
|
||||
{".", "foo1.cluster.local.", false},
|
||||
{"cluster.local.", ".", false},
|
||||
}
|
||||
|
||||
for _, foo := range fooTests {
|
||||
r := matchHostnames(foo.pattern, foo.host)
|
||||
if r != foo.er {
|
||||
t.Errorf("Returned %v but expected %v for foo=%v", r, foo.er, foo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeLocationAnnotations(t *testing.T) {
|
||||
// initial parameters
|
||||
loc := ingress.Location{}
|
||||
|
|
|
|||
|
|
@ -88,4 +88,15 @@ type Backend struct {
|
|||
// WhitelistSourceRange allows limiting access to certain client addresses
|
||||
// http://nginx.org/en/docs/http/ngx_http_access_module.html
|
||||
WhitelistSourceRange []string `json:"whitelist-source-range,-"`
|
||||
|
||||
// Limits the rate of response transmission to a client.
|
||||
// The rate is specified in bytes per second. The zero value disables rate limiting.
|
||||
// The limit is set per a request, and so if a client simultaneously opens two connections,
|
||||
// the overall rate will be twice as much as the specified limit.
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate
|
||||
LimitRate int `json:"limit-rate"`
|
||||
|
||||
// Sets the initial amount after which the further transmission of a response to a client will be rate limited.
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate_after
|
||||
LimitRateAfter int `json:"limit-rate-after"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package ingress
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"time"
|
||||
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -72,6 +73,7 @@ func (c LocationByPath) Less(i, j int) bool {
|
|||
// SSLCert describes a SSL certificate to be used in a server
|
||||
type SSLCert struct {
|
||||
meta_v1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Certificate *x509.Certificate `json:"certificate,omitempty"`
|
||||
// CAFileName contains the path to the file with the root certificate
|
||||
CAFileName string `json:"caFileName"`
|
||||
// PemFileName contains the path to the file with the certificate and key concatenated
|
||||
|
|
|
|||
|
|
@ -92,10 +92,16 @@ type statusSync struct {
|
|||
// Run starts the loop to keep the status in sync
|
||||
func (s statusSync) Run(stopCh <-chan struct{}) {
|
||||
go wait.Forever(s.elector.Run, 0)
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -47,13 +47,12 @@ func TestEqualConfiguration(t *testing.T) {
|
|||
}
|
||||
|
||||
if !b.Equal(a) {
|
||||
t.Errorf("expected equal configurations (configuration-a.json and configuration-b.json)")
|
||||
t.Errorf("expected equal configurations (configuration-b.json and configuration-a.json)")
|
||||
}
|
||||
|
||||
if a.Equal(c) {
|
||||
t.Errorf("expected equal configurations (configuration-a.json and configuration-c.json)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func readJSON(p string) (*Configuration, error) {
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ type Endpoint struct {
|
|||
// to consider the endpoint unavailable
|
||||
FailTimeout int `json:"failTimeout"`
|
||||
// Target returns a reference to the object providing the endpoint
|
||||
Target *api.ObjectReference `json:"target"`
|
||||
Target *api.ObjectReference `json:"target,omipempty"`
|
||||
}
|
||||
|
||||
// Server describes a website
|
||||
|
|
@ -255,7 +255,7 @@ type Location struct {
|
|||
// Backend describes the name of the backend to use.
|
||||
Backend string `json:"backend"`
|
||||
|
||||
Service *api.Service `json:"service"`
|
||||
Service *api.Service `json:"service,omitempty"`
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
// BasicDigestAuth returns authentication configuration for
|
||||
// an Ingress rule.
|
||||
|
|
|
|||
|
|
@ -149,21 +149,17 @@ func (b1 *Backend) Equal(b2 *Backend) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if (b1.Service == nil && b2.Service != nil) ||
|
||||
(b1.Service != nil && b2.Service == nil) {
|
||||
if b1.Service == nil || b2.Service == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if b1.Service != nil && b2.Service != nil {
|
||||
if b1.Service.GetNamespace() != b2.Service.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
if b1.Service.GetName() != b2.Service.GetName() {
|
||||
return false
|
||||
}
|
||||
if b1.Service.GetResourceVersion() != b2.Service.GetResourceVersion() {
|
||||
return false
|
||||
}
|
||||
if b1.Service.GetNamespace() != b2.Service.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
if b1.Service.GetName() != b2.Service.GetName() {
|
||||
return false
|
||||
}
|
||||
if b1.Service.GetResourceVersion() != b2.Service.GetResourceVersion() {
|
||||
return false
|
||||
}
|
||||
|
||||
if b1.Port != b2.Port {
|
||||
|
|
@ -258,7 +254,14 @@ func (e1 *Endpoint) Equal(e2 *Endpoint) bool {
|
|||
if e1.FailTimeout != e2.FailTimeout {
|
||||
return false
|
||||
}
|
||||
if e1.Target != e2.Target {
|
||||
|
||||
if e1.Target == nil || e2.Target == nil {
|
||||
return false
|
||||
}
|
||||
if e1.Target.UID != e2.Target.UID {
|
||||
return false
|
||||
}
|
||||
if e1.Target.ResourceVersion != e2.Target.ResourceVersion {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -324,21 +327,17 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if (l1.Service == nil && l2.Service != nil) ||
|
||||
(l1.Service != nil && l2.Service == nil) {
|
||||
if l1.Service == nil || l2.Service == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if l1.Service != nil && l2.Service != nil {
|
||||
if l1.Service.GetNamespace() != l2.Service.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
if l1.Service.GetName() != l2.Service.GetName() {
|
||||
return false
|
||||
}
|
||||
if l1.Service.GetResourceVersion() != l2.Service.GetResourceVersion() {
|
||||
return false
|
||||
}
|
||||
if l1.Service.GetNamespace() != l2.Service.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
if l1.Service.GetName() != l2.Service.GetName() {
|
||||
return false
|
||||
}
|
||||
if l1.Service.GetResourceVersion() != l2.Service.GetResourceVersion() {
|
||||
return false
|
||||
}
|
||||
|
||||
if l1.Port.StrVal != l2.Port.StrVal {
|
||||
|
|
@ -398,21 +397,18 @@ func (ptb1 *SSLPassthroughBackend) Equal(ptb2 *SSLPassthroughBackend) bool {
|
|||
if ptb1.Port != ptb2.Port {
|
||||
return false
|
||||
}
|
||||
if (ptb1.Service == nil && ptb2.Service != nil) ||
|
||||
(ptb1.Service != nil && ptb2.Service == nil) {
|
||||
|
||||
if ptb1.Service == nil || ptb2.Service == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if ptb1.Service != nil && ptb2.Service != nil {
|
||||
if ptb1.Service.GetNamespace() != ptb2.Service.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
if ptb1.Service.GetName() != ptb2.Service.GetName() {
|
||||
return false
|
||||
}
|
||||
if ptb1.Service.GetResourceVersion() != ptb2.Service.GetResourceVersion() {
|
||||
return false
|
||||
}
|
||||
if ptb1.Service.GetNamespace() != ptb2.Service.GetNamespace() {
|
||||
return false
|
||||
}
|
||||
if ptb1.Service.GetName() != ptb2.Service.GetName() {
|
||||
return false
|
||||
}
|
||||
if ptb1.Service.GetResourceVersion() != ptb2.Service.GetResourceVersion() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func GetSystemNameServers() ([]net.IP, error) {
|
|||
continue
|
||||
}
|
||||
fields := strings.Fields(trimmed)
|
||||
if len(fields) == 0 {
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
if fields[0] == "nameserver" {
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
caFile.Write([]byte("\n"))
|
||||
|
||||
return &ingress.SSLCert{
|
||||
Certificate: pemCert,
|
||||
CAFileName: pemFileName,
|
||||
PemFileName: pemFileName,
|
||||
PemSHA: file.SHA1(pemFileName),
|
||||
|
|
@ -169,6 +170,7 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
}
|
||||
|
||||
return &ingress.SSLCert{
|
||||
Certificate: pemCert,
|
||||
PemFileName: pemFileName,
|
||||
PemSHA: file.SHA1(pemFileName),
|
||||
CN: cn.List(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue