Split implementations from generic code

This commit is contained in:
Manuel de Brito Fontes 2016-11-10 19:56:29 -03:00
parent d1e8a629ca
commit ed9a416b01
107 changed files with 5777 additions and 3546 deletions

View file

@ -0,0 +1,122 @@
/*
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 (
"errors"
"fmt"
"io/ioutil"
"os"
"regexp"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
authType = "ingress.kubernetes.io/auth-type"
authSecret = "ingress.kubernetes.io/auth-secret"
authRealm = "ingress.kubernetes.io/auth-realm"
// DefAuthDirectory default directory used to store files
// to authenticate request
DefAuthDirectory = "/etc/ingress-controller/auth"
)
func init() {
// TODO: check permissions required
os.MkdirAll(DefAuthDirectory, 0655)
}
var (
authTypeRegex = regexp.MustCompile(`basic|digest`)
// ErrInvalidAuthType is return in case of unsupported authentication type
ErrInvalidAuthType = errors.New("invalid authentication type")
// ErrMissingSecretName is returned when the name of the secret is missing
ErrMissingSecretName = errors.New("secret name is missing")
// ErrMissingAuthInSecret is returned when there is no auth key in secret data
ErrMissingAuthInSecret = errors.New("the secret does not contains the auth key")
)
// BasicDigest returns authentication configuration for an Ingress rule
type BasicDigest struct {
Type string
Realm string
File string
Secured bool
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to add authentication in the paths defined in the rule
// and generated an htpasswd compatible file to be used as source
// during the authentication process
func ParseAnnotations(ing *extensions.Ingress, authDir string, fn func(string) (*api.Secret, error)) (*BasicDigest, error) {
if ing.GetAnnotations() == nil {
return &BasicDigest{}, parser.ErrMissingAnnotations
}
at, err := parser.GetStringAnnotation(authType, ing)
if err != nil {
return &BasicDigest{}, err
}
if !authTypeRegex.MatchString(at) {
return &BasicDigest{}, ErrInvalidAuthType
}
s, err := parser.GetStringAnnotation(authSecret, ing)
if err != nil {
return &BasicDigest{}, err
}
secret, err := fn(fmt.Sprintf("%v/%v", ing.Namespace, s))
if err != nil {
return &BasicDigest{}, err
}
realm, _ := parser.GetStringAnnotation(authRealm, ing)
passFile := fmt.Sprintf("%v/%v-%v.passwd", authDir, ing.GetNamespace(), ing.GetName())
err = dumpSecret(passFile, secret)
if err != nil {
return &BasicDigest{}, err
}
return &BasicDigest{
Type: at,
Realm: realm,
File: passFile,
Secured: true,
}, 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 ErrMissingAuthInSecret
}
// TODO: check permissions required
return ioutil.WriteFile(filename, val, 0777)
}

View file

@ -0,0 +1,143 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func mockSecret(name string) (*api.Secret, error) {
return &api.Secret{
ObjectMeta: api.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()
_, err := ParseAnnotations(ing, "", mockSecret)
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)
auth, err := ParseAnnotations(ing, dir, mockSecret)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
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 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("demo")
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,101 @@
/*
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"
"net/url"
"strings"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
// external URL that provides the authentication
authURL = "ingress.kubernetes.io/auth-url"
authMethod = "ingress.kubernetes.io/auth-method"
authBody = "ingress.kubernetes.io/auth-send-body"
)
// External returns external authentication configuration for an Ingress rule
type External struct {
URL string
Method string
SendBody bool
}
var (
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
)
func validMethod(method string) bool {
if len(method) == 0 {
return false
}
for _, m := range methods {
if method == m {
return true
}
}
return false
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to use an external URL as source for authentication
func ParseAnnotations(ing *extensions.Ingress) (External, error) {
if ing.GetAnnotations() == nil {
return External{}, parser.ErrMissingAnnotations
}
str, err := parser.GetStringAnnotation(authURL, ing)
if err != nil {
return External{}, err
}
if str == "" {
return External{}, fmt.Errorf("an empty string is not a valid URL")
}
ur, err := url.Parse(str)
if err != nil {
return External{}, err
}
if ur.Scheme == "" {
return External{}, fmt.Errorf("url scheme is empty")
}
if ur.Host == "" {
return External{}, fmt.Errorf("url host is empty")
}
if strings.Contains(ur.Host, "..") {
return External{}, fmt.Errorf("invalid url host")
}
m, _ := parser.GetStringAnnotation(authMethod, ing)
if len(m) != 0 && !validMethod(m) {
return External{}, fmt.Errorf("invalid HTTP method")
}
sb, _ := parser.GetBoolAnnotation(authBody, ing)
return External{
URL: str,
Method: m,
SendBody: sb,
}, nil
}

View file

@ -0,0 +1,109 @@
/*
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"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
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[authURL] = test.url
data[authBody] = fmt.Sprintf("%v", test.sendBody)
data[authMethod] = fmt.Sprintf("%v", test.method)
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,65 @@
/*
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 (
"fmt"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/k8s"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
// name of the secret
authTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
)
// SSLCert returns external authentication configuration for an Ingress rule
type SSLCert struct {
Secret string
CertFileName string
KeyFileName string
CAFileName string
PemSHA string
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to use an external URL as source for authentication
func ParseAnnotations(ing *extensions.Ingress,
fn func(secret string) (*SSLCert, error)) (*SSLCert, error) {
if ing.GetAnnotations() == nil {
return &SSLCert{}, parser.ErrMissingAnnotations
}
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
if err != nil {
return &SSLCert{}, err
}
if str == "" {
return &SSLCert{}, fmt.Errorf("an empty string is not a valid secret name")
}
_, _, err = k8s.ParseNameNS(str)
if err != nil {
return &SSLCert{}, err
}
return fn(str)
}

View file

@ -0,0 +1,107 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
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,33 @@
/*
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 (
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
cors = "ingress.kubernetes.io/enable-cors"
)
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if the location/s should allows CORS
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
return parser.GetBoolAnnotation(cors, ing)
}

View file

@ -0,0 +1,56 @@
/*
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 (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
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
FailTimeout int
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure upstream check parameters
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Upstream {
if ing.GetAnnotations() == nil {
return &Upstream{cfg.UpstreamMaxFails, cfg.UpstreamFailTimeout}
}
mf, err := parser.GetIntAnnotation(upsMaxFails, ing)
if err != nil {
mf = cfg.UpstreamMaxFails
}
ft, err := parser.GetIntAnnotation(upsFailTimeout, ing)
if err != nil {
ft = cfg.UpstreamFailTimeout
}
return &Upstream{mf, ft}
}

View file

@ -0,0 +1,82 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestIngressHealthCheck(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[upsMaxFails] = "2"
ing.SetAnnotations(data)
cfg := defaults.Backend{UpstreamFailTimeout: 1}
nginxHz := ParseAnnotations(cfg, ing)
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,72 @@
/*
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 (
"errors"
"strings"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/net/sets"
)
const (
whitelist = "ingress.kubernetes.io/whitelist-source-range"
)
var (
// ErrInvalidCIDR returned error when the whitelist annotation does not
// contains a valid IP or network address
ErrInvalidCIDR = errors.New("the annotation does not contains a valid IP address or network")
)
// SourceRange returns the CIDR
type SourceRange struct {
CIDR []string
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to limit access to certain client addresses or networks.
// Multiple ranges can specified using commas as separator
// e.g. `18.0.0.0/8,56.0.0.0/8`
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*SourceRange, error) {
cidrs := []string{}
if ing.GetAnnotations() == nil {
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, parser.ErrMissingAnnotations
}
val, err := parser.GetStringAnnotation(whitelist, ing)
if err != nil {
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, err
}
values := strings.Split(val, ",")
ipnets, err := sets.ParseIPNets(values...)
if err != nil {
return &SourceRange{CIDR: cfg.WhitelistSourceRange}, ErrInvalidCIDR
}
for k := range ipnets {
cidrs = append(cidrs, k)
}
return &SourceRange{cidrs}, nil
}

View file

@ -0,0 +1,109 @@
/*
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 (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestParseAnnotations(t *testing.T) {
ing := buildIngress()
testNet := "10.0.0.0/24"
enet := []string{testNet}
data := map[string]string{}
data[whitelist] = testNet
ing.SetAnnotations(data)
expected := &SourceRange{
CIDR: enet,
}
sr, err := ParseAnnotations(defaults.Backend{}, ing)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(sr, expected) {
t.Errorf("Expected %v but returned %s", sr, expected)
}
data[whitelist] = "www"
ing.SetAnnotations(data)
_, err = ParseAnnotations(defaults.Backend{}, ing)
if err == nil {
t.Errorf("Expected error parsing an invalid cidr")
}
delete(data, "whitelist")
ing.SetAnnotations(data)
//sr, _ = ParseAnnotations(defaults.Backend{}, ing)
// TODO: fix test
/*
if !reflect.DeepEqual(sr.CIDR, []string{}) {
t.Errorf("Expected empty CIDR but %v returned", sr.CIDR)
}
sr, _ = ParseAnnotations(defaults.Upstream{}, &extensions.Ingress{})
if !reflect.DeepEqual(sr.CIDR, []string{}) {
t.Errorf("Expected empty CIDR but %v returned", sr.CIDR)
}
*/
}

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 (
"errors"
"fmt"
"strconv"
"k8s.io/kubernetes/pkg/apis/extensions"
)
var (
// ErrMissingAnnotations is returned when the ingress rule
// does not contains annotations related with rate limit
ErrMissingAnnotations = errors.New("Ingress rule without annotations")
// ErrInvalidName ...
ErrInvalidName = errors.New("invalid annotation name")
)
type ingAnnotations map[string]string
func (a ingAnnotations) parseBool(name string) (bool, error) {
val, ok := a[name]
if ok {
if b, err := strconv.ParseBool(val); err == nil {
return b, nil
}
}
return false, ErrMissingAnnotations
}
func (a ingAnnotations) parseString(name string) (string, error) {
val, ok := a[name]
if ok {
return val, nil
}
return "", 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, fmt.Errorf("invalid annotations value: %v", err)
}
return i, nil
}
return 0, ErrMissingAnnotations
}
// GetBoolAnnotation ...
func GetBoolAnnotation(name string, ing *extensions.Ingress) (bool, error) {
if ing == nil || ing.GetAnnotations() == nil {
return false, ErrMissingAnnotations
}
if name == "" {
return false, ErrInvalidName
}
return ingAnnotations(ing.GetAnnotations()).parseBool(name)
}
// GetStringAnnotation ...
func GetStringAnnotation(name string, ing *extensions.Ingress) (string, error) {
if ing == nil || ing.GetAnnotations() == nil {
return "", ErrMissingAnnotations
}
if name == "" {
return "", ErrInvalidName
}
return ingAnnotations(ing.GetAnnotations()).parseString(name)
}
// GetIntAnnotation ...
func GetIntAnnotation(name string, ing *extensions.Ingress) (int, error) {
if ing == nil || ing.GetAnnotations() == nil {
return 0, ErrMissingAnnotations
}
if name == "" {
return 0, ErrInvalidName
}
return ingAnnotations(ing.GetAnnotations()).parseInt(name)
}

