Update dependencies

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

View file

@ -0,0 +1,41 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package alias
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/server-alias"
)
type alias struct {
}
// NewParser creates a new Alias annotation parser
func NewParser() parser.IngressAnnotation {
return alias{}
}
// Parse parses the annotations contained in the ingress rule
// used to add an alias to the provided hosts
func (a alias) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(annotation, ing)
}

View file

@ -0,0 +1,60 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package alias
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestParse(t *testing.T) {
ap := NewParser()
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected string
}{
{map[string]string{annotation: "www.example.com"}, "www.example.com"},
{map[string]string{annotation: "*.example.com www.example.*"}, "*.example.com www.example.*"},
{map[string]string{annotation: `~^www\d+\.example\.com$`}, `~^www\d+\.example\.com$`},
{map[string]string{annotation: ""}, ""},
{map[string]string{}, ""},
{nil, ""},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -0,0 +1,171 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"fmt"
"io/ioutil"
"os"
"path"
"regexp"
"github.com/pkg/errors"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/file"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
authType = "ingress.kubernetes.io/auth-type"
authSecret = "ingress.kubernetes.io/auth-secret"
authRealm = "ingress.kubernetes.io/auth-realm"
)
var (
authTypeRegex = regexp.MustCompile(`basic|digest`)
// AuthDirectory default directory used to store files
// to authenticate request
AuthDirectory = "/etc/ingress-controller/auth"
)
// BasicDigest returns authentication configuration for an Ingress rule
type BasicDigest struct {
Type string `json:"type"`
Realm string `json:"realm"`
File string `json:"file"`
Secured bool `json:"secured"`
FileSHA string `json:"fileSha"`
}
// Equal tests for equality between two BasicDigest types
func (bd1 *BasicDigest) Equal(bd2 *BasicDigest) bool {
if bd1 == bd2 {
return true
}
if bd1 == nil || bd2 == nil {
return false
}
if bd1.Type != bd2.Type {
return false
}
if bd1.Realm != bd2.Realm {
return false
}
if bd1.File != bd2.File {
return false
}
if bd1.Secured != bd2.Secured {
return false
}
if bd1.FileSHA != bd2.FileSHA {
return false
}
return true
}
type auth struct {
secretResolver resolver.Secret
authDirectory string
}
// NewParser creates a new authentication annotation parser
func NewParser(authDirectory string, sr resolver.Secret) parser.IngressAnnotation {
os.MkdirAll(authDirectory, 0755)
currPath := authDirectory
for currPath != "/" {
currPath = path.Dir(currPath)
err := os.Chmod(currPath, 0755)
if err != nil {
break
}
}
return auth{sr, authDirectory}
}
// Parse parses the annotations contained in the ingress
// rule used to add authentication in the paths defined in the rule
// and generated an htpasswd compatible file to be used as source
// during the authentication process
func (a auth) Parse(ing *extensions.Ingress) (interface{}, error) {
at, err := parser.GetStringAnnotation(authType, ing)
if err != nil {
return nil, err
}
if !authTypeRegex.MatchString(at) {
return nil, ing_errors.NewLocationDenied("invalid authentication type")
}
s, err := parser.GetStringAnnotation(authSecret, ing)
if err != nil {
return nil, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "error reading secret name from annotation"),
}
}
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
secret, err := a.secretResolver.GetSecret(name)
if err != nil {
return nil, ing_errors.LocationDenied{
Reason: errors.Wrapf(err, "unexpected error reading secret %v", name),
}
}
realm, _ := parser.GetStringAnnotation(authRealm, ing)
passFile := fmt.Sprintf("%v/%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.GetName())
err = dumpSecret(passFile, secret)
if err != nil {
return nil, err
}
return &BasicDigest{
Type: at,
Realm: realm,
File: passFile,
Secured: true,
FileSHA: file.SHA1(passFile),
}, nil
}
// dumpSecret dumps the content of a secret into a file
// in the expected format for the specified authorization
func dumpSecret(filename string, secret *api.Secret) error {
val, ok := secret.Data["auth"]
if !ok {
return ing_errors.LocationDenied{
Reason: errors.Errorf("the secret %v does not contain a key with value auth", secret.Name),
}
}
// TODO: check permissions required
err := ioutil.WriteFile(filename, val, 0777)
if err != nil {
return ing_errors.LocationDenied{
Reason: errors.Wrap(err, "unexpected error creating password file"),
}
}
return nil
}

View file

@ -0,0 +1,177 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"fmt"
"io/ioutil"
"os"
"testing"
"time"
"github.com/pkg/errors"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type 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: meta_v1.ObjectMeta{
Namespace: api.NamespaceDefault,
Name: "demo-secret",
},
Data: map[string][]byte{"auth": []byte("foo:$apr1$OFG3Xybp$ckL0FHDAkoXYIlH9.cysT0")},
}, nil
}
func TestIngressWithoutAuth(t *testing.T) {
ing := buildIngress()
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
_, err := NewParser(dir, mockSecret{}).Parse(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
}
func TestIngressAuth(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[authType] = "basic"
data[authSecret] = "demo-secret"
data[authRealm] = "-realm-"
ing.SetAnnotations(data)
_, dir, _ := dummySecretContent(t)
defer os.RemoveAll(dir)
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)
}
if auth.Realm != "-realm-" {
t.Errorf("Expected -realm- as realm but returned %s", auth.Realm)
}
if !auth.Secured {
t.Errorf("Expected true as secured but returned %v", auth.Secured)
}
}
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 {
t.Error(err)
}
tmpfile, err := ioutil.TempFile("", "example-")
if err != nil {
t.Error(err)
}
defer tmpfile.Close()
s, _ := mockSecret{}.GetSecret("default/demo-secret")
return tmpfile.Name(), dir, s
}
func TestDumpSecret(t *testing.T) {
tmpfile, dir, s := dummySecretContent(t)
defer os.RemoveAll(dir)
sd := s.Data
s.Data = nil
err := dumpSecret(tmpfile, s)
if err == nil {
t.Errorf("Expected error with secret without auth")
}
s.Data = sd
err = dumpSecret(tmpfile, s)
if err != nil {
t.Errorf("Unexpected error creating htpasswd file %v: %v", tmpfile, err)
}
}

View file

