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
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue