Deny location mapping in case of specific errors

This commit is contained in:
Manuel de Brito Fontes 2016-12-29 17:02:06 -03:00
parent c49b03facc
commit 597a0e691a
34 changed files with 968 additions and 333 deletions

View file

@ -17,44 +17,31 @@ limitations under the License.
package auth
import (
"errors"
"fmt"
"io/ioutil"
"os"
"regexp"
"github.com/pkg/errors"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"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 (
authType = "ingress.kubernetes.io/auth-type"
authSecret = "ingress.kubernetes.io/auth-secret"
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 (
authTypeRegex = regexp.MustCompile(`basic|digest`)
// ErrInvalidAuthType is return in case of unsupported authentication type
ErrInvalidAuthType = errors.New("invalid authentication type")
// 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")
// AuthDirectory default directory used to store files
// to authenticate request
AuthDirectory = "/etc/ingress-controller/auth"
)
// BasicDigest returns authentication configuration for an Ingress rule
@ -65,40 +52,53 @@ type BasicDigest struct {
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
// and generated an htpasswd compatible file to be used as source
// during the authentication process
func ParseAnnotations(ing *extensions.Ingress, authDir string, fn func(string) (*api.Secret, error)) (*BasicDigest, error) {
if ing.GetAnnotations() == nil {
return &BasicDigest{}, parser.ErrMissingAnnotations
}
func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
at, err := parser.GetStringAnnotation(authType, ing)
if err != nil {
return &BasicDigest{}, err
return nil, err
}
if !authTypeRegex.MatchString(at) {
return &BasicDigest{}, ErrInvalidAuthType
return nil, ing_errors.NewLocationDenied("invalid authentication type")
}
s, err := parser.GetStringAnnotation(authSecret, ing)
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 {
return &BasicDigest{}, err
return nil, ing_errors.LocationDenied{
Reason: errors.Wrapf(err, "unexpected error reading secret %v", name),
}
}
realm, _ := parser.GetStringAnnotation(authRealm, ing)
passFile := fmt.Sprintf("%v/%v-%v.passwd", authDir, ing.GetNamespace(), ing.GetName())
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
err = dumpSecret(passFile, secret)
if err != nil {
return &BasicDigest{}, err
return nil, err
}
return &BasicDigest{
@ -114,9 +114,18 @@ func ParseAnnotations(ing *extensions.Ingress, authDir string, fn func(string) (
func dumpSecret(filename string, secret *api.Secret) error {
val, ok := secret.Data["auth"]
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
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
}

View file

@ -23,6 +23,7 @@ import (
"testing"
"time"
"github.com/pkg/errors"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"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{
ObjectMeta: api.ObjectMeta{
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")},
}, nil
}
func TestIngressWithoutAuth(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(ing, "", mockSecret)
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
_, err := NewParser(dir, mockSecret{}).Parse(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
@ -92,11 +103,14 @@ func TestIngressAuth(t *testing.T) {
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
auth, err := ParseAnnotations(ing, dir, mockSecret)
i, err := NewParser(dir, mockSecret{}).Parse(ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
auth, ok := i.(*BasicDigest)
if !ok {
t.Errorf("expected a BasicDigest type")
}
if auth.Type != "basic" {
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) {
dir, err := ioutil.TempDir("", fmt.Sprintf("%v", time.Now().Unix()))
if err != nil {
@ -119,7 +151,7 @@ func dummySecretContent(t *testing.T) (string, string, *api.Secret) {
t.Error(err)
}
defer tmpfile.Close()
s, _ := mockSecret("demo")
s, _ := mockSecret{}.GetSecret("default/demo-secret")
return tmpfile.Name(), dir, s
}

View file

@ -17,13 +17,13 @@ limitations under the License.
package authreq
import (
"fmt"
"net/url"
"strings"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
)
const (
@ -57,44 +57,49 @@ func validMethod(method string) bool {
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
// rule used to use an external URL as source for authentication
func ParseAnnotations(ing *extensions.Ingress) (External, error) {
if ing.GetAnnotations() == nil {
return External{}, parser.ErrMissingAnnotations
}
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
str, err := parser.GetStringAnnotation(authURL, ing)
if err != nil {
return External{}, err
return nil, err
}
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)
if err != nil {
return External{}, err
return nil, err
}
if ur.Scheme == "" {
return External{}, fmt.Errorf("url scheme is empty")
return nil, ing_errors.NewLocationDenied("url scheme is empty")
}
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, "..") {
return External{}, fmt.Errorf("invalid url host")
return nil, ing_errors.NewLocationDenied("invalid url host")
}
m, _ := parser.GetStringAnnotation(authMethod, ing)
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)
return External{
return &External{
URL: str,
Method: m,
SendBody: sb,

View file

@ -87,15 +87,17 @@ func TestAnnotations(t *testing.T) {
data[authBody] = fmt.Sprintf("%v", test.sendBody)
data[authMethod] = fmt.Sprintf("%v", test.method)
u, err := ParseAnnotations(ing)
i, err := NewParser().Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", test.title)
}
continue
}
u, ok := i.(*External)
if !ok {
t.Errorf("%v: expected an External type", test.title)
}
if u.URL != test.url {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
}

View file

@ -17,11 +17,10 @@ limitations under the License.
package authtls
import (
"fmt"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress/core/pkg/ingress/errors"
"k8s.io/ingress/core/pkg/k8s"
)
@ -30,6 +29,13 @@ const (
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
type SSLCert struct {
Secret string `json:"secret"`
@ -39,27 +45,31 @@ type SSLCert struct {
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
// rule used to use an external URL as source for authentication
func ParseAnnotations(ing *extensions.Ingress,
fn func(secret string) (*SSLCert, error)) (*SSLCert, error) {
if ing.GetAnnotations() == nil {
return &SSLCert{}, parser.ErrMissingAnnotations
}
func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
if err != nil {
return &SSLCert{}, err
return nil, err
}
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)
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)
}

View file

@ -23,11 +23,19 @@ import (
)
const (
cors = "ingress.kubernetes.io/enable-cors"
annotation = "ingress.kubernetes.io/enable-cors"
)
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if the location/s should allows CORS
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
return parser.GetBoolAnnotation(cors, ing)
type cors struct {
}
// NewParser creates a new CORS annotation parser
func NewParser() parser.IngressAnnotation {
return cors{}
}
// Parse parses the annotations contained in the ingress
// rule used to indicate if the location/s should allows CORS
func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation(annotation, ing)
}

View file

@ -20,7 +20,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver"
)
const (
@ -35,22 +35,32 @@ type Upstream struct {
FailTimeout int `json:"failTimeout"`
}
type healthCheck struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new health check annotation parser
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
return healthCheck{br}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure upstream check parameters
func 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 {
return &Upstream{cfg.UpstreamMaxFails, cfg.UpstreamFailTimeout}
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
}
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
if err != nil {
mf = cfg.UpstreamMaxFails
mf = defBackend.UpstreamMaxFails
}
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
if err != nil {
ft = cfg.UpstreamFailTimeout
ft = defBackend.UpstreamFailTimeout
}
return &Upstream{mf, ft}
return &Upstream{mf, ft}, nil
}

View file

@ -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) {
ing := buildIngress()
@ -68,15 +75,17 @@ func TestIngressHealthCheck(t *testing.T) {
data[upsMaxFails] = "2"
ing.SetAnnotations(data)
cfg := defaults.Backend{UpstreamFailTimeout: 1}
nginxHz := ParseAnnotations(cfg, ing)
hzi, _ := NewParser(mockBackend{}).Parse(ing)
nginxHz, ok := hzi.(*Upstream)
if !ok {
t.Errorf("expected a Upstream type")
}
if nginxHz.MaxFails != 2 {
t.Errorf("Expected 2 as max-fails but returned %v", nginxHz.MaxFails)
t.Errorf("expected 2 as max-fails but returned %v", nginxHz.MaxFails)
}
if nginxHz.FailTimeout != 1 {
t.Errorf("Expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
t.Errorf("expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
}
}

View file

@ -17,51 +17,55 @@ limitations under the License.
package ipwhitelist
import (
"errors"
"sort"
"strings"
"github.com/pkg/errors"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/net/sets"
"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 (
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
type SourceRange struct {
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
// rule used to limit access to certain client addresses or networks.
// Multiple ranges can specified using commas as separator
// e.g. `18.0.0.0/8,56.0.0.0/8`
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*SourceRange, error) {
sort.Strings(cfg.WhitelistSourceRange)
if ing.GetAnnotations() == nil {
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, parser.ErrMissingAnnotations
}
func (a ipwhitelist) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
sort.Strings(defBackend.WhitelistSourceRange)
val, err := parser.GetStringAnnotation(whitelist, ing)
if err != nil {
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, err
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, err
}
values := strings.Split(val, ",")
ipnets, err := sets.ParseIPNets(values...)
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{}

View file

@ -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) {
// TODO: convert test cases to tables
ing := buildIngress()
@ -77,38 +84,56 @@ func TestParseAnnotations(t *testing.T) {
CIDR: enet,
}
sr, err := ParseAnnotations(defaults.Backend{}, ing)
p := NewParser(mockBackend{})
i, err := p.Parse(ing)
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) {
t.Errorf("Expected %v but returned %s", sr, expected)
t.Errorf("expected %v but returned %s", sr, expected)
}
data[whitelist] = "www"
_, err = ParseAnnotations(defaults.Backend{}, ing)
_, err = p.Parse(ing)
if err == nil {
t.Errorf("Expected error parsing an invalid cidr")
t.Errorf("expected error parsing an invalid cidr")
}
delete(data, whitelist)
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 {
t.Errorf("Expected error parsing an invalid cidr")
t.Errorf("expected error parsing an invalid cidr")
}
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{}) {
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"
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"}
if !strsEquals(sr.CIDR, ecidr) {
t.Errorf("Expected %v CIDR but %v returned", ecidr, sr.CIDR)

View file

@ -17,32 +17,30 @@ limitations under the License.
package parser
import (
"errors"
"fmt"
"strconv"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/errors"
)
var (
// ErrMissingAnnotations is returned when the ingress rule
// does not contains annotations related with rate limit
ErrMissingAnnotations = errors.New("Ingress rule without annotations")
// ErrInvalidName ...
ErrInvalidName = errors.New("invalid annotation name")
)
// IngressAnnotation has a method to parse annotations located in Ingress
type IngressAnnotation interface {
Parse(ing *extensions.Ingress) (interface{}, error)
}
type ingAnnotations map[string]string
func (a ingAnnotations) parseBool(name string) (bool, error) {
val, ok := a[name]
if ok {
if b, err := strconv.ParseBool(val); err == nil {
return b, nil
b, err := strconv.ParseBool(val)
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) {
@ -50,7 +48,7 @@ func (a ingAnnotations) parseString(name string) (string, error) {
if ok {
return val, nil
}
return "", ErrMissingAnnotations
return "", errors.ErrMissingAnnotations
}
func (a ingAnnotations) parseInt(name string) (int, error) {
@ -58,45 +56,47 @@ func (a ingAnnotations) parseInt(name string) (int, error) {
if ok {
i, err := strconv.Atoi(val)
if err != nil {
return 0, fmt.Errorf("invalid annotations value: %v", err)
return 0, errors.NewInvalidAnnotationContent(name)
}
return i, nil
}
return 0, ErrMissingAnnotations
return 0, errors.ErrMissingAnnotations
}
// GetBoolAnnotation ...
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
if ing == nil || ing.GetAnnotations() == nil {
return false, ErrMissingAnnotations
func checkAnnotation(name string, ing *extensions.Ingress) error {
if ing == nil || len(ing.GetAnnotations()) == 0 {
return errors.ErrMissingAnnotations
}
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)
}
// GetStringAnnotation ...
// GetStringAnnotation extracts a string from an Ingress annotation
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
if ing == nil || ing.GetAnnotations() == nil {
return "", ErrMissingAnnotations
err := checkAnnotation(name, ing)
if err != nil {
return "", err
}
if name == "" {
return "", ErrInvalidName
}
return ingAnnotations(ing.GetAnnotations()).parseString(name)
}
// GetIntAnnotation ...
// GetIntAnnotation extracts an int from an Ingress annotation
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
if ing == nil || ing.GetAnnotations() == nil {
return 0, ErrMissingAnnotations
err := checkAnnotation(name, ing)
if err != nil {
return 0, err
}
if name == "" {
return 0, ErrInvalidName
}
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
}

View file

@ -70,6 +70,8 @@ func TestGetBoolAnnotation(t *testing.T) {
if u != test.exp {
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 {
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 {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
}
delete(data, test.field)
}
}

View file

@ -20,7 +20,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver"
)
const (
@ -38,37 +38,38 @@ type Configuration struct {
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
// rule used to configure upstream check parameters
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Configuration {
if ing == nil || ing.GetAnnotations() == nil {
return &Configuration{
cfg.ProxyConnectTimeout,
cfg.ProxySendTimeout,
cfg.ProxyReadTimeout,
cfg.ProxyBufferSize,
}
}
func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
ct, err := parser.GetIntAnnotation(connect, ing)
if err != nil {
ct = cfg.ProxyConnectTimeout
ct = defBackend.ProxyConnectTimeout
}
st, err := parser.GetIntAnnotation(send, ing)
if err != nil {
st = cfg.ProxySendTimeout
st = defBackend.ProxySendTimeout
}
rt, err := parser.GetIntAnnotation(read, ing)
if err != nil {
rt = cfg.ProxyReadTimeout
rt = defBackend.ProxyReadTimeout
}
bs, err := parser.GetStringAnnotation(bufferSize, ing)
if err != nil || bs == "" {
bs = cfg.ProxyBufferSize
bs = defBackend.ProxyBufferSize
}
return &Configuration{ct, st, rt, bs}
return &Configuration{ct, st, rt, bs}, nil
}

View file

@ -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()
data := map[string]string{}
@ -71,20 +78,24 @@ func TestIngressHealthCheck(t *testing.T) {
data[bufferSize] = "1k"
ing.SetAnnotations(data)
cfg := defaults.Backend{UpstreamFailTimeout: 1}
p := ParseAnnotations(cfg, ing)
i, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing a valid")
}
p, ok := i.(*Configuration)
if !ok {
t.Errorf("expected a Configuration type")
}
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 {
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 {
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" {
t.Errorf("Expected 1k as buffer-size but returned %v", p.BufferSize)
t.Errorf("expected 1k as buffer-size but returned %v", p.BufferSize)
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
package ratelimit
import (
"errors"
"fmt"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -37,11 +36,6 @@ const (
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
// Is possible to limit the number of connections per IP address or
// connections per second.
@ -63,12 +57,17 @@ type Zone struct {
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
// rule used to rewrite the defined paths
func ParseAnnotations(ing *extensions.Ingress) (*RateLimit, error) {
if ing.GetAnnotations() == nil {
return &RateLimit{}, parser.ErrMissingAnnotations
}
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
conn, _ := parser.GetIntAnnotation(limitIP, ing)
@ -77,7 +76,7 @@ func ParseAnnotations(ing *extensions.Ingress) (*RateLimit, error) {
return &RateLimit{
Connections: Zone{},
RPS: Zone{},
}, ErrInvalidRateLimit
}, nil
}
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())

View file

@ -61,9 +61,9 @@ func buildIngress() *extensions.Ingress {
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
_, err := NewParser().Parse(ing)
if err != nil {
t.Error("unexpected error with ingress without annotations")
}
}
@ -75,9 +75,9 @@ func TestBadRateLimiting(t *testing.T) {
data[limitRPS] = "0"
ing.SetAnnotations(data)
_, err := ParseAnnotations(ing)
if err == nil {
t.Errorf("Expected error with invalid limits (0)")
_, err := NewParser().Parse(ing)
if err != nil {
t.Errorf("unexpected error with invalid limits (0)")
}
data = map[string]string{}
@ -85,16 +85,18 @@ func TestBadRateLimiting(t *testing.T) {
data[limitRPS] = "100"
ing.SetAnnotations(data)
rateLimit, err := ParseAnnotations(ing)
i, err := NewParser().Parse(ing)
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 {
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 {
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)
}
}

View file

@ -17,12 +17,10 @@ limitations under the License.
package rewrite
import (
"errors"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver"
)
const (
@ -42,19 +40,27 @@ type Redirect struct {
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
// rule used to rewrite the defined paths
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*Redirect, error) {
if ing.GetAnnotations() == nil {
return &Redirect{}, errors.New("no annotations present")
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
rt, err := parser.GetStringAnnotation(rewriteTo, ing)
if err != nil {
return nil, err
}
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
if err != nil {
sslRe = cfg.SSLRedirect
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
}
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
return &Redirect{
Target: rt,

View file

@ -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) {
ing := buildIngress()
_, err := ParseAnnotations(defaults.Backend{}, ing)
_, err := NewParser(mockBackend{}).Parse(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
@ -80,11 +88,14 @@ func TestRedirect(t *testing.T) {
data[rewriteTo] = defRoute
ing.SetAnnotations(data)
redirect, err := ParseAnnotations(defaults.Backend{}, ing)
i, err := NewParser(mockBackend{}).Parse(ing)
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 {
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) {
ing := buildIngress()
cfg := defaults.Backend{SSLRedirect: true}
data := map[string]string{}
data[rewriteTo] = defRoute
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 {
t.Errorf("Expected true but returned false")
@ -108,8 +124,11 @@ func TestSSLRedirect(t *testing.T) {
data[sslRedirect] = "false"
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 {
t.Errorf("Expected false but returned true")
}

View file

@ -26,8 +26,16 @@ const (
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
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation(secureUpstream, ing)
}

View file

@ -65,7 +65,7 @@ func TestAnnotations(t *testing.T) {
data[secureUpstream] = "true"
ing.SetAnnotations(data)
_, err := ParseAnnotations(ing)
_, err := NewParser().Parse(ing)
if err != nil {
t.Error("Expected error with ingress without annotations")
}
@ -73,7 +73,7 @@ func TestAnnotations(t *testing.T) {
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(ing)
_, err := NewParser().Parse(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}

View file

@ -17,28 +17,29 @@ limitations under the License.
package sslpassthrough
import (
"fmt"
"k8s.io/kubernetes/pkg/apis/extensions"
"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 (
passthrough = "ingress.kubernetes.io/ssl-passthrough"
)
type sslpt struct {
}
// NewParser creates a new SSL passthrough annotation parser
func NewParser() parser.IngressAnnotation {
return sslpt{}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if is required to configure
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (bool, error) {
func (a sslpt) Parse(ing *extensions.Ingress) (interface{}, error) {
if ing.GetAnnotations() == nil {
return false, parser.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 false, ing_errors.ErrMissingAnnotations
}
return parser.GetBoolAnnotation(passthrough, ing)

View file

@ -22,8 +22,6 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
@ -44,7 +42,7 @@ func buildIngress() *extensions.Ingress {
func TestParseAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(defaults.Backend{}, ing)
_, err := NewParser().Parse(ing)
if err == nil {
t.Errorf("unexpected error: %v", err)
}
@ -53,9 +51,9 @@ func TestParseAnnotations(t *testing.T) {
data[passthrough] = "true"
ing.SetAnnotations(data)
// test ingress using the annotation without a TLS section
val, err := ParseAnnotations(defaults.Backend{}, ing)
if err == nil {
t.Errorf("expected error parsing an invalid cidr")
_, err = NewParser().Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress with sslpassthrough")
}
// test with a valid host
@ -64,9 +62,13 @@ func TestParseAnnotations(t *testing.T) {
Hosts: []string{"foo.bar.com"},
},
}
val, err = ParseAnnotations(defaults.Backend{}, ing)
i, err := NewParser().Parse(ing)
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 {
t.Errorf("expected true but false returned")