@ -0,0 +1,183 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package authreq
import (
"net/url"
"regexp"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
)
const (
// external URL that provides the authentication
authURL = "ingress.kubernetes.io/auth-url"
authSigninURL = "ingress.kubernetes.io/auth-signin"
authMethod = "ingress.kubernetes.io/auth-method"
authBody = "ingress.kubernetes.io/auth-send-body"
authHeaders = "ingress.kubernetes.io/auth-response-headers"
)
// External returns external authentication configuration for an Ingress rule
type External struct {
URL string `json:"url"`
// Host contains the hostname defined in the URL
Host string `json:"host"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"`
SendBody bool `json:"sendBody"`
ResponseHeaders []string `json:"responseHeaders,omitEmpty"`
}
// Equal tests for equality between two External types
func (e1 *External) Equal(e2 *External) bool {
if e1 == e2 {
return true
}
if e1 == nil || e2 == nil {
return false
}
if e1.URL != e2.URL {
return false
}
if e1.Host != e2.Host {
return false
}
if e1.SigninURL != e2.SigninURL {
return false
}
if e1.Method != e2.Method {
return false
}
if e1.SendBody != e2.SendBody {
return false
}
if e1.Method != e2.Method {
return false
}
for _, ep1 := range e1.ResponseHeaders {
found := false
for _, ep2 := range e2.ResponseHeaders {
if ep1 == ep2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
var (
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)
)
func validMethod(method string) bool {
if len(method) == 0 {
return false
}
for _, m := range methods {
if method == m {
return true
}
}
return false
}
func validHeader(header string) bool {
return headerRegexp.Match([]byte(header))
}
type authReq struct {
}
// NewParser creates a new authentication request annotation parser
func NewParser() parser.IngressAnnotation {
return authReq{}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to use an external URL as source for authentication
func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
str, err := parser.GetStringAnnotation(authURL, ing)
if err != nil {
return nil, err
}
if str == "" {
return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL")
}
signin, _ := parser.GetStringAnnotation(authSigninURL, ing)
ur, err := url.Parse(str)
if err != nil {
return nil, err
}
if ur.Scheme == "" {
return nil, ing_errors.NewLocationDenied("url scheme is empty")
}
if ur.Host == "" {
return nil, ing_errors.NewLocationDenied("url host is empty")
}
if strings.Contains(ur.Host, "..") {
return nil, ing_errors.NewLocationDenied("invalid url host")
}
m, _ := parser.GetStringAnnotation(authMethod, ing)
if len(m) != 0 && !validMethod(m) {
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
}
h := []string{}
hstr, _ := parser.GetStringAnnotation(authHeaders, ing)
if len(hstr) != 0 {
harr := strings.Split(hstr, ",")
for _, header := range harr {
header = strings.TrimSpace(header)
if len(header) > 0 {
if !validHeader(header) {
return nil, ing_errors.NewLocationDenied("invalid headers list")
}
h = append(h, header)
}
}
}
sb, _ := parser.GetBoolAnnotation(authBody, ing)
return &External{
URL: str,
Host: ur.Hostname(),
SigninURL: signin,
Method: m,
SendBody: sb,
ResponseHeaders: h,
}, nil
}

View file

@ -0,0 +1,167 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package authreq
import (
"fmt"
"reflect"
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
ing.SetAnnotations(data)
tests := []struct {
title string
url string
signinURL string
method string
sendBody bool
expErr bool
}{
{"empty", "", "", "", false, true},
{"no scheme", "bar", "bar", "", false, true},
{"invalid host", "http://", "http://", "", false, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", false, true},
{"valid URL", "http://bar.foo.com/external-auth", "http://bar.foo.com/external-auth", "", false, false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "POST", true, false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", true, false},
}
for _, test := range tests {
data[authURL] = test.url
data[authSigninURL] = test.signinURL
data[authBody] = fmt.Sprintf("%v", test.sendBody)
data[authMethod] = fmt.Sprintf("%v", test.method)
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)
}
if u.SigninURL != test.signinURL {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.signinURL, u.SigninURL)
}
if u.Method != test.method {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method)
}
if u.SendBody != test.sendBody {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.sendBody, u.SendBody)
}
}
}
func TestHeaderAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
ing.SetAnnotations(data)
tests := []struct {
title string
url string
headers string
parsedHeaders []string
expErr bool
}{
{"single header", "http://goog.url", "h1", []string{"h1"}, false},
{"nothing", "http://goog.url", "", []string{}, false},
{"spaces", "http://goog.url", " ", []string{}, false},
{"two headers", "http://goog.url", "1,2", []string{"1", "2"}, false},
{"two headers and empty entries", "http://goog.url", ",1,,2,", []string{"1", "2"}, false},
{"header with spaces", "http://goog.url", "1 2", []string{}, true},
{"header with other bad symbols", "http://goog.url", "1+2", []string{}, true},
}
for _, test := range tests {
data[authURL] = test.url
data[authHeaders] = test.headers
data[authMethod] = "GET"
i, err := NewParser().Parse(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", err.Error())
}
continue
}
t.Log(i)
u, ok := i.(*External)
if !ok {
t.Errorf("%v: expected an External type", test.title)
continue
}
if !reflect.DeepEqual(u.ResponseHeaders, test.parsedHeaders) {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.headers, u.ResponseHeaders)
}
}
}

View file

@ -0,0 +1,131 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package authtls
import (
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
"k8s.io/ingress-nginx/pkg/k8s"
"regexp"
)
const (
// name of the secret
annotationAuthTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
annotationAuthVerifyClient = "ingress.kubernetes.io/auth-tls-verify-client"
annotationAuthTLSDepth = "ingress.kubernetes.io/auth-tls-verify-depth"
annotationAuthTLSErrorPage = "ingress.kubernetes.io/auth-tls-error-page"
defaultAuthTLSDepth = 1
defaultAuthVerifyClient = "on"
)
var (
authVerifyClientRegex = regexp.MustCompile(`on|off|optional|optional_no_ca`)
)
// AuthSSLConfig contains the AuthSSLCert used for muthual autentication
// and the configured ValidationDepth
type AuthSSLConfig struct {
resolver.AuthSSLCert
VerifyClient string `json:"verify_client"`
ValidationDepth int `json:"validationDepth"`
ErrorPage string `json:"errorPage"`
}
// Equal tests for equality between two AuthSSLConfig types
func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool {
if assl1 == assl2 {
return true
}
if assl1 == nil || assl2 == nil {
return false
}
if !(&assl1.AuthSSLCert).Equal(&assl2.AuthSSLCert) {
return false
}
if assl1.VerifyClient != assl2.VerifyClient {
return false
}
if assl1.ValidationDepth != assl2.ValidationDepth {
return false
}
if assl1.ErrorPage != assl2.ErrorPage {
return false
}
return true
}
// NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
return authTLS{resolver}
}
type authTLS struct {
certResolver resolver.AuthCertificate
}
// Parse parses the annotations contained in the ingress
// rule used to use a Certificate as authentication method
func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
if err != nil {
return &AuthSSLConfig{}, err
}
if tlsauthsecret == "" {
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
}
_, _, err = k8s.ParseNameNS(tlsauthsecret)
if err != nil {
return &AuthSSLConfig{}, ing_errors.NewLocationDenied(err.Error())
}
tlsVerifyClient, err := parser.GetStringAnnotation(annotationAuthVerifyClient, ing)
if err != nil || !authVerifyClientRegex.MatchString(tlsVerifyClient) {
tlsVerifyClient = defaultAuthVerifyClient
}
tlsdepth, err := parser.GetIntAnnotation(annotationAuthTLSDepth, ing)
if err != nil || tlsdepth == 0 {
tlsdepth = defaultAuthTLSDepth
}
authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret)
if err != nil {
return &AuthSSLConfig{}, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "error obtaining certificate"),
}
}
errorpage, err := parser.GetStringAnnotation(annotationAuthTLSErrorPage, ing)
if err != nil || errorpage == "" {
errorpage = ""
}
return &AuthSSLConfig{
AuthSSLCert: *authCert,
VerifyClient: tlsVerifyClient,
ValidationDepth: tlsdepth,
ErrorPage: errorpage,
}, nil
}

View file

@ -0,0 +1,108 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package authtls
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
ing.SetAnnotations(data)
/*
tests := []struct {
title string
url string
method string
sendBody bool
expErr bool
}{
{"empty", "", "", false, true},
{"no scheme", "bar", "", false, true},
{"invalid host", "http://", "", false, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "", false, true},
{"valid URL", "http://bar.foo.com/external-auth", "", false, false},
{"valid URL - send body", "http://foo.com/external-auth", "POST", true, false},
{"valid URL - send body", "http://foo.com/external-auth", "GET", true, false},
}
for _, test := range tests {
data[authTLSSecret] = ""
test.title
u, err := ParseAnnotations(ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", test.title)
}
continue
}
if u.URL != test.url {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
}
if u.Method != test.method {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method)
}
if u.SendBody != test.sendBody {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.sendBody, u.SendBody)
}
}*/
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package class
import (
"github.com/golang/glog"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/errors"
)
const (
// IngressKey picks a specific "class" for the Ingress.
// The controller only processes Ingresses with this annotation either
// unset, or set to either the configured value or the empty string.
IngressKey = "kubernetes.io/ingress.class"
)
// IsValid returns true if the given Ingress either doesn't specify
// the ingress.class annotation, or it's set to the configured in the
// ingress controller.
func IsValid(ing *extensions.Ingress, controller, defClass string) bool {
ingress, err := parser.GetStringAnnotation(IngressKey, ing)
if err != nil && !errors.IsMissingAnnotations(err) {
glog.Warningf("unexpected error reading ingress annotation: %v", err)
}
// we have 2 valid combinations
// 1 - ingress with default class | blank annotation on ingress
// 2 - ingress with specific class | same annotation on ingress
//
// and 2 invalid combinations
// 3 - ingress with default class | fixed annotation on ingress
// 4 - ingress with specific class | different annotation on ingress
if ingress == "" && controller == defClass {
return true
}
return ingress == controller
}

