Deny location mapping in case of specific errors
This commit is contained in:
parent
c49b03facc
commit
597a0e691a
34 changed files with 968 additions and 333 deletions
|
@ -137,6 +137,7 @@ var (
|
||||||
"buildRateLimit": buildRateLimit,
|
"buildRateLimit": buildRateLimit,
|
||||||
"buildSSPassthroughUpstreams": buildSSPassthroughUpstreams,
|
"buildSSPassthroughUpstreams": buildSSPassthroughUpstreams,
|
||||||
"buildResolvers": buildResolvers,
|
"buildResolvers": buildResolvers,
|
||||||
|
"isLocationAllowed": isLocationAllowed,
|
||||||
|
|
||||||
"contains": strings.Contains,
|
"contains": strings.Contains,
|
||||||
"hasPrefix": strings.HasPrefix,
|
"hasPrefix": strings.HasPrefix,
|
||||||
|
@ -352,3 +353,13 @@ func buildRateLimit(input interface{}) []string {
|
||||||
|
|
||||||
return limits
|
return limits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isLocationAllowed(input interface{}) bool {
|
||||||
|
loc, ok := input.(*ingress.Location)
|
||||||
|
if !ok {
|
||||||
|
glog.Errorf("expected an ingress.Location type but %T was returned", input)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return loc.Denied == nil
|
||||||
|
}
|
||||||
|
|
|
@ -240,6 +240,7 @@ http {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
location {{ $path }} {
|
location {{ $path }} {
|
||||||
|
{{ if isLocationAllowed $location }}
|
||||||
{{ if gt (len $location.Whitelist.CIDR) 0 }}
|
{{ if gt (len $location.Whitelist.CIDR) 0 }}
|
||||||
{{ range $ip := $location.Whitelist.CIDR }}
|
{{ range $ip := $location.Whitelist.CIDR }}
|
||||||
allow {{ $ip }};{{ end }}
|
allow {{ $ip }};{{ end }}
|
||||||
|
@ -312,6 +313,10 @@ http {
|
||||||
|
|
||||||
set $proxy_upstream_name "{{ $location.Backend }}";
|
set $proxy_upstream_name "{{ $location.Backend }}";
|
||||||
{{ buildProxyPass $backends $location }}
|
{{ buildProxyPass $backends $location }}
|
||||||
|
{{ else }}
|
||||||
|
#{{ $location.Denied }}
|
||||||
|
return 503;
|
||||||
|
{{ end }}
|
||||||
}
|
}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
@ -326,6 +331,7 @@ http {
|
||||||
# with an external software (like sysdig)
|
# with an external software (like sysdig)
|
||||||
location /nginx_status {
|
location /nginx_status {
|
||||||
allow 127.0.0.1;
|
allow 127.0.0.1;
|
||||||
|
allow ::1;
|
||||||
deny all;
|
deny all;
|
||||||
|
|
||||||
access_log off;
|
access_log off;
|
||||||
|
@ -365,6 +371,7 @@ http {
|
||||||
# TODO: enable extraction for vts module.
|
# TODO: enable extraction for vts module.
|
||||||
location /internal_nginx_status {
|
location /internal_nginx_status {
|
||||||
allow 127.0.0.1;
|
allow 127.0.0.1;
|
||||||
|
allow ::1;
|
||||||
deny all;
|
deny all;
|
||||||
|
|
||||||
access_log off;
|
access_log off;
|
||||||
|
|
92
core/pkg/cache/main_test.go
vendored
Normal file
92
core/pkg/cache/main_test.go
vendored
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
"k8s.io/kubernetes/pkg/client/cache"
|
||||||
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStoreToIngressLister(t *testing.T) {
|
||||||
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||||
|
ids := sets.NewString("foo", "bar", "baz")
|
||||||
|
for id := range ids {
|
||||||
|
store.Add(&extensions.Ingress{ObjectMeta: api.ObjectMeta{Name: id}})
|
||||||
|
}
|
||||||
|
sml := StoreToIngressLister{store}
|
||||||
|
|
||||||
|
gotIngress := sml.List()
|
||||||
|
got := make([]string, len(gotIngress))
|
||||||
|
for ix := range gotIngress {
|
||||||
|
ing, ok := gotIngress[ix].(*extensions.Ingress)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected an Ingress type")
|
||||||
|
}
|
||||||
|
got[ix] = ing.Name
|
||||||
|
}
|
||||||
|
if !ids.HasAll(got...) || len(got) != len(ids) {
|
||||||
|
t.Errorf("expected %v, got %v", ids, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreToSecretsLister(t *testing.T) {
|
||||||
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||||
|
ids := sets.NewString("foo", "bar", "baz")
|
||||||
|
for id := range ids {
|
||||||
|
store.Add(&api.Secret{ObjectMeta: api.ObjectMeta{Name: id}})
|
||||||
|
}
|
||||||
|
sml := StoreToSecretsLister{store}
|
||||||
|
|
||||||
|
gotIngress := sml.List()
|
||||||
|
got := make([]string, len(gotIngress))
|
||||||
|
for ix := range gotIngress {
|
||||||
|
s, ok := gotIngress[ix].(*api.Secret)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Secret type")
|
||||||
|
}
|
||||||
|
got[ix] = s.Name
|
||||||
|
}
|
||||||
|
if !ids.HasAll(got...) || len(got) != len(ids) {
|
||||||
|
t.Errorf("expected %v, got %v", ids, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStoreToConfigmapLister(t *testing.T) {
|
||||||
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||||
|
ids := sets.NewString("foo", "bar", "baz")
|
||||||
|
for id := range ids {
|
||||||
|
store.Add(&api.ConfigMap{ObjectMeta: api.ObjectMeta{Name: id}})
|
||||||
|
}
|
||||||
|
sml := StoreToConfigmapLister{store}
|
||||||
|
|
||||||
|
gotIngress := sml.List()
|
||||||
|
got := make([]string, len(gotIngress))
|
||||||
|
for ix := range gotIngress {
|
||||||
|
m, ok := gotIngress[ix].(*api.ConfigMap)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected an Ingress type")
|
||||||
|
}
|
||||||
|
got[ix] = m.Name
|
||||||
|
}
|
||||||
|
if !ids.HasAll(got...) || len(got) != len(ids) {
|
||||||
|
t.Errorf("expected %v, got %v", ids, got)
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,44 +17,31 @@ limitations under the License.
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
authType = "ingress.kubernetes.io/auth-type"
|
authType = "ingress.kubernetes.io/auth-type"
|
||||||
authSecret = "ingress.kubernetes.io/auth-secret"
|
authSecret = "ingress.kubernetes.io/auth-secret"
|
||||||
authRealm = "ingress.kubernetes.io/auth-realm"
|
authRealm = "ingress.kubernetes.io/auth-realm"
|
||||||
|
|
||||||
// DefAuthDirectory default directory used to store files
|
|
||||||
// to authenticate request
|
|
||||||
DefAuthDirectory = "/etc/ingress-controller/auth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
// TODO: check permissions required
|
|
||||||
os.MkdirAll(DefAuthDirectory, 0655)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
authTypeRegex = regexp.MustCompile(`basic|digest`)
|
authTypeRegex = regexp.MustCompile(`basic|digest`)
|
||||||
|
// AuthDirectory default directory used to store files
|
||||||
// ErrInvalidAuthType is return in case of unsupported authentication type
|
// to authenticate request
|
||||||
ErrInvalidAuthType = errors.New("invalid authentication type")
|
AuthDirectory = "/etc/ingress-controller/auth"
|
||||||
|
|
||||||
// ErrMissingSecretName is returned when the name of the secret is missing
|
|
||||||
ErrMissingSecretName = errors.New("secret name is missing")
|
|
||||||
|
|
||||||
// ErrMissingAuthInSecret is returned when there is no auth key in secret data
|
|
||||||
ErrMissingAuthInSecret = errors.New("the secret does not contains the auth key")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BasicDigest returns authentication configuration for an Ingress rule
|
// BasicDigest returns authentication configuration for an Ingress rule
|
||||||
|
@ -65,40 +52,53 @@ type BasicDigest struct {
|
||||||
Secured bool `json:"secured"`
|
Secured bool `json:"secured"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
type auth struct {
|
||||||
|
secretResolver resolver.Secret
|
||||||
|
authDirectory string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new authentication annotation parser
|
||||||
|
func NewParser(authDirectory string, sr resolver.Secret) parser.IngressAnnotation {
|
||||||
|
// TODO: check permissions required
|
||||||
|
os.MkdirAll(authDirectory, 0655)
|
||||||
|
return auth{sr, authDirectory}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress
|
||||||
// rule used to add authentication in the paths defined in the rule
|
// rule used to add authentication in the paths defined in the rule
|
||||||
// and generated an htpasswd compatible file to be used as source
|
// and generated an htpasswd compatible file to be used as source
|
||||||
// during the authentication process
|
// during the authentication process
|
||||||
func ParseAnnotations(ing *extensions.Ingress, authDir string, fn func(string) (*api.Secret, error)) (*BasicDigest, error) {
|
func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
if ing.GetAnnotations() == nil {
|
|
||||||
return &BasicDigest{}, parser.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
at, err := parser.GetStringAnnotation(authType, ing)
|
at, err := parser.GetStringAnnotation(authType, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &BasicDigest{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authTypeRegex.MatchString(at) {
|
if !authTypeRegex.MatchString(at) {
|
||||||
return &BasicDigest{}, ErrInvalidAuthType
|
return nil, ing_errors.NewLocationDenied("invalid authentication type")
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := parser.GetStringAnnotation(authSecret, ing)
|
s, err := parser.GetStringAnnotation(authSecret, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &BasicDigest{}, err
|
return nil, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "error reading secret name from annotation"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := fn(fmt.Sprintf("%v/%v", ing.Namespace, s))
|
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
|
||||||
|
secret, err := a.secretResolver.GetSecret(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &BasicDigest{}, err
|
return nil, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrapf(err, "unexpected error reading secret %v", name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
realm, _ := parser.GetStringAnnotation(authRealm, ing)
|
realm, _ := parser.GetStringAnnotation(authRealm, ing)
|
||||||
|
|
||||||
passFile := fmt.Sprintf("%v/%v-%v.passwd", authDir, ing.GetNamespace(), ing.GetName())
|
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
|
||||||
err = dumpSecret(passFile, secret)
|
err = dumpSecret(passFile, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &BasicDigest{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BasicDigest{
|
return &BasicDigest{
|
||||||
|
@ -114,9 +114,18 @@ func ParseAnnotations(ing *extensions.Ingress, authDir string, fn func(string) (
|
||||||
func dumpSecret(filename string, secret *api.Secret) error {
|
func dumpSecret(filename string, secret *api.Secret) error {
|
||||||
val, ok := secret.Data["auth"]
|
val, ok := secret.Data["auth"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrMissingAuthInSecret
|
return ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Errorf("the secret %v does not contains a key with value auth", secret.Name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check permissions required
|
// TODO: check permissions required
|
||||||
return ioutil.WriteFile(filename, val, 0777)
|
err := ioutil.WriteFile(filename, val, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "unexpected error creating password file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/util/intstr"
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
|
@ -63,7 +64,14 @@ func buildIngress() *extensions.Ingress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockSecret(name string) (*api.Secret, error) {
|
type mockSecret struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockSecret) GetSecret(name string) (*api.Secret, error) {
|
||||||
|
if name != "default/demo-secret" {
|
||||||
|
return nil, errors.Errorf("there is no secret with name %v", name)
|
||||||
|
}
|
||||||
|
|
||||||
return &api.Secret{
|
return &api.Secret{
|
||||||
ObjectMeta: api.ObjectMeta{
|
ObjectMeta: api.ObjectMeta{
|
||||||
Namespace: api.NamespaceDefault,
|
Namespace: api.NamespaceDefault,
|
||||||
|
@ -72,9 +80,12 @@ func mockSecret(name string) (*api.Secret, error) {
|
||||||
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
|
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIngressWithoutAuth(t *testing.T) {
|
func TestIngressWithoutAuth(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
_, err := ParseAnnotations(ing, "", mockSecret)
|
_, dir, _ := dummySecretContent(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
_, err := NewParser(dir, mockSecret{}).Parse(ing)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error with ingress without annotations")
|
t.Error("Expected error with ingress without annotations")
|
||||||
}
|
}
|
||||||
|
@ -92,11 +103,14 @@ func TestIngressAuth(t *testing.T) {
|
||||||
_, dir, _ := dummySecretContent(t)
|
_, dir, _ := dummySecretContent(t)
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
auth, err := ParseAnnotations(ing, dir, mockSecret)
|
i, err := NewParser(dir, mockSecret{}).Parse(ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Uxpected error with ingress: %v", err)
|
t.Errorf("Uxpected error with ingress: %v", err)
|
||||||
}
|
}
|
||||||
|
auth, ok := i.(*BasicDigest)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a BasicDigest type")
|
||||||
|
}
|
||||||
if auth.Type != "basic" {
|
if auth.Type != "basic" {
|
||||||
t.Errorf("Expected basic as auth type but returned %s", auth.Type)
|
t.Errorf("Expected basic as auth type but returned %s", auth.Type)
|
||||||
}
|
}
|
||||||
|
@ -108,6 +122,24 @@ func TestIngressAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIngressAuthWithoutSecret(t *testing.T) {
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
data := map[string]string{}
|
||||||
|
data[authType] = "basic"
|
||||||
|
data[authSecret] = "invalid-secret"
|
||||||
|
data[authRealm] = "-realm-"
|
||||||
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
|
_, dir, _ := dummySecretContent(t)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
_, err := NewParser(dir, mockSecret{}).Parse(ing)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected an error with invalid secret name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
||||||
dir, err := ioutil.TempDir("", fmt.Sprintf("%v", time.Now().Unix()))
|
dir, err := ioutil.TempDir("", fmt.Sprintf("%v", time.Now().Unix()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -119,7 +151,7 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
defer tmpfile.Close()
|
defer tmpfile.Close()
|
||||||
s, _ := mockSecret("demo")
|
s, _ := mockSecret{}.GetSecret("default/demo-secret")
|
||||||
return tmpfile.Name(), dir, s
|
return tmpfile.Name(), dir, s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,13 @@ limitations under the License.
|
||||||
package authreq
|
package authreq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -57,44 +57,49 @@ func validMethod(method string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to use an external URL as source for authentication
|
// rule used to use an external URL as source for authentication
|
||||||
func ParseAnnotations(ing *extensions.Ingress) (External, error) {
|
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
if ing.GetAnnotations() == nil {
|
|
||||||
return External{}, parser.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := parser.GetStringAnnotation(authURL, ing)
|
str, err := parser.GetStringAnnotation(authURL, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return External{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if str == "" {
|
if str == "" {
|
||||||
return External{}, fmt.Errorf("an empty string is not a valid URL")
|
return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
ur, err := url.Parse(str)
|
ur, err := url.Parse(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return External{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ur.Scheme == "" {
|
if ur.Scheme == "" {
|
||||||
return External{}, fmt.Errorf("url scheme is empty")
|
return nil, ing_errors.NewLocationDenied("url scheme is empty")
|
||||||
}
|
}
|
||||||
if ur.Host == "" {
|
if ur.Host == "" {
|
||||||
return External{}, fmt.Errorf("url host is empty")
|
return nil, ing_errors.NewLocationDenied("url host is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(ur.Host, "..") {
|
if strings.Contains(ur.Host, "..") {
|
||||||
return External{}, fmt.Errorf("invalid url host")
|
return nil, ing_errors.NewLocationDenied("invalid url host")
|
||||||
}
|
}
|
||||||
|
|
||||||
m, _ := parser.GetStringAnnotation(authMethod, ing)
|
m, _ := parser.GetStringAnnotation(authMethod, ing)
|
||||||
if len(m) != 0 && !validMethod(m) {
|
if len(m) != 0 && !validMethod(m) {
|
||||||
return External{}, fmt.Errorf("invalid HTTP method")
|
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
|
||||||
}
|
}
|
||||||
|
|
||||||
sb, _ := parser.GetBoolAnnotation(authBody, ing)
|
sb, _ := parser.GetBoolAnnotation(authBody, ing)
|
||||||
|
|
||||||
return External{
|
return &External{
|
||||||
URL: str,
|
URL: str,
|
||||||
Method: m,
|
Method: m,
|
||||||
SendBody: sb,
|
SendBody: sb,
|
||||||
|
|
|
@ -87,15 +87,17 @@ func TestAnnotations(t *testing.T) {
|
||||||
data[authBody] = fmt.Sprintf("%v", test.sendBody)
|
data[authBody] = fmt.Sprintf("%v", test.sendBody)
|
||||||
data[authMethod] = fmt.Sprintf("%v", test.method)
|
data[authMethod] = fmt.Sprintf("%v", test.method)
|
||||||
|
|
||||||
u, err := ParseAnnotations(ing)
|
i, err := NewParser().Parse(ing)
|
||||||
|
|
||||||
if test.expErr {
|
if test.expErr {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("%v: expected error but retuned nil", test.title)
|
t.Errorf("%v: expected error but retuned nil", test.title)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
u, ok := i.(*External)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%v: expected an External type", test.title)
|
||||||
|
}
|
||||||
if u.URL != test.url {
|
if u.URL != test.url {
|
||||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,10 @@ limitations under the License.
|
||||||
package authtls
|
package authtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
"k8s.io/ingress/core/pkg/k8s"
|
"k8s.io/ingress/core/pkg/k8s"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,6 +29,13 @@ const (
|
||||||
authTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
authTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AuthCertificate has a method that searchs for a secret
|
||||||
|
// that contains a SSL certificate.
|
||||||
|
// The secret must contain 3 keys named:
|
||||||
|
type AuthCertificate interface {
|
||||||
|
GetAuthCertificate(string) (*SSLCert, error)
|
||||||
|
}
|
||||||
|
|
||||||
// SSLCert returns external authentication configuration for an Ingress rule
|
// SSLCert returns external authentication configuration for an Ingress rule
|
||||||
type SSLCert struct {
|
type SSLCert struct {
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
|
@ -39,27 +45,31 @@ type SSLCert struct {
|
||||||
PemSHA string `json:"pemSha"`
|
PemSHA string `json:"pemSha"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authTLS struct {
|
||||||
|
certResolver AuthCertificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new TLS authentication annotation parser
|
||||||
|
func NewParser(resolver AuthCertificate) parser.IngressAnnotation {
|
||||||
|
return authTLS{resolver}
|
||||||
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to use an external URL as source for authentication
|
// rule used to use an external URL as source for authentication
|
||||||
func ParseAnnotations(ing *extensions.Ingress,
|
func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
fn func(secret string) (*SSLCert, error)) (*SSLCert, error) {
|
|
||||||
if ing.GetAnnotations() == nil {
|
|
||||||
return &SSLCert{}, parser.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
|
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &SSLCert{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if str == "" {
|
if str == "" {
|
||||||
return &SSLCert{}, fmt.Errorf("an empty string is not a valid secret name")
|
return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = k8s.ParseNameNS(str)
|
_, _, err = k8s.ParseNameNS(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &SSLCert{}, err
|
return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(str)
|
return a.certResolver.GetAuthCertificate(str)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cors = "ingress.kubernetes.io/enable-cors"
|
annotation = "ingress.kubernetes.io/enable-cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
type cors struct {
|
||||||
// rule used to indicate if the location/s should allows CORS
|
}
|
||||||
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
|
|
||||||
return parser.GetBoolAnnotation(cors, ing)
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,22 +35,32 @@ type Upstream struct {
|
||||||
FailTimeout int `json:"failTimeout"`
|
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
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to configure upstream check parameters
|
// rule used to configure upstream check parameters
|
||||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Upstream {
|
func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
if ing.GetAnnotations() == nil {
|
if ing.GetAnnotations() == nil {
|
||||||
return &Upstream{cfg.UpstreamMaxFails, cfg.UpstreamFailTimeout}
|
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
|
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mf = cfg.UpstreamMaxFails
|
mf = defBackend.UpstreamMaxFails
|
||||||
}
|
}
|
||||||
|
|
||||||
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
|
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ft = cfg.UpstreamFailTimeout
|
ft = defBackend.UpstreamFailTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Upstream{mf, ft}
|
return &Upstream{mf, ft}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,13 @@ func buildIngress() *extensions.Ingress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{UpstreamFailTimeout: 1}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIngressHealthCheck(t *testing.T) {
|
func TestIngressHealthCheck(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
|
|
||||||
|
@ -68,15 +75,17 @@ func TestIngressHealthCheck(t *testing.T) {
|
||||||
data[upsMaxFails] = "2"
|
data[upsMaxFails] = "2"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
cfg := defaults.Backend{UpstreamFailTimeout: 1}
|
hzi, _ := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
nginxHz, ok := hzi.(*Upstream)
|
||||||
nginxHz := ParseAnnotations(cfg, ing)
|
if !ok {
|
||||||
|
t.Errorf("expected a Upstream type")
|
||||||
|
}
|
||||||
|
|
||||||
if nginxHz.MaxFails != 2 {
|
if nginxHz.MaxFails != 2 {
|
||||||
t.Errorf("Expected 2 as max-fails but returned %v", nginxHz.MaxFails)
|
t.Errorf("expected 2 as max-fails but returned %v", nginxHz.MaxFails)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nginxHz.FailTimeout != 1 {
|
if nginxHz.FailTimeout != 1 {
|
||||||
t.Errorf("Expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
|
t.Errorf("expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,51 +17,55 @@ limitations under the License.
|
||||||
package ipwhitelist
|
package ipwhitelist
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/util/net/sets"
|
"k8s.io/kubernetes/pkg/util/net/sets"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
whitelist = "ingress.kubernetes.io/whitelist-source-range"
|
whitelist = "ingress.kubernetes.io/whitelist-source-range"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidCIDR returned error when the whitelist annotation does not
|
|
||||||
// contains a valid IP or network address
|
|
||||||
ErrInvalidCIDR = errors.New("the annotation does not contains a valid IP address or network")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SourceRange returns the CIDR
|
// SourceRange returns the CIDR
|
||||||
type SourceRange struct {
|
type SourceRange struct {
|
||||||
CIDR []string `json:"cidr"`
|
CIDR []string `json:"cidr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to limit access to certain client addresses or networks.
|
// rule used to limit access to certain client addresses or networks.
|
||||||
// Multiple ranges can specified using commas as separator
|
// Multiple ranges can specified using commas as separator
|
||||||
// e.g. `18.0.0.0/8,56.0.0.0/8`
|
// e.g. `18.0.0.0/8,56.0.0.0/8`
|
||||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*SourceRange, error) {
|
func (a ipwhitelist) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
sort.Strings(cfg.WhitelistSourceRange)
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
if ing.GetAnnotations() == nil {
|
sort.Strings(defBackend.WhitelistSourceRange)
|
||||||
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, parser.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := parser.GetStringAnnotation(whitelist, ing)
|
val, err := parser.GetStringAnnotation(whitelist, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, err
|
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
values := strings.Split(val, ",")
|
values := strings.Split(val, ",")
|
||||||
ipnets, err := sets.ParseIPNets(values...)
|
ipnets, err := sets.ParseIPNets(values...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, ErrInvalidCIDR
|
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, ing_errors.LocationDenied{
|
||||||
|
Reason: errors.Wrap(err, "the annotation does not contains a valid IP address or network"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cidrs := []string{}
|
cidrs := []string{}
|
||||||
|
|
|
@ -62,6 +62,13 @@ func buildIngress() *extensions.Ingress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{}
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseAnnotations(t *testing.T) {
|
func TestParseAnnotations(t *testing.T) {
|
||||||
// TODO: convert test cases to tables
|
// TODO: convert test cases to tables
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
|
@ -77,38 +84,56 @@ func TestParseAnnotations(t *testing.T) {
|
||||||
CIDR: enet,
|
CIDR: enet,
|
||||||
}
|
}
|
||||||
|
|
||||||
sr, err := ParseAnnotations(defaults.Backend{}, ing)
|
p := NewParser(mockBackend{})
|
||||||
|
|
||||||
|
i, err := p.Parse(ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
sr, ok := i.(*SourceRange)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a SourceRange type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(sr, expected) {
|
if !reflect.DeepEqual(sr, expected) {
|
||||||
t.Errorf("Expected %v but returned %s", sr, expected)
|
t.Errorf("expected %v but returned %s", sr, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
data[whitelist] = "www"
|
data[whitelist] = "www"
|
||||||
_, err = ParseAnnotations(defaults.Backend{}, ing)
|
_, err = p.Parse(ing)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected error parsing an invalid cidr")
|
t.Errorf("expected error parsing an invalid cidr")
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(data, whitelist)
|
delete(data, whitelist)
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
sr, err = ParseAnnotations(defaults.Backend{}, ing)
|
i, err = p.Parse(ing)
|
||||||
|
sr, ok = i.(*SourceRange)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a SourceRange type")
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected error parsing an invalid cidr")
|
t.Errorf("expected error parsing an invalid cidr")
|
||||||
}
|
}
|
||||||
if !strsEquals(sr.CIDR, []string{}) {
|
if !strsEquals(sr.CIDR, []string{}) {
|
||||||
t.Errorf("Expected empty CIDR but %v returned", sr.CIDR)
|
t.Errorf("expected empty CIDR but %v returned", sr.CIDR)
|
||||||
}
|
}
|
||||||
|
|
||||||
sr, _ = ParseAnnotations(defaults.Backend{}, &extensions.Ingress{})
|
i, _ = p.Parse(&extensions.Ingress{})
|
||||||
|
sr, ok = i.(*SourceRange)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a SourceRange type")
|
||||||
|
}
|
||||||
if !strsEquals(sr.CIDR, []string{}) {
|
if !strsEquals(sr.CIDR, []string{}) {
|
||||||
t.Errorf("Expected empty CIDR but %v returned", sr.CIDR)
|
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"
|
data[whitelist] = "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24"
|
||||||
sr, _ = ParseAnnotations(defaults.Backend{}, ing)
|
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"}
|
ecidr := []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"}
|
||||||
if !strsEquals(sr.CIDR, ecidr) {
|
if !strsEquals(sr.CIDR, ecidr) {
|
||||||
t.Errorf("Expected %v CIDR but %v returned", ecidr, sr.CIDR)
|
t.Errorf("Expected %v CIDR but %v returned", ecidr, sr.CIDR)
|
||||||
|
|
|
@ -17,32 +17,30 @@ limitations under the License.
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// IngressAnnotation has a method to parse annotations located in Ingress
|
||||||
// ErrMissingAnnotations is returned when the ingress rule
|
type IngressAnnotation interface {
|
||||||
// does not contains annotations related with rate limit
|
Parse(ing *extensions.Ingress) (interface{}, error)
|
||||||
ErrMissingAnnotations = errors.New("Ingress rule without annotations")
|
}
|
||||||
|
|
||||||
// ErrInvalidName ...
|
|
||||||
ErrInvalidName = errors.New("invalid annotation name")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ingAnnotations map[string]string
|
type ingAnnotations map[string]string
|
||||||
|
|
||||||
func (a ingAnnotations) parseBool(name string) (bool, error) {
|
func (a ingAnnotations) parseBool(name string) (bool, error) {
|
||||||
val, ok := a[name]
|
val, ok := a[name]
|
||||||
if ok {
|
if ok {
|
||||||
if b, err := strconv.ParseBool(val); err == nil {
|
b, err := strconv.ParseBool(val)
|
||||||
return b, nil
|
if err != nil {
|
||||||
|
return false, errors.NewInvalidAnnotationContent(name)
|
||||||
}
|
}
|
||||||
|
return b, nil
|
||||||
}
|
}
|
||||||
return false, ErrMissingAnnotations
|
return false, errors.ErrMissingAnnotations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ingAnnotations) parseString(name string) (string, error) {
|
func (a ingAnnotations) parseString(name string) (string, error) {
|
||||||
|
@ -50,7 +48,7 @@ func (a ingAnnotations) parseString(name string) (string, error) {
|
||||||
if ok {
|
if ok {
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
return "", ErrMissingAnnotations
|
return "", errors.ErrMissingAnnotations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ingAnnotations) parseInt(name string) (int, error) {
|
func (a ingAnnotations) parseInt(name string) (int, error) {
|
||||||
|
@ -58,45 +56,47 @@ func (a ingAnnotations) parseInt(name string) (int, error) {
|
||||||
if ok {
|
if ok {
|
||||||
i, err := strconv.Atoi(val)
|
i, err := strconv.Atoi(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("invalid annotations value: %v", err)
|
return 0, errors.NewInvalidAnnotationContent(name)
|
||||||
}
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
return 0, ErrMissingAnnotations
|
return 0, errors.ErrMissingAnnotations
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoolAnnotation ...
|
func checkAnnotation(name string, ing *extensions.Ingress) error {
|
||||||
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
|
if ing == nil || len(ing.GetAnnotations()) == 0 {
|
||||||
if ing == nil || ing.GetAnnotations() == nil {
|
return errors.ErrMissingAnnotations
|
||||||
return false, ErrMissingAnnotations
|
|
||||||
}
|
}
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return false, ErrInvalidName
|
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)
|
return ingAnnotations(ing.GetAnnotations()).parseBool(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringAnnotation ...
|
// GetStringAnnotation extracts a string from an Ingress annotation
|
||||||
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
|
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
|
||||||
if ing == nil || ing.GetAnnotations() == nil {
|
err := checkAnnotation(name, ing)
|
||||||
return "", ErrMissingAnnotations
|
if err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
if name == "" {
|
|
||||||
return "", ErrInvalidName
|
|
||||||
}
|
|
||||||
|
|
||||||
return ingAnnotations(ing.GetAnnotations()).parseString(name)
|
return ingAnnotations(ing.GetAnnotations()).parseString(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIntAnnotation ...
|
// GetIntAnnotation extracts an int from an Ingress annotation
|
||||||
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
|
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
|
||||||
if ing == nil || ing.GetAnnotations() == nil {
|
err := checkAnnotation(name, ing)
|
||||||
return 0, ErrMissingAnnotations
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
}
|
}
|
||||||
if name == "" {
|
|
||||||
return 0, ErrInvalidName
|
|
||||||
}
|
|
||||||
|
|
||||||
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
|
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,8 @@ func TestGetBoolAnnotation(t *testing.T) {
|
||||||
if u != test.exp {
|
if u != test.exp {
|
||||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, u)
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(data, test.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +112,8 @@ func TestGetStringAnnotation(t *testing.T) {
|
||||||
if s != test.exp {
|
if s != test.exp {
|
||||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(data, test.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,5 +154,7 @@ func TestGetIntAnnotation(t *testing.T) {
|
||||||
if s != test.exp {
|
if s != test.exp {
|
||||||
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
|
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(data, test.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -38,37 +38,38 @@ type Configuration struct {
|
||||||
BufferSize string `json:"bufferSize"`
|
BufferSize string `json:"bufferSize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to configure upstream check parameters
|
// rule used to configure upstream check parameters
|
||||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Configuration {
|
func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
if ing == nil || ing.GetAnnotations() == nil {
|
defBackend := a.backendResolver.GetDefaultBackend()
|
||||||
return &Configuration{
|
|
||||||
cfg.ProxyConnectTimeout,
|
|
||||||
cfg.ProxySendTimeout,
|
|
||||||
cfg.ProxyReadTimeout,
|
|
||||||
cfg.ProxyBufferSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ct, err := parser.GetIntAnnotation(connect, ing)
|
ct, err := parser.GetIntAnnotation(connect, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ct = cfg.ProxyConnectTimeout
|
ct = defBackend.ProxyConnectTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := parser.GetIntAnnotation(send, ing)
|
st, err := parser.GetIntAnnotation(send, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
st = cfg.ProxySendTimeout
|
st = defBackend.ProxySendTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
rt, err := parser.GetIntAnnotation(read, ing)
|
rt, err := parser.GetIntAnnotation(read, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rt = cfg.ProxyReadTimeout
|
rt = defBackend.ProxyReadTimeout
|
||||||
}
|
}
|
||||||
|
|
||||||
bs, err := parser.GetStringAnnotation(bufferSize, ing)
|
bs, err := parser.GetStringAnnotation(bufferSize, ing)
|
||||||
if err != nil || bs == "" {
|
if err != nil || bs == "" {
|
||||||
bs = cfg.ProxyBufferSize
|
bs = defBackend.ProxyBufferSize
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Configuration{ct, st, rt, bs}
|
return &Configuration{ct, st, rt, bs}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,14 @@ func buildIngress() *extensions.Ingress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIngressHealthCheck(t *testing.T) {
|
type mockBackend struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{UpstreamFailTimeout: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxy(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
|
|
||||||
data := map[string]string{}
|
data := map[string]string{}
|
||||||
|
@ -71,20 +78,24 @@ func TestIngressHealthCheck(t *testing.T) {
|
||||||
data[bufferSize] = "1k"
|
data[bufferSize] = "1k"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
cfg := defaults.Backend{UpstreamFailTimeout: 1}
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
|
if err != nil {
|
||||||
p := ParseAnnotations(cfg, ing)
|
t.Errorf("unexpected error parsing a valid")
|
||||||
|
}
|
||||||
|
p, ok := i.(*Configuration)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Configuration type")
|
||||||
|
}
|
||||||
if p.ConnectTimeout != 1 {
|
if p.ConnectTimeout != 1 {
|
||||||
t.Errorf("Expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
|
t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
|
||||||
}
|
}
|
||||||
if p.SendTimeout != 2 {
|
if p.SendTimeout != 2 {
|
||||||
t.Errorf("Expected 2 as send-timeout but returned %v", p.SendTimeout)
|
t.Errorf("expected 2 as send-timeout but returned %v", p.SendTimeout)
|
||||||
}
|
}
|
||||||
if p.ReadTimeout != 3 {
|
if p.ReadTimeout != 3 {
|
||||||
t.Errorf("Expected 3 as read-timeout but returned %v", p.ReadTimeout)
|
t.Errorf("expected 3 as read-timeout but returned %v", p.ReadTimeout)
|
||||||
}
|
}
|
||||||
if p.BufferSize != "1k" {
|
if p.BufferSize != "1k" {
|
||||||
t.Errorf("Expected 1k as buffer-size but returned %v", p.BufferSize)
|
t.Errorf("expected 1k as buffer-size but returned %v", p.BufferSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
package ratelimit
|
package ratelimit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
@ -37,11 +36,6 @@ const (
|
||||||
defSharedSize = 5
|
defSharedSize = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidRateLimit is returned when the annotation caontains invalid values
|
|
||||||
ErrInvalidRateLimit = errors.New("invalid rate limit value. Must be > 0")
|
|
||||||
)
|
|
||||||
|
|
||||||
// RateLimit returns rate limit configuration for an Ingress rule
|
// RateLimit returns rate limit configuration for an Ingress rule
|
||||||
// Is possible to limit the number of connections per IP address or
|
// Is possible to limit the number of connections per IP address or
|
||||||
// connections per second.
|
// connections per second.
|
||||||
|
@ -63,12 +57,17 @@ type Zone struct {
|
||||||
SharedSize int `json:"sharedSize"`
|
SharedSize int `json:"sharedSize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ratelimit struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new ratelimit annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return ratelimit{}
|
||||||
|
}
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to rewrite the defined paths
|
// rule used to rewrite the defined paths
|
||||||
func ParseAnnotations(ing *extensions.Ingress) (*RateLimit, error) {
|
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
if ing.GetAnnotations() == nil {
|
|
||||||
return &RateLimit{}, parser.ErrMissingAnnotations
|
|
||||||
}
|
|
||||||
|
|
||||||
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
|
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
|
||||||
conn, _ := parser.GetIntAnnotation(limitIP, ing)
|
conn, _ := parser.GetIntAnnotation(limitIP, ing)
|
||||||
|
@ -77,7 +76,7 @@ func ParseAnnotations(ing *extensions.Ingress) (*RateLimit, error) {
|
||||||
return &RateLimit{
|
return &RateLimit{
|
||||||
Connections: Zone{},
|
Connections: Zone{},
|
||||||
RPS: Zone{},
|
RPS: Zone{},
|
||||||
}, ErrInvalidRateLimit
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
|
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
|
||||||
|
|
|
@ -61,9 +61,9 @@ func buildIngress() *extensions.Ingress {
|
||||||
|
|
||||||
func TestWithoutAnnotations(t *testing.T) {
|
func TestWithoutAnnotations(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
_, err := ParseAnnotations(ing)
|
_, err := NewParser().Parse(ing)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Error("Expected error with ingress without annotations")
|
t.Error("unexpected error with ingress without annotations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,9 +75,9 @@ func TestBadRateLimiting(t *testing.T) {
|
||||||
data[limitRPS] = "0"
|
data[limitRPS] = "0"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
_, err := ParseAnnotations(ing)
|
_, err := NewParser().Parse(ing)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Errorf("Expected error with invalid limits (0)")
|
t.Errorf("unexpected error with invalid limits (0)")
|
||||||
}
|
}
|
||||||
|
|
||||||
data = map[string]string{}
|
data = map[string]string{}
|
||||||
|
@ -85,16 +85,18 @@ func TestBadRateLimiting(t *testing.T) {
|
||||||
data[limitRPS] = "100"
|
data[limitRPS] = "100"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
rateLimit, err := ParseAnnotations(ing)
|
i, err := NewParser().Parse(ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Uxpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
rateLimit, ok := i.(*RateLimit)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a RateLimit type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rateLimit.Connections.Limit != 5 {
|
if rateLimit.Connections.Limit != 5 {
|
||||||
t.Errorf("Expected 5 in limit by ip but %v was returend", rateLimit.Connections)
|
t.Errorf("expected 5 in limit by ip but %v was returend", rateLimit.Connections)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rateLimit.RPS.Limit != 100 {
|
if rateLimit.RPS.Limit != 100 {
|
||||||
t.Errorf("Expected 100 in limit by rps but %v was returend", rateLimit.RPS)
|
t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,10 @@ limitations under the License.
|
||||||
package rewrite
|
package rewrite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -42,19 +40,27 @@ type Redirect struct {
|
||||||
SSLRedirect bool `json:"sslRedirect"`
|
SSLRedirect bool `json:"sslRedirect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to rewrite the defined paths
|
// rule used to rewrite the defined paths
|
||||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*Redirect, error) {
|
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
if ing.GetAnnotations() == nil {
|
rt, err := parser.GetStringAnnotation(rewriteTo, ing)
|
||||||
return &Redirect{}, errors.New("no annotations present")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sslRe = cfg.SSLRedirect
|
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
|
||||||
}
|
}
|
||||||
|
|
||||||
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
|
||||||
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
|
||||||
return &Redirect{
|
return &Redirect{
|
||||||
Target: rt,
|
Target: rt,
|
||||||
|
|
|
@ -65,9 +65,17 @@ func buildIngress() *extensions.Ingress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockBackend struct {
|
||||||
|
redirect bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{SSLRedirect: m.redirect}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWithoutAnnotations(t *testing.T) {
|
func TestWithoutAnnotations(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
_, err := ParseAnnotations(defaults.Backend{}, ing)
|
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error with ingress without annotations")
|
t.Error("Expected error with ingress without annotations")
|
||||||
}
|
}
|
||||||
|
@ -80,11 +88,14 @@ func TestRedirect(t *testing.T) {
|
||||||
data[rewriteTo] = defRoute
|
data[rewriteTo] = defRoute
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
redirect, err := ParseAnnotations(defaults.Backend{}, ing)
|
i, err := NewParser(mockBackend{}).Parse(ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Uxpected error with ingress: %v", err)
|
t.Errorf("Unexpected error with ingress: %v", err)
|
||||||
|
}
|
||||||
|
redirect, ok := i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirect.Target != defRoute {
|
if redirect.Target != defRoute {
|
||||||
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
|
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
|
||||||
}
|
}
|
||||||
|
@ -93,13 +104,18 @@ func TestRedirect(t *testing.T) {
|
||||||
func TestSSLRedirect(t *testing.T) {
|
func TestSSLRedirect(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
|
|
||||||
cfg := defaults.Backend{SSLRedirect: true}
|
|
||||||
|
|
||||||
data := map[string]string{}
|
data := map[string]string{}
|
||||||
|
data[rewriteTo] = defRoute
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
redirect, _ := ParseAnnotations(cfg, ing)
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
if !redirect.SSLRedirect {
|
if !redirect.SSLRedirect {
|
||||||
t.Errorf("Expected true but returned false")
|
t.Errorf("Expected true but returned false")
|
||||||
|
@ -108,8 +124,11 @@ func TestSSLRedirect(t *testing.T) {
|
||||||
data[sslRedirect] = "false"
|
data[sslRedirect] = "false"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
redirect, _ = ParseAnnotations(cfg, ing)
|
i, _ = NewParser(mockBackend{false}).Parse(ing)
|
||||||
|
redirect, ok = i.(*Redirect)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a Redirect type")
|
||||||
|
}
|
||||||
if redirect.SSLRedirect {
|
if redirect.SSLRedirect {
|
||||||
t.Errorf("Expected false but returned true")
|
t.Errorf("Expected false but returned true")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,16 @@ const (
|
||||||
secureUpstream = "ingress.kubernetes.io/secure-backends"
|
secureUpstream = "ingress.kubernetes.io/secure-backends"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseAnnotations parses the annotations contained in the ingress
|
type su struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new secure upstream annotation parser
|
||||||
|
func NewParser() parser.IngressAnnotation {
|
||||||
|
return su{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the annotations contained in the ingress
|
||||||
// rule used to indicate if the upstream servers should use SSL
|
// rule used to indicate if the upstream servers should use SSL
|
||||||
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
|
func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
return parser.GetBoolAnnotation(secureUpstream, ing)
|
return parser.GetBoolAnnotation(secureUpstream, ing)
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ func TestAnnotations(t *testing.T) {
|
||||||
data[secureUpstream] = "true"
|
data[secureUpstream] = "true"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
|
|
||||||
_, err := ParseAnnotations(ing)
|
_, err := NewParser().Parse(ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("Expected error with ingress without annotations")
|
t.Error("Expected error with ingress without annotations")
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ func TestAnnotations(t *testing.T) {
|
||||||
|
|
||||||
func TestWithoutAnnotations(t *testing.T) {
|
func TestWithoutAnnotations(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
_, err := ParseAnnotations(ing)
|
_, err := NewParser().Parse(ing)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected error with ingress without annotations")
|
t.Error("Expected error with ingress without annotations")
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,28 +17,29 @@ limitations under the License.
|
||||||
package sslpassthrough
|
package sslpassthrough
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
passthrough = "ingress.kubernetes.io/ssl-passthrough"
|
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
|
// ParseAnnotations parses the annotations contained in the ingress
|
||||||
// rule used to indicate if is required to configure
|
// rule used to indicate if is required to configure
|
||||||
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (bool, error) {
|
func (a sslpt) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||||
|
|
||||||
if ing.GetAnnotations() == nil {
|
if ing.GetAnnotations() == nil {
|
||||||
return false, parser.ErrMissingAnnotations
|
return false, ing_errors.ErrMissingAnnotations
|
||||||
}
|
|
||||||
|
|
||||||
if len(ing.Spec.TLS) == 0 {
|
|
||||||
return false, fmt.Errorf("ingres rule %v/%v does not contains a TLS section", ing.Name, ing.Namespace)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parser.GetBoolAnnotation(passthrough, ing)
|
return parser.GetBoolAnnotation(passthrough, ing)
|
||||||
|
|
|
@ -22,8 +22,6 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
"k8s.io/kubernetes/pkg/util/intstr"
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildIngress() *extensions.Ingress {
|
func buildIngress() *extensions.Ingress {
|
||||||
|
@ -44,7 +42,7 @@ func buildIngress() *extensions.Ingress {
|
||||||
func TestParseAnnotations(t *testing.T) {
|
func TestParseAnnotations(t *testing.T) {
|
||||||
ing := buildIngress()
|
ing := buildIngress()
|
||||||
|
|
||||||
_, err := ParseAnnotations(defaults.Backend{}, ing)
|
_, err := NewParser().Parse(ing)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -53,9 +51,9 @@ func TestParseAnnotations(t *testing.T) {
|
||||||
data[passthrough] = "true"
|
data[passthrough] = "true"
|
||||||
ing.SetAnnotations(data)
|
ing.SetAnnotations(data)
|
||||||
// test ingress using the annotation without a TLS section
|
// test ingress using the annotation without a TLS section
|
||||||
val, err := ParseAnnotations(defaults.Backend{}, ing)
|
_, err = NewParser().Parse(ing)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Errorf("expected error parsing an invalid cidr")
|
t.Errorf("unexpected error parsing ingress with sslpassthrough")
|
||||||
}
|
}
|
||||||
|
|
||||||
// test with a valid host
|
// test with a valid host
|
||||||
|
@ -64,9 +62,13 @@ func TestParseAnnotations(t *testing.T) {
|
||||||
Hosts: []string{"foo.bar.com"},
|
Hosts: []string{"foo.bar.com"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
val, err = ParseAnnotations(defaults.Backend{}, ing)
|
i, err := NewParser().Parse(ing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected error parsing an invalid cidr")
|
t.Errorf("expected error parsing ingress with sslpassthrough")
|
||||||
|
}
|
||||||
|
val, ok := i.(bool)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected a bool type")
|
||||||
}
|
}
|
||||||
if !val {
|
if !val {
|
||||||
t.Errorf("expected true but false returned")
|
t.Errorf("expected true but false returned")
|
||||||
|
|
114
core/pkg/ingress/controller/annotations.go
Normal file
114
core/pkg/ingress/controller/annotations.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/cors"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/healthcheck"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/secureupstream"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/errors"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type extractorConfig interface {
|
||||||
|
resolver.AuthCertificate
|
||||||
|
resolver.DefaultBackend
|
||||||
|
resolver.Secret
|
||||||
|
}
|
||||||
|
|
||||||
|
type annotationExtractor struct {
|
||||||
|
annotations map[string]parser.IngressAnnotation
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
||||||
|
return annotationExtractor{
|
||||||
|
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),
|
||||||
|
"Proxy": proxy.NewParser(cfg),
|
||||||
|
"RateLimit": ratelimit.NewParser(),
|
||||||
|
"Redirect": rewrite.NewParser(cfg),
|
||||||
|
"SecureUpstream": secureupstream.NewParser(),
|
||||||
|
"SSLPassthrough": sslpassthrough.NewParser(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interface{} {
|
||||||
|
anns := make(map[string]interface{}, 0)
|
||||||
|
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 {
|
||||||
|
_, de := anns["Denied"]
|
||||||
|
if errors.IsLocationDenied(err) && !de {
|
||||||
|
anns["Denied"] = err
|
||||||
|
glog.Errorf("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !errors.IsMissingAnnotations(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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) bool {
|
||||||
|
val, _ := e.annotations[secureUpstream].Parse(ing)
|
||||||
|
return val.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
92
core/pkg/ingress/controller/annotations_test.go
Normal file
92
core/pkg/ingress/controller/annotations_test.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
"k8s.io/kubernetes/pkg/util/intstr"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockCfg struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockCfg) GetDefaultBackend() defaults.Backend {
|
||||||
|
return defaults.Backend{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockCfg) GetSecret(string) (*api.Secret, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m mockCfg) GetAuthCertificate(string) (*authtls.SSLCert, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnotationExtractor(t *testing.T) {
|
||||||
|
ec := newAnnotationExtractor(mockCfg{})
|
||||||
|
ing := buildIngress()
|
||||||
|
|
||||||
|
m := ec.Extract(ing)
|
||||||
|
// the map at least should contains HealthCheck and Proxy information (defaults)
|
||||||
|
if _, ok := m["HealthCheck"]; !ok {
|
||||||
|
t.Error("expected HealthCheck annotation")
|
||||||
|
}
|
||||||
|
if _, ok := m["Proxy"]; !ok {
|
||||||
|
t.Error("expected Proxy annotation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildIngress() *extensions.Ingress {
|
||||||
|
defaultBackend := extensions.IngressBackend{
|
||||||
|
ServiceName: "default-backend",
|
||||||
|
ServicePort: intstr.FromInt(80),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions.Ingress{
|
||||||
|
ObjectMeta: api.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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,18 +39,11 @@ import (
|
||||||
|
|
||||||
cache_store "k8s.io/ingress/core/pkg/cache"
|
cache_store "k8s.io/ingress/core/pkg/cache"
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/cors"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/healthcheck"
|
"k8s.io/ingress/core/pkg/ingress/annotations/healthcheck"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/secureupstream"
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/service"
|
"k8s.io/ingress/core/pkg/ingress/annotations/service"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough"
|
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||||
"k8s.io/ingress/core/pkg/ingress/status"
|
"k8s.io/ingress/core/pkg/ingress/status"
|
||||||
"k8s.io/ingress/core/pkg/k8s"
|
"k8s.io/ingress/core/pkg/k8s"
|
||||||
local_strings "k8s.io/ingress/core/pkg/strings"
|
local_strings "k8s.io/ingress/core/pkg/strings"
|
||||||
|
@ -90,6 +83,8 @@ type GenericController struct {
|
||||||
secrLister cache_store.StoreToSecretsLister
|
secrLister cache_store.StoreToSecretsLister
|
||||||
mapLister cache_store.StoreToConfigmapLister
|
mapLister cache_store.StoreToConfigmapLister
|
||||||
|
|
||||||
|
annotations annotationExtractor
|
||||||
|
|
||||||
recorder record.EventRecorder
|
recorder record.EventRecorder
|
||||||
|
|
||||||
syncQueue *task.Queue
|
syncQueue *task.Queue
|
||||||
|
@ -268,6 +263,8 @@ func newIngressController(config *Configuration) *GenericController {
|
||||||
IngressLister: ic.ingLister,
|
IngressLister: ic.ingLister,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ic.annotations = newAnnotationExtractor(ic)
|
||||||
|
|
||||||
return &ic
|
return &ic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,8 +286,13 @@ func (ic GenericController) IngressClass() string {
|
||||||
return ic.cfg.IngressClass
|
return ic.cfg.IngressClass
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSecret searchs for a secret in the local secrets Store
|
// GetDefaultBackend returns the default backend
|
||||||
func (ic *GenericController) getSecret(name string) (*api.Secret, error) {
|
func (ic GenericController) GetDefaultBackend() defaults.Backend {
|
||||||
|
return ic.cfg.Backend.BackendDefaults()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecret searchs 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)
|
s, exists, err := ic.secrLister.Store.GetByKey(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -551,53 +553,10 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
upstreams := ic.createUpstreams(ings)
|
upstreams := ic.createUpstreams(ings)
|
||||||
servers := ic.createServers(ings, upstreams)
|
servers := ic.createServers(ings, upstreams)
|
||||||
|
|
||||||
upsDefaults := ic.cfg.Backend.BackendDefaults()
|
|
||||||
|
|
||||||
for _, ingIf := range ings {
|
for _, ingIf := range ings {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
|
|
||||||
nginxAuth, err := auth.ParseAnnotations(ing, auth.DefAuthDirectory, ic.getSecret)
|
anns := ic.annotations.Extract(ing)
|
||||||
glog.V(5).Infof("auth annotation: %v", nginxAuth)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading authentication in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rl, err := ratelimit.ParseAnnotations(ing)
|
|
||||||
glog.V(5).Infof("rate limit annotation: %v", rl)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading rate limit annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
locRew, err := rewrite.ParseAnnotations(upsDefaults, ing)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error parsing rewrite annotations for Ingress rule %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wl, err := ipwhitelist.ParseAnnotations(upsDefaults, ing)
|
|
||||||
glog.V(5).Infof("white list annotation: %v", wl)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading white list annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
eCORS, err := cors.ParseAnnotations(ing)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading CORS annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ra, err := authreq.ParseAnnotations(ing)
|
|
||||||
glog.V(5).Infof("auth request annotation: %v", ra)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading auth request annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prx := proxy.ParseAnnotations(upsDefaults, ing)
|
|
||||||
glog.V(5).Infof("proxy timeouts annotation: %v", prx)
|
|
||||||
|
|
||||||
certAuth, err := authtls.ParseAnnotations(ing, ic.getAuthCertificate)
|
|
||||||
glog.V(5).Infof("auth request annotation: %v", certAuth)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading certificate auth annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range ing.Spec.Rules {
|
for _, rule := range ing.Spec.Rules {
|
||||||
host := rule.Host
|
host := rule.Host
|
||||||
|
@ -664,34 +623,21 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
glog.V(3).Infof("replacing ingress rule %v/%v location %v upstream %v (%v)", ing.Namespace, ing.Name, loc.Path, ups.Name, loc.Backend)
|
glog.V(3).Infof("replacing ingress rule %v/%v location %v upstream %v (%v)", ing.Namespace, ing.Name, loc.Path, ups.Name, loc.Backend)
|
||||||
loc.Backend = ups.Name
|
loc.Backend = ups.Name
|
||||||
loc.IsDefBackend = false
|
loc.IsDefBackend = false
|
||||||
loc.BasicDigestAuth = *nginxAuth
|
|
||||||
loc.RateLimit = *rl
|
|
||||||
loc.Redirect = *locRew
|
|
||||||
loc.Whitelist = *wl
|
|
||||||
loc.Backend = ups.Name
|
loc.Backend = ups.Name
|
||||||
loc.EnableCORS = eCORS
|
mergeLocationAnnotations(loc, anns)
|
||||||
loc.ExternalAuth = ra
|
|
||||||
loc.Proxy = *prx
|
|
||||||
loc.CertificateAuth = *certAuth
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// is a new location
|
// is a new location
|
||||||
if addLoc {
|
if addLoc {
|
||||||
glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream %v", nginxPath, ing.Namespace, ing.Name, ups.Name)
|
glog.V(3).Infof("adding location %v in ingress rule %v/%v upstream %v", nginxPath, ing.Namespace, ing.Name, ups.Name)
|
||||||
server.Locations = append(server.Locations, &ingress.Location{
|
loc := &ingress.Location{
|
||||||
Path: nginxPath,
|
Path: nginxPath,
|
||||||
Backend: ups.Name,
|
Backend: ups.Name,
|
||||||
IsDefBackend: false,
|
IsDefBackend: false,
|
||||||
BasicDigestAuth: *nginxAuth,
|
}
|
||||||
RateLimit: *rl,
|
mergeLocationAnnotations(loc, anns)
|
||||||
Redirect: *locRew,
|
server.Locations = append(server.Locations, loc)
|
||||||
Whitelist: *wl,
|
|
||||||
EnableCORS: eCORS,
|
|
||||||
ExternalAuth: ra,
|
|
||||||
Proxy: *prx,
|
|
||||||
CertificateAuth: *certAuth,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -721,7 +667,8 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
||||||
return aUpstreams, aServers
|
return aUpstreams, aServers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *GenericController) getAuthCertificate(secretName string) (*authtls.SSLCert, error) {
|
// GetAuthCertificate ...
|
||||||
|
func (ic GenericController) GetAuthCertificate(secretName string) (*authtls.SSLCert, error) {
|
||||||
bc, exists := ic.sslCertTracker.Get(secretName)
|
bc, exists := ic.sslCertTracker.Get(secretName)
|
||||||
if !exists {
|
if !exists {
|
||||||
return &authtls.SSLCert{}, fmt.Errorf("secret %v does not exists", secretName)
|
return &authtls.SSLCert{}, fmt.Errorf("secret %v does not exists", secretName)
|
||||||
|
@ -741,16 +688,11 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
||||||
upstreams := make(map[string]*ingress.Backend)
|
upstreams := make(map[string]*ingress.Backend)
|
||||||
upstreams[defUpstreamName] = ic.getDefaultUpstream()
|
upstreams[defUpstreamName] = ic.getDefaultUpstream()
|
||||||
|
|
||||||
upsDefaults := ic.cfg.Backend.BackendDefaults()
|
|
||||||
for _, ingIf := range data {
|
for _, ingIf := range data {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
|
|
||||||
secUpstream, err := secureupstream.ParseAnnotations(ing)
|
secUpstream := ic.annotations.SecureUpstream(ing)
|
||||||
if err != nil {
|
hz := ic.annotations.HealthCheck(ing)
|
||||||
glog.V(5).Infof("error reading secure upstream in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hz := healthcheck.ParseAnnotations(upsDefaults, ing)
|
|
||||||
|
|
||||||
var defBackend string
|
var defBackend string
|
||||||
if ing.Spec.Backend != nil {
|
if ing.Spec.Backend != nil {
|
||||||
|
@ -843,9 +785,16 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
||||||
|
|
||||||
func (ic *GenericController) createServers(data []interface{}, upstreams map[string]*ingress.Backend) map[string]*ingress.Server {
|
func (ic *GenericController) createServers(data []interface{}, upstreams map[string]*ingress.Backend) map[string]*ingress.Server {
|
||||||
servers := make(map[string]*ingress.Server)
|
servers := make(map[string]*ingress.Server)
|
||||||
ngxProxy := *proxy.ParseAnnotations(ic.cfg.Backend.BackendDefaults(), nil)
|
|
||||||
|
|
||||||
upsDefaults := ic.cfg.Backend.BackendDefaults()
|
bdef := ic.GetDefaultBackend()
|
||||||
|
ngxProxy := proxy.Configuration{
|
||||||
|
ConnectTimeout: bdef.ProxyConnectTimeout,
|
||||||
|
SendTimeout: bdef.ProxySendTimeout,
|
||||||
|
ReadTimeout: bdef.ProxyReadTimeout,
|
||||||
|
BufferSize: bdef.ProxyBufferSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
dun := ic.getDefaultUpstream().Name
|
||||||
|
|
||||||
// default server
|
// default server
|
||||||
servers[defServerName] = &ingress.Server{
|
servers[defServerName] = &ingress.Server{
|
||||||
|
@ -854,7 +803,7 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
{
|
{
|
||||||
Path: rootLocation,
|
Path: rootLocation,
|
||||||
IsDefBackend: true,
|
IsDefBackend: true,
|
||||||
Backend: ic.getDefaultUpstream().Name,
|
Backend: dun,
|
||||||
Proxy: ngxProxy,
|
Proxy: ngxProxy,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -863,10 +812,7 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
for _, ingIf := range data {
|
for _, ingIf := range data {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
// check if ssl passthrough is configured
|
// check if ssl passthrough is configured
|
||||||
sslpt, err := sslpassthrough.ParseAnnotations(upsDefaults, ing)
|
sslpt := ic.annotations.SSLPassthrough(ing)
|
||||||
if err != nil {
|
|
||||||
glog.V(5).Infof("error reading ssl passthrough annotation in Ingress %v/%v: %v", ing.GetNamespace(), ing.GetName(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rule := range ing.Spec.Rules {
|
for _, rule := range ing.Spec.Rules {
|
||||||
host := rule.Host
|
host := rule.Host
|
||||||
|
@ -883,7 +829,7 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
{
|
{
|
||||||
Path: rootLocation,
|
Path: rootLocation,
|
||||||
IsDefBackend: true,
|
IsDefBackend: true,
|
||||||
Backend: ic.getDefaultUpstream().Name,
|
Backend: dun,
|
||||||
Proxy: ngxProxy,
|
Proxy: ngxProxy,
|
||||||
},
|
},
|
||||||
}, SSLPassthrough: sslpt}
|
}, SSLPassthrough: sslpt}
|
||||||
|
|
|
@ -19,6 +19,9 @@ package controller
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress"
|
"k8s.io/ingress/core/pkg/ingress"
|
||||||
|
@ -93,3 +96,16 @@ func IsValidClass(ing *extensions.Ingress, class string) bool {
|
||||||
|
|
||||||
return cc == class
|
return cc == class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const denied = "Denied"
|
||||||
|
|
||||||
|
func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}) {
|
||||||
|
if _, ok := anns[denied]; ok {
|
||||||
|
loc.Denied = anns[denied].(error)
|
||||||
|
}
|
||||||
|
delete(anns, denied)
|
||||||
|
err := mergo.Map(loc, anns)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error merging extracted annotations in location type: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
89
core/pkg/ingress/errors/errors.go
Normal file
89
core/pkg/ingress/errors/errors.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
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 errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrMissingAnnotations the ingress rule does not contains annotations
|
||||||
|
// This is an error only when annotations are being parsed
|
||||||
|
ErrMissingAnnotations = errors.New("ingress rule without annotations")
|
||||||
|
|
||||||
|
// ErrInvalidAnnotationName the ingress rule does contains an invalid
|
||||||
|
// annotation name
|
||||||
|
ErrInvalidAnnotationName = errors.New("invalid annotation name")
|
||||||
|
|
||||||
|
// ErrInvalidAnnotationContent the ingress rule annotation content is
|
||||||
|
// invalid
|
||||||
|
ErrInvalidAnnotationContent = errors.New("invalid annotation content")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewInvalidAnnotationContent returns a new InvalidContent error
|
||||||
|
func NewInvalidAnnotationContent(name string) error {
|
||||||
|
return InvalidContent{
|
||||||
|
Name: fmt.Sprintf("the annotation %v does not contains a valid value", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocationDenied returns a new LocationDenied error
|
||||||
|
func NewLocationDenied(reason string) error {
|
||||||
|
return LocationDenied{
|
||||||
|
Reason: errors.Errorf("Location denied, reason: %v", reason),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidContent error
|
||||||
|
type InvalidContent struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e InvalidContent) Error() string {
|
||||||
|
return e.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocationDenied error
|
||||||
|
type LocationDenied struct {
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e LocationDenied) Error() string {
|
||||||
|
return e.Reason.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLocationDenied checks if the err is an error which
|
||||||
|
// indicates a location should return HTTP code 503
|
||||||
|
func IsLocationDenied(e error) bool {
|
||||||
|
_, ok := e.(LocationDenied)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMissingAnnotations checks if the err is an error which
|
||||||
|
// indicates the ingress does not contains annotations
|
||||||
|
func IsMissingAnnotations(e error) bool {
|
||||||
|
return e == ErrMissingAnnotations
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInvalidContent checks if the err is an error which
|
||||||
|
// indicates an annotations value is not valid
|
||||||
|
func IsInvalidContent(e error) bool {
|
||||||
|
_, ok := e.(InvalidContent)
|
||||||
|
return ok
|
||||||
|
}
|
52
core/pkg/ingress/errors/errors_test.go
Normal file
52
core/pkg/ingress/errors/errors_test.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
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 errors
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsLocationDenied(t *testing.T) {
|
||||||
|
err := NewLocationDenied("demo")
|
||||||
|
if !IsLocationDenied(err) {
|
||||||
|
t.Error("expected true")
|
||||||
|
}
|
||||||
|
if IsLocationDenied(nil) {
|
||||||
|
t.Error("expected false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsMissingAnnotations(t *testing.T) {
|
||||||
|
if !IsMissingAnnotations(ErrMissingAnnotations) {
|
||||||
|
t.Error("expected true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidContent(t *testing.T) {
|
||||||
|
if IsInvalidContent(ErrMissingAnnotations) {
|
||||||
|
t.Error("expected false")
|
||||||
|
}
|
||||||
|
err := NewInvalidAnnotationContent("demo")
|
||||||
|
if !IsInvalidContent(err) {
|
||||||
|
t.Error("expected true")
|
||||||
|
}
|
||||||
|
if IsInvalidContent(nil) {
|
||||||
|
t.Error("expected false")
|
||||||
|
}
|
||||||
|
err = NewLocationDenied("demo")
|
||||||
|
if IsInvalidContent(err) {
|
||||||
|
t.Error("expected false")
|
||||||
|
}
|
||||||
|
}
|
43
core/pkg/ingress/resolver/main.go
Normal file
43
core/pkg/ingress/resolver/main.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
|
||||||
|
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||||
|
"k8s.io/ingress/core/pkg/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 searchs for secrets contenating
|
||||||
|
// the namespace and name using a the character /
|
||||||
|
type Secret interface {
|
||||||
|
GetSecret(string) (*api.Secret, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthCertificate has a method that searchs for a secret
|
||||||
|
// that contains a SSL certificate.
|
||||||
|
// The secret must contain 3 keys named:
|
||||||
|
type AuthCertificate interface {
|
||||||
|
GetAuthCertificate(string) (*authtls.SSLCert, error)
|
||||||
|
}
|
|
@ -203,6 +203,9 @@ type Location struct {
|
||||||
// an Ingress rule.
|
// an Ingress rule.
|
||||||
// +optional
|
// +optional
|
||||||
BasicDigestAuth auth.BasicDigest `json:"basicDigestAuth,omitempty"`
|
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
|
||||||
// EnableCORS indicates if path must support CORS
|
// EnableCORS indicates if path must support CORS
|
||||||
// +optional
|
// +optional
|
||||||
EnableCORS bool `json:"enableCors,omitempty"`
|
EnableCORS bool `json:"enableCors,omitempty"`
|
||||||
|
|
|
@ -18,11 +18,12 @@ package task
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sr int = 0
|
var sr uint32
|
||||||
|
|
||||||
type mockEnqueueObj struct {
|
type mockEnqueueObj struct {
|
||||||
k string
|
k string
|
||||||
|
@ -31,7 +32,7 @@ type mockEnqueueObj struct {
|
||||||
|
|
||||||
func mockSynFn(interface{}) error {
|
func mockSynFn(interface{}) error {
|
||||||
// sr will be plus one times after enqueue
|
// sr will be plus one times after enqueue
|
||||||
sr++
|
atomic.AddUint32(&sr, 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ func TestShutdown(t *testing.T) {
|
||||||
|
|
||||||
func TestEnqueueSuccess(t *testing.T) {
|
func TestEnqueueSuccess(t *testing.T) {
|
||||||
// initialize result
|
// initialize result
|
||||||
sr = 0
|
atomic.StoreUint32(&sr, 0)
|
||||||
q := NewCustomTaskQueue(mockSynFn, mockKeyFn)
|
q := NewCustomTaskQueue(mockSynFn, mockKeyFn)
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
// run queue
|
// run queue
|
||||||
|
@ -73,7 +74,7 @@ func TestEnqueueSuccess(t *testing.T) {
|
||||||
q.Enqueue(mo)
|
q.Enqueue(mo)
|
||||||
// wait for 'mockSynFn'
|
// wait for 'mockSynFn'
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
if sr != 1 {
|
if atomic.LoadUint32(&sr) != 1 {
|
||||||
t.Errorf("sr should be 1, but is %d", sr)
|
t.Errorf("sr should be 1, but is %d", sr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ func TestEnqueueSuccess(t *testing.T) {
|
||||||
|
|
||||||
func TestEnqueueFailed(t *testing.T) {
|
func TestEnqueueFailed(t *testing.T) {
|
||||||
// initialize result
|
// initialize result
|
||||||
sr = 0
|
atomic.StoreUint32(&sr, 0)
|
||||||
q := NewCustomTaskQueue(mockSynFn, mockKeyFn)
|
q := NewCustomTaskQueue(mockSynFn, mockKeyFn)
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
// run queue
|
// run queue
|
||||||
|
@ -102,14 +103,14 @@ func TestEnqueueFailed(t *testing.T) {
|
||||||
// wait for 'mockSynFn'
|
// wait for 'mockSynFn'
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
// queue is shutdown, so mockSynFn should not be executed, so the result should be 0
|
// queue is shutdown, so mockSynFn should not be executed, so the result should be 0
|
||||||
if sr != 0 {
|
if atomic.LoadUint32(&sr) != 0 {
|
||||||
t.Errorf("queue has been shutdown, so sr should be 0, but is %d", sr)
|
t.Errorf("queue has been shutdown, so sr should be 0, but is %d", sr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEnqueueKeyError(t *testing.T) {
|
func TestEnqueueKeyError(t *testing.T) {
|
||||||
// initialize result
|
// initialize result
|
||||||
sr = 0
|
atomic.StoreUint32(&sr, 0)
|
||||||
q := NewCustomTaskQueue(mockSynFn, mockErrorKeyFn)
|
q := NewCustomTaskQueue(mockSynFn, mockErrorKeyFn)
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
// run queue
|
// run queue
|
||||||
|
@ -124,7 +125,7 @@ func TestEnqueueKeyError(t *testing.T) {
|
||||||
// wait for 'mockSynFn'
|
// wait for 'mockSynFn'
|
||||||
time.Sleep(time.Millisecond * 10)
|
time.Sleep(time.Millisecond * 10)
|
||||||
// key error, so the result should be 0
|
// key error, so the result should be 0
|
||||||
if sr != 0 {
|
if atomic.LoadUint32(&sr) != 0 {
|
||||||
t.Errorf("error occurs while get key, so sr should be 0, but is %d", sr)
|
t.Errorf("error occurs while get key, so sr should be 0, but is %d", sr)
|
||||||
}
|
}
|
||||||
// shutdown queue before exit
|
// shutdown queue before exit
|
||||||
|
|
Loading…
Reference in a new issue