View file

@ -0,0 +1,154 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
ObjectMeta: api.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)
}
}
}
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)
}
}
}
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)
}
}
}

View file

@ -0,0 +1,74 @@
/*
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 (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
const (
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"
)
// Configuration returns the proxy timeout to use in the upstream server/s
type Configuration struct {
ConnectTimeout int
SendTimeout int
ReadTimeout int
BufferSize string
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure upstream check parameters
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) *Configuration {
if ing == nil || ing.GetAnnotations() == nil {
return &Configuration{
cfg.ProxyConnectTimeout,
cfg.ProxySendTimeout,
cfg.ProxyReadTimeout,
cfg.ProxyBufferSize,
}
}
ct, err := parser.GetIntAnnotation(connect, ing)
if err != nil {
ct = cfg.ProxyConnectTimeout
}
st, err := parser.GetIntAnnotation(send, ing)
if err != nil {
st = cfg.ProxySendTimeout
}
rt, err := parser.GetIntAnnotation(read, ing)
if err != nil {
rt = cfg.ProxyReadTimeout
}
bs, err := parser.GetStringAnnotation(bufferSize, ing)
if err != nil || bs == "" {
bs = cfg.ProxyBufferSize
}
return &Configuration{ct, st, rt, bs}
}

View file

@ -0,0 +1,90 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestIngressHealthCheck(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[connect] = "1"
data[send] = "2"
data[read] = "3"
data[bufferSize] = "1k"
ing.SetAnnotations(data)
cfg := defaults.Backend{UpstreamFailTimeout: 1}
p := ParseAnnotations(cfg, ing)
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)
}
}

View file

@ -0,0 +1,99 @@
/*
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 (
"errors"
"fmt"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
limitIP = "ingress.kubernetes.io/limit-connections"
limitRPS = "ingress.kubernetes.io/limit-rps"
// 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
)
var (
// ErrInvalidRateLimit is returned when the annotation caontains invalid values
ErrInvalidRateLimit = errors.New("invalid rate limit value. Must be > 0")
)
// RateLimit returns rate limit configuration for an Ingress rule
// Is possible to limit the number of connections per IP address or
// connections per second.
// Note: Is possible to specify both limits
type RateLimit struct {
// Connections indicates a limit with the number of connections per IP address
Connections Zone
// RPS indicates a limit with the number of connections per second
RPS Zone
}
// 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
Limit int
Burst int
// SharedSize amount of shared memory for the zone
SharedSize int
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths
func ParseAnnotations(ing *extensions.Ingress) (*RateLimit, error) {
if ing.GetAnnotations() == nil {
return &RateLimit{}, parser.ErrMissingAnnotations
}
rps, _ := parser.GetIntAnnotation(limitRPS, ing)
conn, _ := parser.GetIntAnnotation(limitIP, ing)
if rps == 0 && conn == 0 {
return &RateLimit{
Connections: Zone{},
RPS: Zone{},
}, ErrInvalidRateLimit
}
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: conn * defBurst,
SharedSize: defSharedSize,
},
}, nil
}

View file

@ -0,0 +1,100 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
}
func TestBadRateLimiting(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[limitIP] = "0"
data[limitRPS] = "0"
ing.SetAnnotations(data)
_, err := ParseAnnotations(ing)
if err == nil {
t.Errorf("Expected error with invalid limits (0)")
}
data = map[string]string{}
data[limitIP] = "5"
data[limitRPS] = "100"
ing.SetAnnotations(data)
rateLimit, err := ParseAnnotations(ing)
if err != nil {
t.Errorf("Uxpected error: %v", err)
}
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)
}
}

View file

@ -0,0 +1,64 @@
/*
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 (
"errors"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
rewriteTo = "ingress.kubernetes.io/rewrite-target"
addBaseURL = "ingress.kubernetes.io/add-base-url"
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
)
// Redirect describes the per location redirect config
type Redirect struct {
// Target URI where the traffic must be redirected
Target string
// AddBaseURL indicates if is required to add a base tag in the head
// of the responses from the upstream servers
AddBaseURL bool
// SSLRedirect indicates if the location section is accessible SSL only
SSLRedirect bool
}
// ParseAnnotations parses the annotations contained in the ingress
// rule used to rewrite the defined paths
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (*Redirect, error) {
if ing.GetAnnotations() == nil {
return &Redirect{}, errors.New("no annotations present")
}
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
if err != nil {
sslRe = cfg.SSLRedirect
}
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
return &Redirect{
Target: rt,
AddBaseURL: abu,
SSLRedirect: sslRe,
}, nil
}

View file

@ -0,0 +1,116 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
const (
defRoute = "/demo"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(defaults.Backend{}, ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
}
func TestRedirect(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[rewriteTo] = defRoute
ing.SetAnnotations(data)
redirect, err := ParseAnnotations(defaults.Backend{}, ing)
if err != nil {
t.Errorf("Uxpected error with ingress: %v", err)
}
if redirect.Target != defRoute {
t.Errorf("Expected %v as redirect but returned %s", defRoute, redirect.Target)
}
}
func TestSSLRedirect(t *testing.T) {
ing := buildIngress()
cfg := defaults.Backend{SSLRedirect: true}
data := map[string]string{}
ing.SetAnnotations(data)
redirect, _ := ParseAnnotations(cfg, ing)
if !redirect.SSLRedirect {
t.Errorf("Expected true but returned false")
}
data[sslRedirect] = "false"
ing.SetAnnotations(data)
redirect, _ = ParseAnnotations(cfg, ing)
if redirect.SSLRedirect {
t.Errorf("Expected false but returned true")
}
}

View file

@ -0,0 +1,33 @@
/*
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 (
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
secureUpstream = "ingress.kubernetes.io/secure-backends"
)
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if the upstream servers should use SSL
func ParseAnnotations(ing *extensions.Ingress) (bool, error) {
return parser.GetBoolAnnotation(secureUpstream, ing)
}

View file

@ -0,0 +1,80 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
},
Rules: []extensions.IngressRule{
{
Host: "foo.bar.com",
IngressRuleValue: extensions.IngressRuleValue{
HTTP: &extensions.HTTPIngressRuleValue{
Paths: []extensions.HTTPIngressPath{
{
Path: "/foo",
Backend: defaultBackend,
},
},
},
},
},
},
},
}
}
func TestAnnotations(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[secureUpstream] = "true"
ing.SetAnnotations(data)
_, err := ParseAnnotations(ing)
if err != nil {
t.Error("Expected error with ingress without annotations")
}
}
func TestWithoutAnnotations(t *testing.T) {
ing := buildIngress()
_, err := ParseAnnotations(ing)
if err == nil {
t.Error("Expected error with ingress without annotations")
}
}

View file

@ -0,0 +1,73 @@
/*
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 service
import (
"encoding/json"
"fmt"
"strconv"
"k8s.io/kubernetes/pkg/api"
"github.com/golang/glog"
)
const (
// NamedPortAnnotation annotation used to map named port in services
NamedPortAnnotation = "ingress.kubernetes.io/named-ports"
)
type namedPortMapping map[string]string
// getPort returns the port defined in a named port
func (npm namedPortMapping) getPort(name string) (string, bool) {
val, ok := npm.getPortMappings()[name]
return val, ok
}
// getPortMappings returns a map containing the mapping of named ports names and number
func (npm namedPortMapping) getPortMappings() map[string]string {
data := npm[NamedPortAnnotation]
var mapping map[string]string
if data == "" {
return mapping
}
if err := json.Unmarshal([]byte(data), &mapping); err != nil {
glog.Errorf("unexpected error reading annotations: %v", err)
}
return mapping
}
// GetPortMapping returns the number of the named port or an error if is not valid
func GetPortMapping(name string, s *api.Service) (int32, error) {
if s == nil {
return -1, fmt.Errorf("impossible to extract por mapping from %v (missing service)", name)
}
namedPorts := s.ObjectMeta.Annotations
val, ok := namedPortMapping(namedPorts).getPort(name)
if ok {
port, err := strconv.Atoi(val)
if err != nil {
return -1, fmt.Errorf("service %v contains an invalid port mapping for %v (%v), %v", s.Name, name, val, err)
}
return int32(port), nil
}
return -1, fmt.Errorf("there is no port with name %v", name)
}

View file

@ -0,0 +1,45 @@
/*
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 (
"fmt"
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/kubernetes/pkg/apis/extensions"
)
const (
passthrough = "ingress.kubernetes.io/ssl-passthrough"
)
// ParseAnnotations parses the annotations contained in the ingress
// rule used to indicate if is required to configure
func ParseAnnotations(cfg defaults.Backend, ing *extensions.Ingress) (bool, error) {
if ing.GetAnnotations() == nil {
return false, parser.ErrMissingAnnotations
}
if len(ing.Spec.TLS) == 0 {
return false, fmt.Errorf("ingres rule %v/%v does not contains a TLS section", ing.Name, ing.Namespace)
}
return parser.GetBoolAnnotation(passthrough, ing)
}

View file

@ -0,0 +1,74 @@
/*
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"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/ingress/core/pkg/ingress/defaults"
)
func buildIngress() *extensions.Ingress {
return &extensions.Ingress{
ObjectMeta: api.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 := ParseAnnotations(defaults.Backend{}, 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
val, err := ParseAnnotations(defaults.Backend{}, ing)
if err == nil {
t.Errorf("expected error parsing an invalid cidr")
}
// test with a valid host
ing.Spec.TLS = []extensions.IngressTLS{
{
Hosts: []string{"foo.bar.com"},
},
}
val, err = ParseAnnotations(defaults.Backend{}, ing)
if err != nil {
t.Errorf("expected error parsing an invalid cidr")
}
if !val {
t.Errorf("expected true but false returned")
}
}