View file

@ -0,0 +1,59 @@
/*
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 class
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestIsValidClass(t *testing.T) {
tests := []struct {
ingress string
controller string
defClass string
isValid bool
}{
{"", "", "nginx", true},
{"", "nginx", "nginx", true},
{"nginx", "nginx", "nginx", true},
{"custom", "custom", "nginx", true},
{"", "killer", "nginx", false},
{"", "", "nginx", true},
{"custom", "nginx", "nginx", false},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
}
data := map[string]string{}
ing.SetAnnotations(data)
for _, test := range tests {
ing.Annotations[IngressKey] = test.ingress
b := IsValid(ing, test.controller, test.defClass)
if b != test.isValid {
t.Errorf("test %v - expected %v but %v was returned", test, test.isValid, b)
}
}
}

View file

@ -0,0 +1,41 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clientbodybuffersize
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/client-body-buffer-size"
)
type clientBodyBufferSize struct {
}
// NewParser creates a new clientBodyBufferSize annotation parser
func NewParser() parser.IngressAnnotation {
return clientBodyBufferSize{}
}
// Parse parses the annotations contained in the ingress rule
// used to add an client-body-buffer-size to the provided locations
func (a clientBodyBufferSize) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(annotation, ing)
}

View file

@ -0,0 +1,59 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clientbodybuffersize
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestParse(t *testing.T) {
ap := NewParser()
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected string
}{
{map[string]string{annotation: "8k"}, "8k"},
{map[string]string{annotation: "16k"}, "16k"},
{map[string]string{annotation: ""}, ""},
{map[string]string{}, ""},
{nil, ""},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -0,0 +1,41 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cors
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/enable-cors"
)
type cors struct {
}
// NewParser creates a new CORS annotation parser
func NewParser() parser.IngressAnnotation {
return cors{}
}
// Parse parses the annotations contained in the ingress
// rule used to indicate if the location/s should allows CORS
func (a cors) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation(annotation, ing)
}

View file

@ -0,0 +1,63 @@
/*
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 cors
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
notCorsAnnotation = "ingress.kubernetes.io/enable-not-cors"
)
func TestParse(t *testing.T) {
ap := NewParser()
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected bool
}{
{map[string]string{annotation: "true"}, true},
{map[string]string{annotation: "false"}, false},
{map[string]string{notCorsAnnotation: "true"}, false},
{map[string]string{}, false},
{nil, false},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %t but returned %t, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -0,0 +1,57 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package defaultbackend
import (
"fmt"
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
defaultBackend = "ingress.kubernetes.io/default-backend"
)
type backend struct {
serviceResolver resolver.Service
}
// NewParser creates a new default backend annotation parser
func NewParser(sr resolver.Service) parser.IngressAnnotation {
return backend{sr}
}
// Parse parses the annotations contained in the ingress to use
// a custom default backend
func (db backend) Parse(ing *extensions.Ingress) (interface{}, error) {
s, err := parser.GetStringAnnotation(defaultBackend, ing)
if err != nil {
return nil, err
}
name := fmt.Sprintf("%v/%v", ing.Namespace, s)
svc, err := db.serviceResolver.GetService(name)
if err != nil {
return nil, errors.Wrapf(err, "unexpected error reading service %v", name)
}
return svc, nil
}

View file

@ -0,0 +1,66 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package healthcheck
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
upsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
upsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
)
// Upstream returns the URL and method to use check the status of
// the upstream server/s
type Upstream struct {
MaxFails int `json:"maxFails"`
FailTimeout int `json:"failTimeout"`
}
type healthCheck struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new health check annotation parser
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
return healthCheck{br}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure upstream check parameters
func (a healthCheck) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
if ing.GetAnnotations() == nil {
return &Upstream{defBackend.UpstreamMaxFails, defBackend.UpstreamFailTimeout}, nil
}
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
if err != nil {
mf = defBackend.UpstreamMaxFails
}
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
if err != nil {
ft = defBackend.UpstreamFailTimeout
}
return &Upstream{mf, ft}, nil
}

View file

@ -0,0 +1,92 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package healthcheck
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockBackend struct {
}
func (m mockBackend) GetDefaultBackend() defaults.Backend {
return defaults.Backend{UpstreamFailTimeout: 1}
}
func TestIngressHealthCheck(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[upsMaxFails] = "2"
ing.SetAnnotations(data)
hzi, _ := NewParser(mockBackend{}).Parse(ing)
nginxHz, ok := hzi.(*Upstream)
if !ok {
t.Errorf("expected a Upstream type")
}
if nginxHz.MaxFails != 2 {
t.Errorf("expected 2 as max-fails but returned %v", nginxHz.MaxFails)
}
if nginxHz.FailTimeout != 1 {
t.Errorf("expected 0 as fail-timeout but returned %v", nginxHz.FailTimeout)
}
}

View file

@ -0,0 +1,113 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ipwhitelist
import (
"sort"
"strings"
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/net"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
whitelist = "ingress.kubernetes.io/whitelist-source-range"
)
// SourceRange returns the CIDR
type SourceRange struct {
CIDR []string `json:"cidr,omitEmpty"`
}
// Equal tests for equality between two SourceRange types
func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool {
if sr1 == sr2 {
return true
}
if sr1 == nil || sr2 == nil {
return false
}
if len(sr1.CIDR) != len(sr2.CIDR) {
return false
}
for _, s1l := range sr1.CIDR {
found := false
for _, sl2 := range sr2.CIDR {
if s1l == sl2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
type ipwhitelist struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new whitelist annotation parser
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
return ipwhitelist{br}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to limit access to certain client addresses or networks.
// Multiple ranges can specified using commas as separator
// e.g. `18.0.0.0/8,56.0.0.0/8`
func (a ipwhitelist) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
sort.Strings(defBackend.WhitelistSourceRange)
val, err := parser.GetStringAnnotation(whitelist, ing)
// A missing annotation is not a problem, just use the default
if err == ing_errors.ErrMissingAnnotations {
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, nil
}
values := strings.Split(val, ",")
ipnets, ips, err := net.ParseIPNets(values...)
if err != nil && len(ips) == 0 {
return &SourceRange{CIDR: defBackend.WhitelistSourceRange}, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "the annotation does not contain a valid IP address or network"),
}
}
cidrs := []string{}
for k := range ipnets {
cidrs = append(cidrs, k)
}
for k := range ips {
cidrs = append(cidrs, k)
}
sort.Strings(cidrs)
return &SourceRange{cidrs}, nil
}

View file

@ -0,0 +1,199 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ipwhitelist
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockBackend struct {
defaults.Backend
}
func (m mockBackend) GetDefaultBackend() defaults.Backend {
return m.Backend
}
func TestParseAnnotations(t *testing.T) {
ing := buildIngress()
tests := map[string]struct {
net string
expectCidr []string
expectErr bool
errOut string
}{
"test parse a valid net": {
net: "10.0.0.0/24",
expectCidr: []string{"10.0.0.0/24"},
expectErr: false,
},
"test parse a invalid net": {
net: "ww",
expectErr: true,
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ww",
},
"test parse a empty net": {
net: "",
expectErr: true,
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ",
},
"test parse multiple valid cidr": {
net: "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24",
expectCidr: []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"},
expectErr: false,
},
}
for testName, test := range tests {
data := map[string]string{}
data[whitelist] = test.net
ing.SetAnnotations(data)
p := NewParser(mockBackend{})
i, err := p.Parse(ing)
if err != nil && !test.expectErr {
t.Errorf("%v:unexpected error: %v", testName, err)
}
if test.expectErr {
if err.Error() != test.errOut {
t.Errorf("%v:expected error: %v but %v return", testName, test.errOut, err.Error())
}
}
if !test.expectErr {
sr, ok := i.(*SourceRange)
if !ok {
t.Errorf("%v:expected a SourceRange type", testName)
}
if !strsEquals(sr.CIDR, test.expectCidr) {
t.Errorf("%v:expected %v CIDR but %v returned", testName, test.expectCidr, sr.CIDR)
}
}
}
}
// Test that when we have a whitelist set on the Backend that is used when we
// don't have the annotation
func TestParseAnnotationsWithDefaultConfig(t *testing.T) {
ing := buildIngress()
mockBackend := mockBackend{}
mockBackend.Backend.WhitelistSourceRange = []string{"4.4.4.0/24", "1.2.3.4/32"}
tests := map[string]struct {
net string
expectCidr []string
expectErr bool
errOut string
}{
"test parse a valid net": {
net: "10.0.0.0/24",
expectCidr: []string{"10.0.0.0/24"},
expectErr: false,
},
"test parse a invalid net": {
net: "ww",
expectErr: true,
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ww",
},
"test parse a empty net": {
net: "",
expectErr: true,
errOut: "the annotation does not contain a valid IP address or network: invalid CIDR address: ",
},
"test parse multiple valid cidr": {
net: "2.2.2.2/32,1.1.1.1/32,3.3.3.0/24",
expectCidr: []string{"1.1.1.1/32", "2.2.2.2/32", "3.3.3.0/24"},
expectErr: false,
},
}
for testName, test := range tests {
data := map[string]string{}
data[whitelist] = test.net
ing.SetAnnotations(data)
p := NewParser(mockBackend)
i, err := p.Parse(ing)
if err != nil && !test.expectErr {
t.Errorf("%v:unexpected error: %v", testName, err)
}
if test.expectErr {
if err.Error() != test.errOut {
t.Errorf("%v:expected error: %v but %v return", testName, test.errOut, err.Error())
}
}
if !test.expectErr {
sr, ok := i.(*SourceRange)
if !ok {
t.Errorf("%v:expected a SourceRange type", testName)
}
if !strsEquals(sr.CIDR, test.expectCidr) {
t.Errorf("%v:expected %v CIDR but %v returned", testName, test.expectCidr, sr.CIDR)
}
}
}
}
func strsEquals(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}

View file

@ -0,0 +1,102 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package parser
import (
"strconv"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/errors"
)
// IngressAnnotation has a method to parse annotations located in Ingress
type IngressAnnotation interface {
Parse(ing *extensions.Ingress) (interface{}, error)
}
type ingAnnotations map[string]string
func (a ingAnnotations) parseBool(name string) (bool, error) {
val, ok := a[name]
if ok {
b, err := strconv.ParseBool(val)
if err != nil {
return false, errors.NewInvalidAnnotationContent(name, val)
}
return b, nil
}
return false, errors.ErrMissingAnnotations
}
func (a ingAnnotations) parseString(name string) (string, error) {
val, ok := a[name]
if ok {
return val, nil
}
return "", errors.ErrMissingAnnotations
}
func (a ingAnnotations) parseInt(name string) (int, error) {
val, ok := a[name]
if ok {
i, err := strconv.Atoi(val)
if err != nil {
return 0, errors.NewInvalidAnnotationContent(name, val)
}
return i, nil
}
return 0, errors.ErrMissingAnnotations
}
func checkAnnotation(name string, ing *extensions.Ingress) error {
if ing == nil || len(ing.GetAnnotations()) == 0 {
return errors.ErrMissingAnnotations
}
if name == "" {
return errors.ErrInvalidAnnotationName
}
return nil
}
// GetBoolAnnotation extracts a boolean from an Ingress annotation
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
err := checkAnnotation(name, ing)
if err != nil {
return false, err
}
return ingAnnotations(ing.GetAnnotations()).parseBool(name)
}
// GetStringAnnotation extracts a string from an Ingress annotation
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
err := checkAnnotation(name, ing)
if err != nil {
return "", err
}
return ingAnnotations(ing.GetAnnotations()).parseString(name)
}
// GetIntAnnotation extracts an int from an Ingress annotation
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
err := checkAnnotation(name, ing)
if err != nil {
return 0, err
}
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
}

View file

@ -0,0 +1,161 @@
/*
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 parser
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
}
func TestGetBoolAnnotation(t *testing.T) {
ing := buildIngress()
_, err := GetBoolAnnotation("", nil)
if err == nil {
t.Errorf("expected error but retuned nil")
}
tests := []struct {
name string
field string
value string
exp bool
expErr bool
}{
{"empty - false", "", "false", false, true},
{"empty - true", "", "true", false, true},
{"valid - false", "bool", "false", false, false},
{"valid - true", "bool", "true", true, false},
}
data := map[string]string{}
ing.SetAnnotations(data)
for _, test := range tests {
data[test.field] = test.value
u, err := GetBoolAnnotation(test.field, ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", test.name)
}
continue
}
if u != test.exp {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, u)
}
delete(data, test.field)
}
}
func TestGetStringAnnotation(t *testing.T) {
ing := buildIngress()
_, err := GetStringAnnotation("", nil)
if err == nil {
t.Errorf("expected error but retuned nil")
}
tests := []struct {
name string
field string
value string
exp string
expErr bool
}{
{"empty - A", "", "A", "", true},
{"empty - B", "", "B", "", true},
{"valid - A", "string", "A", "A", false},
{"valid - B", "string", "B", "B", false},
}
data := map[string]string{}
ing.SetAnnotations(data)
for _, test := range tests {
data[test.field] = test.value
s, err := GetStringAnnotation(test.field, ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", test.name)
}
continue
}
if s != test.exp {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
}
delete(data, test.field)
}
}
func TestGetIntAnnotation(t *testing.T) {
ing := buildIngress()
_, err := GetIntAnnotation("", nil)
if err == nil {
t.Errorf("expected error but retuned nil")
}
tests := []struct {
name string
field string
value string
exp int
expErr bool
}{
{"empty - A", "", "1", 0, true},
{"empty - B", "", "2", 0, true},
{"valid - A", "string", "1", 1, false},
{"valid - B", "string", "2", 2, false},
}
data := map[string]string{}
ing.SetAnnotations(data)
for _, test := range tests {
data[test.field] = test.value
s, err := GetIntAnnotation(test.field, ing)
if test.expErr {
if err == nil {
t.Errorf("%v: expected error but retuned nil", test.name)
}
continue
}
if s != test.exp {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.name, test.exp, s)
}
delete(data, test.field)
}
}

View file

@ -0,0 +1,48 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package portinredirect
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
annotation = "ingress.kubernetes.io/use-port-in-redirects"
)
type portInRedirect struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new port in redirect annotation parser
func NewParser(db resolver.DefaultBackend) parser.IngressAnnotation {
return portInRedirect{db}
}
// Parse parses the annotations contained in the ingress
// rule used to indicate if the redirects must
func (a portInRedirect) Parse(ing *extensions.Ingress) (interface{}, error) {
up, err := parser.GetBoolAnnotation(annotation, ing)
if err != nil {
return a.backendResolver.GetDefaultBackend().UsePortInRedirects, nil
}
return up, nil
}

View file

@ -0,0 +1,121 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package portinredirect
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"fmt"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockBackend struct {
usePortInRedirects bool
}
func (m mockBackend) GetDefaultBackend() defaults.Backend {
return defaults.Backend{UsePortInRedirects: m.usePortInRedirects}
}
func TestPortInRedirect(t *testing.T) {
tests := []struct {
title string
usePort *bool
def bool
exp bool
}{
{"false - default false", newFalse(), false, false},
{"false - default true", newFalse(), true, false},
{"no annotation - default false", nil, false, false},
{"no annotation - default true", nil, true, true},
{"true - default true", newTrue(), true, true},
}
for _, test := range tests {
ing := buildIngress()
data := map[string]string{}
if test.usePort != nil {
data[annotation] = fmt.Sprintf("%v", *test.usePort)
}
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{test.def}).Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing a valid")
}
p, ok := i.(bool)
if !ok {
t.Errorf("expected a bool type")
}
if p != test.exp {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.exp, p)
}
}
}
func newTrue() *bool {
b := true
return &b
}
func newFalse() *bool {
b := false
return &b
}

View file

@ -0,0 +1,160 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proxy
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
bodySize = "ingress.kubernetes.io/proxy-body-size"
connect = "ingress.kubernetes.io/proxy-connect-timeout"
send = "ingress.kubernetes.io/proxy-send-timeout"
read = "ingress.kubernetes.io/proxy-read-timeout"
bufferSize = "ingress.kubernetes.io/proxy-buffer-size"
cookiePath = "ingress.kubernetes.io/proxy-cookie-path"
cookieDomain = "ingress.kubernetes.io/proxy-cookie-domain"
nextUpstream = "ingress.kubernetes.io/proxy-next-upstream"
passParams = "ingress.kubernetes.io/proxy-pass-params"
requestBuffering = "ingress.kubernetes.io/proxy-request-buffering"
)
// Configuration returns the proxy timeout to use in the upstream server/s
type Configuration struct {
BodySize string `json:"bodySize"`
ConnectTimeout int `json:"connectTimeout"`
SendTimeout int `json:"sendTimeout"`
ReadTimeout int `json:"readTimeout"`
BufferSize string `json:"bufferSize"`
CookieDomain string `json:"cookieDomain"`
CookiePath string `json:"cookiePath"`
NextUpstream string `json:"nextUpstream"`
PassParams string `json:"passParams"`
RequestBuffering string `json:"requestBuffering"`
}
// Equal tests for equality between two Configuration types
func (l1 *Configuration) Equal(l2 *Configuration) bool {
if l1 == l2 {
return true
}
if l1 == nil || l2 == nil {
return false
}
if l1.BodySize != l2.BodySize {
return false
}
if l1.ConnectTimeout != l2.ConnectTimeout {
return false
}
if l1.SendTimeout != l2.SendTimeout {
return false
}
if l1.ReadTimeout != l2.ReadTimeout {
return false
}
if l1.BufferSize != l2.BufferSize {
return false
}
if l1.CookieDomain != l2.CookieDomain {
return false
}
if l1.CookiePath != l2.CookiePath {
return false
}
if l1.NextUpstream != l2.NextUpstream {
return false
}
if l1.PassParams != l2.PassParams {
return false
}
if l1.RequestBuffering != l2.RequestBuffering {
return false
}
return true
}
type proxy struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new reverse proxy configuration annotation parser
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
return proxy{br}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure upstream check parameters
func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
ct, err := parser.GetIntAnnotation(connect, ing)
if err != nil {
ct = defBackend.ProxyConnectTimeout
}
st, err := parser.GetIntAnnotation(send, ing)
if err != nil {
st = defBackend.ProxySendTimeout
}
rt, err := parser.GetIntAnnotation(read, ing)
if err != nil {
rt = defBackend.ProxyReadTimeout
}
bufs, err := parser.GetStringAnnotation(bufferSize, ing)
if err != nil || bufs == "" {
bufs = defBackend.ProxyBufferSize
}
cp, err := parser.GetStringAnnotation(cookiePath, ing)
if err != nil || cp == "" {
cp = defBackend.ProxyCookiePath
}
cd, err := parser.GetStringAnnotation(cookieDomain, ing)
if err != nil || cd == "" {
cd = defBackend.ProxyCookieDomain
}
bs, err := parser.GetStringAnnotation(bodySize, ing)
if err != nil || bs == "" {
bs = defBackend.ProxyBodySize
}
nu, err := parser.GetStringAnnotation(nextUpstream, ing)
if err != nil || nu == "" {
nu = defBackend.ProxyNextUpstream
}
pp, err := parser.GetStringAnnotation(passParams, ing)
if err != nil || pp == "" {
pp = defBackend.ProxyPassParams
}
rb, err := parser.GetStringAnnotation(requestBuffering, ing)
if err != nil || rb == "" {
rb = defBackend.ProxyRequestBuffering
}
return &Configuration{bs, ct, st, rt, bufs, cd, cp, nu, pp, rb}, nil
}

View file

@ -0,0 +1,168 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package proxy
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockBackend struct {
}
func (m mockBackend) GetDefaultBackend() defaults.Backend {
return defaults.Backend{
UpstreamFailTimeout: 1,
ProxyConnectTimeout: 10,
ProxySendTimeout: 15,
ProxyReadTimeout: 20,
ProxyBufferSize: "10k",
ProxyBodySize: "3k",
ProxyNextUpstream: "error",
ProxyPassParams: "nocanon keepalive=On",
ProxyRequestBuffering: "on",
}
}
func TestProxy(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[connect] = "1"
data[send] = "2"
data[read] = "3"
data[bufferSize] = "1k"
data[bodySize] = "2k"
data[nextUpstream] = "off"
data[passParams] = "smax=5 max=10"
data[requestBuffering] = "off"
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Fatalf("unexpected error parsing a valid")
}
p, ok := i.(*Configuration)
if !ok {
t.Fatalf("expected a Configuration type")
}
if p.ConnectTimeout != 1 {
t.Errorf("expected 1 as connect-timeout but returned %v", p.ConnectTimeout)
}
if p.SendTimeout != 2 {
t.Errorf("expected 2 as send-timeout but returned %v", p.SendTimeout)
}
if p.ReadTimeout != 3 {
t.Errorf("expected 3 as read-timeout but returned %v", p.ReadTimeout)
}
if p.BufferSize != "1k" {
t.Errorf("expected 1k as buffer-size but returned %v", p.BufferSize)
}
if p.BodySize != "2k" {
t.Errorf("expected 2k as body-size but returned %v", p.BodySize)
}
if p.NextUpstream != "off" {
t.Errorf("expected off as next-upstream but returned %v", p.NextUpstream)
}
if p.PassParams != "smax=5 max=10" {
t.Errorf("expected \"smax=5 max=10\" as pass-params but returned \"%v\"", p.PassParams)
}
if p.RequestBuffering != "off" {
t.Errorf("expected off as request-buffering but returned %v", p.RequestBuffering)
}
}
func TestProxyWithNoAnnotation(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Fatalf("unexpected error parsing a valid")
}
p, ok := i.(*Configuration)
if !ok {
t.Fatalf("expected a Configuration type")
}
if p.ConnectTimeout != 10 {
t.Errorf("expected 10 as connect-timeout but returned %v", p.ConnectTimeout)
}
if p.SendTimeout != 15 {
t.Errorf("expected 15 as send-timeout but returned %v", p.SendTimeout)
}
if p.ReadTimeout != 20 {
t.Errorf("expected 20 as read-timeout but returned %v", p.ReadTimeout)
}
if p.BufferSize != "10k" {
t.Errorf("expected 10k as buffer-size but returned %v", p.BufferSize)
}
if p.BodySize != "3k" {
t.Errorf("expected 3k as body-size but returned %v", p.BodySize)
}
if p.NextUpstream != "error" {
t.Errorf("expected error as next-upstream but returned %v", p.NextUpstream)
}
if p.PassParams != "nocanon keepalive=On" {
t.Errorf("expected \"nocanon keepalive=On\" as pass-params but returned \"%v\"", p.PassParams)
}
if p.RequestBuffering != "on" {
t.Errorf("expected on as request-buffering but returned %v", p.RequestBuffering)
}
}

View file

@ -0,0 +1,255 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ratelimit
import (
"encoding/base64"
"fmt"
"sort"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
"k8s.io/ingress-nginx/pkg/net"
)
const (
limitIP = "ingress.kubernetes.io/limit-connections"
limitRPS = "ingress.kubernetes.io/limit-rps"
limitRPM = "ingress.kubernetes.io/limit-rpm"
limitRATE = "ingress.kubernetes.io/limit-rate"
limitRATEAFTER = "ingress.kubernetes.io/limit-rate-after"
limitWhitelist = "ingress.kubernetes.io/limit-whitelist"
// allow 5 times the specified limit as burst
defBurst = 5
// 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states
// default is 5MB
defSharedSize = 5
)
// RateLimit returns rate limit configuration for an Ingress rule limiting the
// number of connections per IP address and/or connections per second.
// If you both annotations are specified in a single Ingress rule, RPS limits
// takes precedence
type RateLimit struct {
// Connections indicates a limit with the number of connections per IP address
Connections Zone `json:"connections"`
// RPS indicates a limit with the number of connections per second
RPS Zone `json:"rps"`
RPM Zone `json:"rpm"`
LimitRate int `json:"limit-rate"`
LimitRateAfter int `json:"limit-rate-after"`
Name string `json:"name"`
ID string `json:"id"`
Whitelist []string `json:"whitelist"`
}
// Equal tests for equality between two RateLimit types
func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
if rt1 == rt2 {
return true
}
if rt1 == nil || rt2 == nil {
return false
}
if !(&rt1.Connections).Equal(&rt2.Connections) {
return false
}
if !(&rt1.RPM).Equal(&rt2.RPM) {
return false
}
if !(&rt1.RPS).Equal(&rt2.RPS) {
return false
}
if rt1.LimitRate != rt2.LimitRate {
return false
}
if rt1.LimitRateAfter != rt2.LimitRateAfter {
return false
}
if rt1.ID != rt2.ID {
return false
}
if rt1.Name != rt2.Name {
return false
}
if len(rt1.Whitelist) != len(rt2.Whitelist) {
return false
}
for _, r1l := range rt1.Whitelist {
found := false
for _, rl2 := range rt2.Whitelist {
if r1l == rl2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// Zone returns information about the NGINX rate limit (limit_req_zone)
// http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
type Zone struct {
Name string `json:"name"`
Limit int `json:"limit"`
Burst int `json:"burst"`
// SharedSize amount of shared memory for the zone
SharedSize int `json:"sharedSize"`
}
// Equal tests for equality between two Zone types
func (z1 *Zone) Equal(z2 *Zone) bool {
if z1 == z2 {
return true
}
if z1 == nil || z2 == nil {
return false
}
if z1.Name != z2.Name {
return false
}
if z1.Limit != z2.Limit {
return false
}
if z1.Burst != z2.Burst {
return false
}
if z1.SharedSize != z2.SharedSize {
return false
}
return true
}
type ratelimit struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new ratelimit annotation parser
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
return ratelimit{br}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths
func (a ratelimit) Parse(ing *extensions.Ingress) (interface{}, error) {
defBackend := a.backendResolver.GetDefaultBackend()
lr, err := parser.GetIntAnnotation(limitRATE, ing)
if err != nil {
lr = defBackend.LimitRate
}
lra, err := parser.GetIntAnnotation(limitRATEAFTER, ing)
if err != nil {
lra = defBackend.LimitRateAfter
}
rpm, _ := parser.GetIntAnnotation(limitRPM, ing)
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
conn, _ := parser.GetIntAnnotation(limitIP, ing)
val, _ := parser.GetStringAnnotation(limitWhitelist, ing)
cidrs, err := parseCIDRs(val)
if err != nil {
return nil, err
}
if rpm == 0 && rps == 0 && conn == 0 {
return &RateLimit{
Connections: Zone{},
RPS: Zone{},
RPM: Zone{},
LimitRate: lr,
LimitRateAfter: lra,
}, nil
}
zoneName := fmt.Sprintf("%v_%v", ing.GetNamespace(), ing.GetName())
return &RateLimit{
Connections: Zone{
Name: fmt.Sprintf("%v_conn", zoneName),
Limit: conn,
Burst: conn * defBurst,
SharedSize: defSharedSize,
},
RPS: Zone{
Name: fmt.Sprintf("%v_rps", zoneName),
Limit: rps,
Burst: rps * defBurst,
SharedSize: defSharedSize,
},
RPM: Zone{
Name: fmt.Sprintf("%v_rpm", zoneName),
Limit: rpm,
Burst: rpm * defBurst,
SharedSize: defSharedSize,
},
LimitRate: lr,
LimitRateAfter: lra,
Name: zoneName,
ID: encode(zoneName),
Whitelist: cidrs,
}, nil
}
func parseCIDRs(s string) ([]string, error) {
if s == "" {
return []string{}, nil
}
values := strings.Split(s, ",")
ipnets, ips, err := net.ParseIPNets(values...)
if err != nil {
return nil, err
}
cidrs := []string{}
for k := range ipnets {
cidrs = append(cidrs, k)
}
for k := range ips {
cidrs = append(cidrs, k)
}
sort.Strings(cidrs)
return cidrs, nil
}
func encode(s string) string {
str := base64.URLEncoding.EncodeToString([]byte(s))
return strings.Replace(str, "=", "", -1)
}

View file

@ -0,0 +1,129 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ratelimit
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockBackend struct {
}
func (m mockBackend) GetDefaultBackend() defaults.Backend {
return defaults.Backend{
LimitRateAfter: 0,
LimitRate: 0,
}
}
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Error("unexpected error with ingress without annotations")
}
}
func TestBadRateLimiting(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[limitIP] = "0"
data[limitRPS] = "0"
data[limitRPM] = "0"
ing.SetAnnotations(data)
_, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error with invalid limits (0)")
}
data = map[string]string{}
data[limitIP] = "5"
data[limitRPS] = "100"
data[limitRPM] = "10"
data[limitRATEAFTER] = "100"
data[limitRATE] = "10"
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
rateLimit, ok := i.(*RateLimit)
if !ok {
t.Errorf("expected a RateLimit type")
}
if rateLimit.Connections.Limit != 5 {
t.Errorf("expected 5 in limit by ip but %v was returend", rateLimit.Connections)
}
if rateLimit.RPS.Limit != 100 {
t.Errorf("expected 100 in limit by rps but %v was returend", rateLimit.RPS)
}
if rateLimit.RPM.Limit != 10 {
t.Errorf("expected 10 in limit by rpm but %v was returend", rateLimit.RPM)
}
if rateLimit.LimitRateAfter != 100 {
t.Errorf("expected 100 in limit by limitrateafter but %v was returend", rateLimit.LimitRateAfter)
}
if rateLimit.LimitRate != 10 {
t.Errorf("expected 10 in limit by limitrate but %v was returend", rateLimit.LimitRate)
}
}

View file

@ -0,0 +1,131 @@
/*
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 redirect
import (
"net/http"
"net/url"
"strings"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/errors"
)
const (
permanent = "ingress.kubernetes.io/permanent-redirect"
temporal = "ingress.kubernetes.io/temporal-redirect"
www = "ingress.kubernetes.io/from-to-www-redirect"
)
// Redirect returns the redirect configuration for an Ingress rule
type Redirect struct {
URL string `json:"url"`
Code int `json:"code"`
FromToWWW bool `json:"fromToWWW"`
}
type redirect struct{}
// NewParser creates a new redirect annotation parser
func NewParser() parser.IngressAnnotation {
return redirect{}
}
// Parse parses the annotations contained in the ingress
// rule used to create a redirect in the paths defined in the rule.
// If the Ingress containes both annotations the execution order is
// temporal and then permanent
func (a redirect) Parse(ing *extensions.Ingress) (interface{}, error) {
r3w, _ := parser.GetBoolAnnotation(www, ing)
tr, err := parser.GetStringAnnotation(temporal, ing)
if err != nil && !errors.IsMissingAnnotations(err) {
return nil, err
}
if tr != "" {
if err := isValidURL(tr); err != nil {
return nil, err
}
return &Redirect{
URL: tr,
Code: http.StatusFound,
FromToWWW: r3w,
}, nil
}
pr, err := parser.GetStringAnnotation(permanent, ing)
if err != nil && !errors.IsMissingAnnotations(err) {
return nil, err
}
if pr != "" {
if err := isValidURL(pr); err != nil {
return nil, err
}
return &Redirect{
URL: pr,
Code: http.StatusMovedPermanently,
FromToWWW: r3w,
}, nil
}
if r3w {
return &Redirect{
FromToWWW: r3w,
}, nil
}
return nil, errors.ErrMissingAnnotations
}
// Equal tests for equality between two Redirect types
func (r1 *Redirect) Equal(r2 *Redirect) bool {
if r1 == r2 {
return true
}
if r1 == nil || r2 == nil {
return false
}
if r1.URL != r2.URL {
return false
}
if r1.Code != r2.Code {
return false
}
if r1.FromToWWW != r2.FromToWWW {
return false
}
return true
}
func isValidURL(s string) error {
u, err := url.Parse(s)
if err != nil {
return err
}
if !strings.HasPrefix(u.Scheme, "http") {
return errors.Errorf("only http and https are valid protocols (%v)", u.Scheme)
}
return nil
}

View file

@ -0,0 +1,114 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rewrite
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
rewriteTo = "ingress.kubernetes.io/rewrite-target"
addBaseURL = "ingress.kubernetes.io/add-base-url"
baseURLScheme = "ingress.kubernetes.io/base-url-scheme"
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
appRoot = "ingress.kubernetes.io/app-root"
)
// Redirect describes the per location redirect config
type Redirect struct {
// Target URI where the traffic must be redirected
Target string `json:"target"`
// AddBaseURL indicates if is required to add a base tag in the head
// of the responses from the upstream servers
AddBaseURL bool `json:"addBaseUrl"`
// BaseURLScheme override for the scheme passed to the base tag
BaseURLScheme string `json:"baseUrlScheme"`
// SSLRedirect indicates if the location section is accessible SSL only
SSLRedirect bool `json:"sslRedirect"`
// ForceSSLRedirect indicates if the location section is accessible SSL only
ForceSSLRedirect bool `json:"forceSSLRedirect"`
// AppRoot defines the Application Root that the Controller must redirect if it's not in '/' context
AppRoot string `json:"appRoot"`
}
// Equal tests for equality between two Redirect types
func (r1 *Redirect) Equal(r2 *Redirect) bool {
if r1 == r2 {
return true
}
if r1 == nil || r2 == nil {
return false
}
if r1.Target != r2.Target {
return false
}
if r1.AddBaseURL != r2.AddBaseURL {
return false
}
if r1.BaseURLScheme != r2.BaseURLScheme {
return false
}
if r1.SSLRedirect != r2.SSLRedirect {
return false
}
if r1.ForceSSLRedirect != r2.ForceSSLRedirect {
return false
}
if r1.AppRoot != r2.AppRoot {
return false
}
return true
}
type rewrite struct {
backendResolver resolver.DefaultBackend
}
// NewParser creates a new reqrite annotation parser
func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
return rewrite{br}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths
func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
if err != nil {
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
}
fSslRe, err := parser.GetBoolAnnotation(forceSSLRedirect, ing)
if err != nil {
fSslRe = a.backendResolver.GetDefaultBackend().ForceSSLRedirect
}
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
bus, _ := parser.GetStringAnnotation(baseURLScheme, ing)
ar, _ := parser.GetStringAnnotation(appRoot, ing)
return &Redirect{
Target: rt,
AddBaseURL: abu,
BaseURLScheme: bus,
SSLRedirect: sslRe,
ForceSSLRedirect: fSslRe,
AppRoot: ar,
}, nil
}

View file

@ -0,0 +1,178 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rewrite
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
)
const (
defRoute = "/demo"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockBackend struct {
redirect bool
}
func (m mockBackend) GetDefaultBackend() defaults.Backend {
return defaults.Backend{SSLRedirect: m.redirect}
}
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("unexpected error with ingress without annotations: %v", err)
}
}
func TestRedirect(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[rewriteTo] = defRoute
ing.SetAnnotations(data)
i, err := NewParser(mockBackend{}).Parse(ing)
if err != nil {
t.Errorf("Unexpected error with ingress: %v", err)
}
redirect, ok := i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if redirect.Target != defRoute {
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
}
}
func TestSSLRedirect(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[rewriteTo] = defRoute
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if !redirect.SSLRedirect {
t.Errorf("Expected true but returned false")
}
data[sslRedirect] = "false"
ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{false}).Parse(ing)
redirect, ok = i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if redirect.SSLRedirect {
t.Errorf("Expected false but returned true")
}
}
func TestForceSSLRedirect(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[rewriteTo] = defRoute
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if redirect.ForceSSLRedirect {
t.Errorf("Expected false but returned true")
}
data[forceSSLRedirect] = "true"
ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{false}).Parse(ing)
redirect, ok = i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if !redirect.ForceSSLRedirect {
t.Errorf("Expected true but returned false")
}
}
func TestAppRoot(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[appRoot] = "/app1"
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
if !ok {
t.Errorf("expected a App Context")
}
if redirect.AppRoot != "/app1" {
t.Errorf("Unexpected value got in AppRoot")
}
}

View file

@ -0,0 +1,78 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secureupstream
import (
"fmt"
"github.com/pkg/errors"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
secureUpstream = "ingress.kubernetes.io/secure-backends"
secureVerifyCASecret = "ingress.kubernetes.io/secure-verify-ca-secret"
)
// Secure describes SSL backend configuration
type Secure struct {
Secure bool `json:"secure"`
CACert resolver.AuthSSLCert `json:"caCert"`
}
type su struct {
certResolver resolver.AuthCertificate
}
// NewParser creates a new secure upstream annotation parser
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
return su{
certResolver: resolver,
}
}
// Parse parses the annotations contained in the ingress
// rule used to indicate if the upstream servers should use SSL
func (a su) Parse(ing *extensions.Ingress) (interface{}, error) {
s, _ := parser.GetBoolAnnotation(secureUpstream, ing)
ca, _ := parser.GetStringAnnotation(secureVerifyCASecret, ing)
secure := &Secure{
Secure: s,
CACert: resolver.AuthSSLCert{},
}
if !s && ca != "" {
return secure,
errors.Errorf("trying to use CA from secret %v/%v on a non secure backend", ing.Namespace, ca)
}
if ca == "" {
return secure, nil
}
caCert, err := a.certResolver.GetAuthCertificate(fmt.Sprintf("%v/%v", ing.Namespace, ca))
if err != nil {
return secure, errors.Wrap(err, "error obtaining certificate")
}
if caCert == nil {
return secure, nil
}
return &Secure{
Secure: s,
CACert: *caCert,
}, nil
}

View file

@ -0,0 +1,121 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secureupstream
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"fmt"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
type mockCfg struct {
certs map[string]resolver.AuthSSLCert
}
func (cfg mockCfg) GetAuthCertificate(secret string) (*resolver.AuthSSLCert, error) {
if cert, ok := cfg.certs[secret]; ok {
return &cert, nil
}
return nil, fmt.Errorf("secret not found: %v", secret)
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[secureUpstream] = "true"
data[secureVerifyCASecret] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{
certs: map[string]resolver.AuthSSLCert{
"default/secure-verify-ca": {},
},
}).Parse(ing)
if err != nil {
t.Errorf("Unexpected error on ingress: %v", err)
}
}
func TestSecretNotFound(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[secureUpstream] = "true"
data[secureVerifyCASecret] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{}).Parse(ing)
if err == nil {
t.Error("Expected secret not found error on ingress")
}
}
func TestSecretOnNonSecure(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[secureUpstream] = "false"
data[secureVerifyCASecret] = "secure-verify-ca"
ing.SetAnnotations(data)
_, err := NewParser(mockCfg{
certs: map[string]resolver.AuthSSLCert{
"default/secure-verify-ca": {},
},
}).Parse(ing)
if err == nil {
t.Error("Expected CA secret on non secure backend error on ingress")
}
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package serversnippet
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/server-snippet"
)
type serverSnippet struct {
}
// NewParser creates a new server snippet annotation parser
func NewParser() parser.IngressAnnotation {
return serverSnippet{}
}
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a serverSnippet) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(annotation, ing)
}

View file

@ -0,0 +1,58 @@
/*
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 serversnippet
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestParse(t *testing.T) {
ap := NewParser()
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected string
}{
{map[string]string{annotation: "more_headers"}, "more_headers"},
{map[string]string{annotation: "false"}, "false"},
{map[string]string{}, ""},
{nil, ""},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -0,0 +1,38 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package serviceupstream
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotationServiceUpstream = "ingress.kubernetes.io/service-upstream"
)
type serviceUpstream struct {
}
// NewParser creates a new serviceUpstream annotation parser
func NewParser() parser.IngressAnnotation {
return serviceUpstream{}
}
func (s serviceUpstream) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetBoolAnnotation(annotationServiceUpstream, ing)
}

View file

@ -0,0 +1,112 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package serviceupstream
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestIngressAnnotationServiceUpstreamEnabled(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[annotationServiceUpstream] = "true"
ing.SetAnnotations(data)
val, _ := NewParser().Parse(ing)
enabled, ok := val.(bool)
if !ok {
t.Errorf("expected a bool type")
}
if !enabled {
t.Errorf("expected annotation value to be true, got false")
}
}
func TestIngressAnnotationServiceUpstreamSetFalse(t *testing.T) {
ing := buildIngress()
// Test with explicitly set to false
data := map[string]string{}
data[annotationServiceUpstream] = "false"
ing.SetAnnotations(data)
val, _ := NewParser().Parse(ing)
enabled, ok := val.(bool)
if !ok {
t.Errorf("expected a bool type")
}
if enabled {
t.Errorf("expected annotation value to be false, got true")
}
// Test with no annotation specified, should default to false
data = map[string]string{}
ing.SetAnnotations(data)
val, _ = NewParser().Parse(ing)
enabled, ok = val.(bool)
if !ok {
t.Errorf("expected a bool type")
}
if enabled {
t.Errorf("expected annotation value to be false, got true")
}
}

View file

@ -0,0 +1,115 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sessionaffinity
import (
"regexp"
"github.com/golang/glog"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotationAffinityType = "ingress.kubernetes.io/affinity"
// If a cookie with this name exists,
// its value is used as an index into the list of available backends.
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
defaultAffinityCookieName = "INGRESSCOOKIE"
// This is the algorithm used by nginx to generate a value for the session cookie, if
// one isn't supplied and affinity is set to "cookie".
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
defaultAffinityCookieHash = "md5"
)
var (
affinityCookieHashRegex = regexp.MustCompile(`^(index|md5|sha1)$`)
)
// AffinityConfig describes the per ingress session affinity config
type AffinityConfig struct {
// The type of affinity that will be used
AffinityType string `json:"type"`
CookieConfig
}
// CookieConfig describes the Config of cookie type affinity
type CookieConfig struct {
// The name of the cookie that will be used in case of cookie affinity type.
Name string `json:"name"`
// The hash that will be used to encode the cookie in case of cookie affinity type
Hash string `json:"hash"`
}
// CookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
func CookieAffinityParse(ing *extensions.Ingress) *CookieConfig {
sn, err := parser.GetStringAnnotation(annotationAffinityCookieName, ing)
if err != nil || sn == "" {
glog.V(3).Infof("Ingress %v: No value found in annotation %v. Using the default %v", ing.Name, annotationAffinityCookieName, defaultAffinityCookieName)
sn = defaultAffinityCookieName
}
sh, err := parser.GetStringAnnotation(annotationAffinityCookieHash, ing)
if err != nil || !affinityCookieHashRegex.MatchString(sh) {
glog.V(3).Infof("Invalid or no annotation value found in Ingress %v: %v. Setting it to default %v", ing.Name, annotationAffinityCookieHash, defaultAffinityCookieHash)
sh = defaultAffinityCookieHash
}
return &CookieConfig{
Name: sn,
Hash: sh,
}
}
// NewParser creates a new Affinity annotation parser
func NewParser() parser.IngressAnnotation {
return affinity{}
}
type affinity struct {
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
func (a affinity) Parse(ing *extensions.Ingress) (interface{}, error) {
cookieAffinityConfig := &CookieConfig{}
// Check the type of affinity that will be used
at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
if err != nil {
at = ""
}
switch at {
case "cookie":
cookieAffinityConfig = CookieAffinityParse(ing)
default:
glog.V(3).Infof("No default affinity was found for Ingress %v", ing.Name)
}
return &AffinityConfig{
AffinityType: at,
CookieConfig: *cookieAffinityConfig,
}, nil
}

View file

@ -0,0 +1,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 sessionaffinity
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestIngressAffinityCookieConfig(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[annotationAffinityType] = "cookie"
data[annotationAffinityCookieHash] = "sha123"
data[annotationAffinityCookieName] = "INGRESSCOOKIE"
ing.SetAnnotations(data)
affin, _ := NewParser().Parse(ing)
nginxAffinity, ok := affin.(*AffinityConfig)
if !ok {
t.Errorf("expected a Config type")
}
if nginxAffinity.AffinityType != "cookie" {
t.Errorf("expected cookie as sticky-type but returned %v", nginxAffinity.AffinityType)
}
if nginxAffinity.CookieConfig.Hash != "md5" {
t.Errorf("expected md5 as sticky-hash but returned %v", nginxAffinity.CookieConfig.Hash)
}
if nginxAffinity.CookieConfig.Name != "INGRESSCOOKIE" {
t.Errorf("expected route as sticky-name but returned %v", nginxAffinity.CookieConfig.Name)
}
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package snippet
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/configuration-snippet"
)
type snippet struct {
}
// NewParser creates a new CORS annotation parser
func NewParser() parser.IngressAnnotation {
return snippet{}
}
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a snippet) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(annotation, ing)
}

View file

@ -0,0 +1,58 @@
/*
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 snippet
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestParse(t *testing.T) {
ap := NewParser()
if ap == nil {
t.Fatalf("expected a parser.IngressAnnotation but returned nil")
}
testCases := []struct {
annotations map[string]string
expected string
}{
{map[string]string{annotation: "more_headers"}, "more_headers"},
{map[string]string{annotation: "false"}, "false"},
{map[string]string{}, ""},
{nil, ""},
}
ing := &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{},
}
for _, testCase := range testCases {
ing.SetAnnotations(testCase.annotations)
result, _ := ap.Parse(ing)
if result != testCase.expected {
t.Errorf("expected %v but returned %v, annotations: %s", testCase.expected, result, testCase.annotations)
}
}
}

View file

@ -0,0 +1,46 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sslpassthrough
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
ing_errors "k8s.io/ingress-nginx/pkg/ingress/errors"
)
const (
passthrough = "ingress.kubernetes.io/ssl-passthrough"
)
type sslpt struct {
}
// NewParser creates a new SSL passthrough annotation parser
func NewParser() parser.IngressAnnotation {
return sslpt{}
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if is required to configure
func (a sslpt) Parse(ing *extensions.Ingress) (interface{}, error) {
if ing.GetAnnotations() == nil {
return false, ing_errors.ErrMissingAnnotations
}
return parser.GetBoolAnnotation(passthrough, ing)
}

View file

@ -0,0 +1,78 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sslpassthrough
import (
"testing"
api "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
},
}
}
func TestParseAnnotations(t *testing.T) {
ing := buildIngress()
_, err := NewParser().Parse(ing)
if err == nil {
t.Errorf("unexpected error: %v", err)
}
data := map[string]string{}
data[passthrough] = "true"
ing.SetAnnotations(data)
// test ingress using the annotation without a TLS section
_, err = NewParser().Parse(ing)
if err != nil {
t.Errorf("unexpected error parsing ingress with sslpassthrough")
}
// test with a valid host
ing.Spec.TLS = []extensions.IngressTLS{
{
Hosts: []string{"foo.bar.com"},
},
}
i, err := NewParser().Parse(ing)
if err != nil {
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")
}
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package upstreamvhost
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/upstream-vhost"
)
type upstreamVhost struct {
}
// NewParser creates a new upstream VHost annotation parser
func NewParser() parser.IngressAnnotation {
return upstreamVhost{}
}
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a upstreamVhost) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(annotation, ing)
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package vtsfilterkey
import (
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
const (
annotation = "ingress.kubernetes.io/vts-filter-key"
)
type vtsFilterKey struct {
}
// NewParser creates a new vts filter key annotation parser
func NewParser() parser.IngressAnnotation {
return vtsFilterKey{}
}
// Parse parses the annotations contained in the ingress rule
// used to indicate if the location/s contains a fragment of
// configuration to be included inside the paths of the rules
func (a vtsFilterKey) Parse(ing *extensions.Ingress) (interface{}, error) {
return parser.GetStringAnnotation(annotation, ing)
}