Merge branch 'master' into nginx/extauth_headers
This commit is contained in:
commit
c8eda8f17f
89 changed files with 3309 additions and 379 deletions
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
|
@ -59,8 +60,17 @@ type auth struct {
|
|||
|
||||
// NewParser creates a new authentication annotation parser
|
||||
func NewParser(authDirectory string, sr resolver.Secret) parser.IngressAnnotation {
|
||||
// TODO: check permissions required
|
||||
os.MkdirAll(authDirectory, 0655)
|
||||
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}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,11 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|||
return nil, ing_errors.NewLocationDenied("invalid url host")
|
||||
}
|
||||
|
||||
m, _ := parser.GetStringAnnotation(authMethod, ing)
|
||||
m, err := parser.GetStringAnnotation(authMethod, ing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(m) != 0 && !validMethod(m) {
|
||||
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,16 @@ import (
|
|||
|
||||
const (
|
||||
// name of the secret
|
||||
authTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
annotationAuthTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
annotationAuthTLSDepth = "ingress.kubernetes.io/auth-tls-verify-depth"
|
||||
defaultAuthTLSDepth = 1
|
||||
)
|
||||
|
||||
type authTLS struct {
|
||||
certResolver resolver.AuthCertificate
|
||||
// AuthSSLConfig contains the AuthSSLCert used for muthual autentication
|
||||
// and the configured ValidationDepth
|
||||
type AuthSSLConfig struct {
|
||||
AuthSSLCert resolver.AuthSSLCert
|
||||
ValidationDepth int `json:"validationDepth"`
|
||||
}
|
||||
|
||||
// NewParser creates a new TLS authentication annotation parser
|
||||
|
|
@ -40,29 +45,42 @@ func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
|
|||
return authTLS{resolver}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to use an external URL as source for authentication
|
||||
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) {
|
||||
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
|
||||
|
||||
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &AuthSSLConfig{}, err
|
||||
}
|
||||
|
||||
if str == "" {
|
||||
return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
if tlsauthsecret == "" {
|
||||
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
}
|
||||
|
||||
_, _, err = k8s.ParseNameNS(str)
|
||||
_, _, err = k8s.ParseNameNS(tlsauthsecret)
|
||||
if err != nil {
|
||||
return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
}
|
||||
|
||||
authCert, err := a.certResolver.GetAuthCertificate(str)
|
||||
tlsdepth, err := parser.GetIntAnnotation(annotationAuthTLSDepth, ing)
|
||||
if err != nil || tlsdepth == 0 {
|
||||
tlsdepth = defaultAuthTLSDepth
|
||||
}
|
||||
|
||||
authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return &AuthSSLConfig{}, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrap(err, "error obtaining certificate"),
|
||||
}
|
||||
}
|
||||
|
||||
return authCert, nil
|
||||
return &AuthSSLConfig{
|
||||
AuthSSLCert: *authCert,
|
||||
ValidationDepth: tlsdepth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import (
|
|||
)
|
||||
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
// Configuration returns the proxy timeout to use in the upstream server/s
|
||||
|
|
@ -38,6 +40,8 @@ type Configuration struct {
|
|||
SendTimeout int `json:"sendTimeout"`
|
||||
ReadTimeout int `json:"readTimeout"`
|
||||
BufferSize string `json:"bufferSize"`
|
||||
CookieDomain string `json:"cookieDomain"`
|
||||
CookiePath string `json:"cookiePath"`
|
||||
}
|
||||
|
||||
type proxy struct {
|
||||
|
|
@ -73,10 +77,20 @@ func (a proxy) Parse(ing *extensions.Ingress) (interface{}, error) {
|
|||
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
|
||||
}
|
||||
|
||||
return &Configuration{bs, ct, st, rt, bufs}, nil
|
||||
return &Configuration{bs, ct, st, rt, bufs, cd, cp}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,11 +52,7 @@ func NewParser(br resolver.DefaultBackend) parser.IngressAnnotation {
|
|||
// 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, err := parser.GetStringAnnotation(rewriteTo, ing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rt, _ := parser.GetStringAnnotation(rewriteTo, ing)
|
||||
sslRe, err := parser.GetBoolAnnotation(sslRedirect, ing)
|
||||
if err != nil {
|
||||
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ func (m mockBackend) GetDefaultBackend() defaults.Backend {
|
|||
func TestWithoutAnnotations(t *testing.T) {
|
||||
ing := buildIngress()
|
||||
_, err := NewParser(mockBackend{}).Parse(ing)
|
||||
if err == nil {
|
||||
t.Error("Expected error with ingress without annotations")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with ingress without annotations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
118
core/pkg/ingress/annotations/sessionaffinity/main.go
Normal file
118
core/pkg/ingress/annotations/sessionaffinity/main.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
|
||||
"k8s.io/ingress/core/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) {
|
||||
|
||||
var cookieAffinityConfig *CookieConfig
|
||||
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
|
||||
|
||||
}
|
||||
88
core/pkg/ingress/annotations/sessionaffinity/main_test.go
Normal file
88
core/pkg/ingress/annotations/sessionaffinity/main_test.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"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 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)
|
||||
}
|
||||
}
|
||||
42
core/pkg/ingress/annotations/snippet/main.go
Normal file
42
core/pkg/ingress/annotations/snippet/main.go
Normal 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 (
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
|
||||
"k8s.io/ingress/core/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)
|
||||
}
|
||||
57
core/pkg/ingress/annotations/snippet/main_test.go
Normal file
57
core/pkg/ingress/annotations/snippet/main_test.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
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: api.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,8 @@ import (
|
|||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/secureupstream"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/sessionaffinity"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/snippet"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/sslpassthrough"
|
||||
"k8s.io/ingress/core/pkg/ingress/errors"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
|
|
@ -51,18 +53,20 @@ type annotationExtractor struct {
|
|||
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
|
||||
return annotationExtractor{
|
||||
map[string]parser.IngressAnnotation{
|
||||
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
||||
"ExternalAuth": authreq.NewParser(),
|
||||
"CertificateAuth": authtls.NewParser(cfg),
|
||||
"EnableCORS": cors.NewParser(),
|
||||
"HealthCheck": healthcheck.NewParser(cfg),
|
||||
"Whitelist": ipwhitelist.NewParser(cfg),
|
||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||
"Proxy": proxy.NewParser(cfg),
|
||||
"RateLimit": ratelimit.NewParser(),
|
||||
"Redirect": rewrite.NewParser(cfg),
|
||||
"SecureUpstream": secureupstream.NewParser(),
|
||||
"SSLPassthrough": sslpassthrough.NewParser(),
|
||||
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
|
||||
"ExternalAuth": authreq.NewParser(),
|
||||
"CertificateAuth": authtls.NewParser(cfg),
|
||||
"EnableCORS": cors.NewParser(),
|
||||
"HealthCheck": healthcheck.NewParser(cfg),
|
||||
"Whitelist": ipwhitelist.NewParser(cfg),
|
||||
"UsePortInRedirects": portinredirect.NewParser(cfg),
|
||||
"Proxy": proxy.NewParser(cfg),
|
||||
"RateLimit": ratelimit.NewParser(),
|
||||
"Redirect": rewrite.NewParser(cfg),
|
||||
"SecureUpstream": secureupstream.NewParser(),
|
||||
"SessionAffinity": sessionaffinity.NewParser(),
|
||||
"SSLPassthrough": sslpassthrough.NewParser(),
|
||||
"ConfigurationSnippet": snippet.NewParser(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -96,9 +100,10 @@ func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interf
|
|||
}
|
||||
|
||||
const (
|
||||
secureUpstream = "SecureUpstream"
|
||||
healthCheck = "HealthCheck"
|
||||
sslPassthrough = "SSLPassthrough"
|
||||
secureUpstream = "SecureUpstream"
|
||||
healthCheck = "HealthCheck"
|
||||
sslPassthrough = "SSLPassthrough"
|
||||
sessionAffinity = "SessionAffinity"
|
||||
)
|
||||
|
||||
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) bool {
|
||||
|
|
@ -115,3 +120,8 @@ func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool {
|
|||
val, _ := e.annotations[sslPassthrough].Parse(ing)
|
||||
return val.(bool)
|
||||
}
|
||||
|
||||
func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig {
|
||||
val, _ := e.annotations[sessionAffinity].Parse(ing)
|
||||
return val.(*sessionaffinity.AffinityConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
annotationSecureUpstream = "ingress.kubernetes.io/secure-backends"
|
||||
annotationUpsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
|
||||
annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
||||
annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough"
|
||||
annotationSecureUpstream = "ingress.kubernetes.io/secure-backends"
|
||||
annotationUpsMaxFails = "ingress.kubernetes.io/upstream-max-fails"
|
||||
annotationUpsFailTimeout = "ingress.kubernetes.io/upstream-fail-timeout"
|
||||
annotationPassthrough = "ingress.kubernetes.io/ssl-passthrough"
|
||||
annotationAffinityType = "ingress.kubernetes.io/affinity"
|
||||
annotationAffinityCookieName = "ingress.kubernetes.io/session-cookie-name"
|
||||
annotationAffinityCookieHash = "ingress.kubernetes.io/session-cookie-hash"
|
||||
)
|
||||
|
||||
type mockCfg struct {
|
||||
|
|
@ -179,3 +182,39 @@ func TestSSLPassthrough(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffinitySession(t *testing.T) {
|
||||
ec := newAnnotationExtractor(mockCfg{})
|
||||
ing := buildIngress()
|
||||
|
||||
fooAnns := []struct {
|
||||
annotations map[string]string
|
||||
affinitytype string
|
||||
hash string
|
||||
name string
|
||||
}{
|
||||
{map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieHash: "md5", annotationAffinityCookieName: "route"}, "cookie", "md5", "route"},
|
||||
{map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieHash: "xpto", annotationAffinityCookieName: "route1"}, "cookie", "md5", "route1"},
|
||||
{map[string]string{annotationAffinityType: "cookie", annotationAffinityCookieHash: "", annotationAffinityCookieName: ""}, "cookie", "md5", "INGRESSCOOKIE"},
|
||||
{map[string]string{}, "", "", ""},
|
||||
{nil, "", "", ""},
|
||||
}
|
||||
|
||||
for _, foo := range fooAnns {
|
||||
ing.SetAnnotations(foo.annotations)
|
||||
r := ec.SessionAffinity(ing)
|
||||
t.Logf("Testing pass %v %v %v", foo.affinitytype, foo.hash, foo.name)
|
||||
if r == nil {
|
||||
t.Errorf("Returned nil but expected a SessionAffinity.AffinityConfig")
|
||||
continue
|
||||
}
|
||||
|
||||
if r.CookieConfig.Hash != foo.hash {
|
||||
t.Errorf("Returned %v but expected %v for Hash", r.CookieConfig.Hash, foo.hash)
|
||||
}
|
||||
|
||||
if r.CookieConfig.Name != foo.name {
|
||||
t.Errorf("Returned %v but expected %v for Name", r.CookieConfig.Name, foo.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ func (ic *GenericController) syncSecret(k interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getPemCertificate receives a secret, and creates a ingress.SSLCert as return.
|
||||
// It parses the secret and verifies if it's a keypair, or a 'ca.crt' secret only.
|
||||
func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
|
||||
secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName)
|
||||
if err != nil {
|
||||
|
|
@ -108,19 +110,24 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
|
|||
}
|
||||
|
||||
secret := secretInterface.(*api.Secret)
|
||||
cert, ok := secret.Data[api.TLSCertKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret named %v has no private key", secretName)
|
||||
}
|
||||
key, ok := secret.Data[api.TLSPrivateKeyKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret named %v has no cert", secretName)
|
||||
}
|
||||
cert, okcert := secret.Data[api.TLSCertKey]
|
||||
key, okkey := secret.Data[api.TLSPrivateKeyKey]
|
||||
|
||||
ca := secret.Data["ca.crt"]
|
||||
|
||||
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
||||
s, err := ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
|
||||
|
||||
var s *ingress.SSLCert
|
||||
if okcert && okkey {
|
||||
glog.V(3).Infof("Found certificate and private key, configuring %v as a TLS Secret", secretName)
|
||||
s, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
|
||||
} else if ca != nil {
|
||||
glog.V(3).Infof("Found only ca.crt, configuring %v as an Certificate Authentication secret", secretName)
|
||||
s, err = ssl.AddCertAuth(nsSecName, ca)
|
||||
} else {
|
||||
return nil, fmt.Errorf("No keypair or CA cert could be found in %v", secretName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,11 +76,13 @@ type GenericController struct {
|
|||
ingController *cache.Controller
|
||||
endpController *cache.Controller
|
||||
svcController *cache.Controller
|
||||
nodeController *cache.Controller
|
||||
secrController *cache.Controller
|
||||
mapController *cache.Controller
|
||||
|
||||
ingLister cache_store.StoreToIngressLister
|
||||
svcLister cache.StoreToServiceLister
|
||||
nodeLister cache.StoreToNodeLister
|
||||
endpLister cache.StoreToEndpointsLister
|
||||
secrLister cache_store.StoreToSecretsLister
|
||||
mapLister cache_store.StoreToConfigmapLister
|
||||
|
|
@ -132,6 +134,7 @@ type Configuration struct {
|
|||
Backend ingress.Controller
|
||||
|
||||
UpdateStatus bool
|
||||
ElectionID string
|
||||
}
|
||||
|
||||
// newIngressController creates an Ingress controller
|
||||
|
|
@ -173,7 +176,7 @@ func newIngressController(config *Configuration) *GenericController {
|
|||
DeleteFunc: func(obj interface{}) {
|
||||
delIng := obj.(*extensions.Ingress)
|
||||
if !IsValidClass(delIng, config.IngressClass) {
|
||||
glog.Infof("ignoring add for ingress %v based on annotation %v", delIng.Name, ingressClassKey)
|
||||
glog.Infof("ignoring delete for ingress %v based on annotation %v", delIng.Name, ingressClassKey)
|
||||
return
|
||||
}
|
||||
ic.recorder.Eventf(delIng, api.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", delIng.Namespace, delIng.Name))
|
||||
|
|
@ -182,7 +185,7 @@ func newIngressController(config *Configuration) *GenericController {
|
|||
UpdateFunc: func(old, cur interface{}) {
|
||||
oldIng := old.(*extensions.Ingress)
|
||||
curIng := cur.(*extensions.Ingress)
|
||||
if !IsValidClass(curIng, config.IngressClass) {
|
||||
if !IsValidClass(curIng, config.IngressClass) && !IsValidClass(oldIng, config.IngressClass) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -292,11 +295,16 @@ func newIngressController(config *Configuration) *GenericController {
|
|||
cache.ResourceEventHandlerFuncs{},
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
|
||||
|
||||
ic.nodeLister.Store, ic.nodeController = cache.NewInformer(
|
||||
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "nodes", api.NamespaceAll, fields.Everything()),
|
||||
&api.Node{}, ic.cfg.ResyncPeriod, eventHandler)
|
||||
|
||||
if config.UpdateStatus {
|
||||
ic.syncStatus = status.NewStatusSyncer(status.Config{
|
||||
Client: config.Client,
|
||||
PublishService: ic.cfg.PublishService,
|
||||
IngressLister: ic.ingLister,
|
||||
ElectionID: config.ElectionID,
|
||||
})
|
||||
} else {
|
||||
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
|
||||
|
|
@ -304,6 +312,15 @@ func newIngressController(config *Configuration) *GenericController {
|
|||
|
||||
ic.annotations = newAnnotationExtractor(ic)
|
||||
|
||||
ic.cfg.Backend.SetListers(ingress.StoreLister{
|
||||
Ingress: ic.ingLister,
|
||||
Service: ic.svcLister,
|
||||
Node: ic.nodeLister,
|
||||
Endpoint: ic.endpLister,
|
||||
Secret: ic.secrLister,
|
||||
ConfigMap: ic.mapLister,
|
||||
})
|
||||
|
||||
return &ic
|
||||
}
|
||||
|
||||
|
|
@ -411,29 +428,30 @@ func (ic *GenericController) sync(key interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (ic *GenericController) getStreamServices(configmapName string, proto api.Protocol) []*ingress.Location {
|
||||
func (ic *GenericController) getStreamServices(configmapName string, proto api.Protocol) []ingress.L4Service {
|
||||
glog.V(3).Infof("obtaining information about stream services of type %v located in configmap %v", proto, configmapName)
|
||||
if configmapName == "" {
|
||||
// no configmap configured
|
||||
return []*ingress.Location{}
|
||||
return []ingress.L4Service{}
|
||||
}
|
||||
|
||||
ns, name, err := k8s.ParseNameNS(configmapName)
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error reading configmap %v: %v", name, err)
|
||||
return []*ingress.Location{}
|
||||
return []ingress.L4Service{}
|
||||
}
|
||||
|
||||
configmap, err := ic.getConfigMap(ns, name)
|
||||
if err != nil {
|
||||
glog.Errorf("unexpected error reading configmap %v: %v", name, err)
|
||||
return []*ingress.Location{}
|
||||
return []ingress.L4Service{}
|
||||
}
|
||||
|
||||
var svcs []*ingress.Location
|
||||
var svcs []ingress.L4Service
|
||||
// k -> port to expose
|
||||
// v -> <namespace>/<service name>:<port from service to be used>
|
||||
for k, v := range configmap.Data {
|
||||
_, err := strconv.Atoi(k)
|
||||
externalPort, err := strconv.Atoi(k)
|
||||
if err != nil {
|
||||
glog.Warningf("%v is not valid as a TCP/UDP port", k)
|
||||
continue
|
||||
|
|
@ -476,6 +494,7 @@ func (ic *GenericController) getStreamServices(configmapName string, proto api.P
|
|||
var endps []ingress.Endpoint
|
||||
targetPort, err := strconv.Atoi(svcPort)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("searching service %v/%v endpoints using the name '%v'", svcNs, svcName, svcPort)
|
||||
for _, sp := range svc.Spec.Ports {
|
||||
if sp.Name == svcPort {
|
||||
endps = ic.getEndpoints(svc, sp.TargetPort, proto, &healthcheck.Upstream{})
|
||||
|
|
@ -484,6 +503,7 @@ func (ic *GenericController) getStreamServices(configmapName string, proto api.P
|
|||
}
|
||||
} else {
|
||||
// we need to use the TargetPort (where the endpoints are running)
|
||||
glog.V(3).Infof("searching service %v/%v endpoints using the target port '%v'", svcNs, svcName, targetPort)
|
||||
for _, sp := range svc.Spec.Ports {
|
||||
if sp.Port == int32(targetPort) {
|
||||
endps = ic.getEndpoints(svc, sp.TargetPort, proto, &healthcheck.Upstream{})
|
||||
|
|
@ -492,18 +512,22 @@ func (ic *GenericController) getStreamServices(configmapName string, proto api.P
|
|||
}
|
||||
}
|
||||
|
||||
sort.Sort(ingress.EndpointByAddrPort(endps))
|
||||
|
||||
// tcp upstreams cannot contain empty upstreams and there is no
|
||||
// default backend equivalent for TCP
|
||||
// stream services cannot contain empty upstreams and there is no
|
||||
// default backend equivalent
|
||||
if len(endps) == 0 {
|
||||
glog.Warningf("service %v/%v does not have any active endpoints", svcNs, svcName)
|
||||
glog.Warningf("service %v/%v does not have any active endpoints for port %v and protocol %v", svcNs, svcName, svcPort, proto)
|
||||
continue
|
||||
}
|
||||
|
||||
svcs = append(svcs, &ingress.Location{
|
||||
Path: k,
|
||||
Backend: fmt.Sprintf("%v-%v-%v", svcNs, svcName, svcPort),
|
||||
svcs = append(svcs, ingress.L4Service{
|
||||
Port: externalPort,
|
||||
Backend: ingress.L4Backend{
|
||||
Name: svcName,
|
||||
Namespace: svcNs,
|
||||
Port: intstr.FromString(svcPort),
|
||||
Protocol: proto,
|
||||
},
|
||||
Endpoints: endps,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -564,6 +588,10 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
|||
for _, ingIf := range ings {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
|
||||
if !IsValidClass(ing, ic.cfg.IngressClass) {
|
||||
continue
|
||||
}
|
||||
|
||||
anns := ic.annotations.Extract(ing)
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
|
|
@ -576,30 +604,9 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
|||
server = servers[defServerName]
|
||||
}
|
||||
|
||||
// use default upstream
|
||||
defBackend := upstreams[defUpstreamName]
|
||||
// we need to check if the spec contains the default backend
|
||||
if ing.Spec.Backend != nil {
|
||||
glog.V(3).Infof("ingress rule %v/%v defines a default Backend %v/%v",
|
||||
ing.Namespace,
|
||||
ing.Name,
|
||||
ing.Spec.Backend.ServiceName,
|
||||
ing.Spec.Backend.ServicePort.String())
|
||||
|
||||
name := fmt.Sprintf("%v-%v-%v",
|
||||
ing.GetNamespace(),
|
||||
ing.Spec.Backend.ServiceName,
|
||||
ing.Spec.Backend.ServicePort.String())
|
||||
|
||||
if defUps, ok := upstreams[name]; ok {
|
||||
defBackend = defUps
|
||||
}
|
||||
}
|
||||
|
||||
if rule.HTTP == nil &&
|
||||
host != defServerName {
|
||||
glog.V(3).Infof("ingress rule %v/%v does not contains HTTP rules. using default backend", ing.Namespace, ing.Name)
|
||||
server.Locations[0].Backend = defBackend.Name
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -659,7 +666,6 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
|||
glog.V(3).Infof("upstream %v does not have any active endpoints. Using default backend", value.Name)
|
||||
value.Endpoints = append(value.Endpoints, newDefaultServer())
|
||||
}
|
||||
sort.Sort(ingress.EndpointByAddrPort(value.Endpoints))
|
||||
aUpstreams = append(aUpstreams, value)
|
||||
}
|
||||
sort.Sort(ingress.BackendByNameServers(aUpstreams))
|
||||
|
|
@ -676,16 +682,23 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
|||
|
||||
// GetAuthCertificate ...
|
||||
func (ic GenericController) GetAuthCertificate(secretName string) (*resolver.AuthSSLCert, error) {
|
||||
key, err := ic.GetSecret(secretName)
|
||||
if err != nil {
|
||||
return &resolver.AuthSSLCert{}, fmt.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if key != nil {
|
||||
ic.secretQueue.Enqueue(key)
|
||||
}
|
||||
|
||||
bc, exists := ic.sslCertTracker.Get(secretName)
|
||||
if !exists {
|
||||
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exists", secretName)
|
||||
}
|
||||
cert := bc.(*ingress.SSLCert)
|
||||
return &resolver.AuthSSLCert{
|
||||
Secret: secretName,
|
||||
CertFileName: cert.PemFileName,
|
||||
CAFileName: cert.CAFileName,
|
||||
PemSHA: cert.PemSHA,
|
||||
Secret: secretName,
|
||||
CAFileName: cert.CAFileName,
|
||||
PemSHA: cert.PemSHA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -698,8 +711,13 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
|||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
|
||||
if !IsValidClass(ing, ic.cfg.IngressClass) {
|
||||
continue
|
||||
}
|
||||
|
||||
secUpstream := ic.annotations.SecureUpstream(ing)
|
||||
hz := ic.annotations.HealthCheck(ing)
|
||||
affinity := ic.annotations.SessionAffinity(ing)
|
||||
|
||||
var defBackend string
|
||||
if ing.Spec.Backend != nil {
|
||||
|
|
@ -739,6 +757,14 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
|
|||
if !upstreams[name].Secure {
|
||||
upstreams[name].Secure = secUpstream
|
||||
}
|
||||
if upstreams[name].SessionAffinity.AffinityType == "" {
|
||||
upstreams[name].SessionAffinity.AffinityType = affinity.AffinityType
|
||||
if affinity.AffinityType == "cookie" {
|
||||
upstreams[name].SessionAffinity.CookieSessionAffinity.Name = affinity.CookieConfig.Name
|
||||
upstreams[name].SessionAffinity.CookieSessionAffinity.Hash = affinity.CookieConfig.Hash
|
||||
}
|
||||
}
|
||||
|
||||
svcKey := fmt.Sprintf("%v/%v", ing.GetNamespace(), path.Backend.ServiceName)
|
||||
endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
|
||||
if err != nil {
|
||||
|
|
@ -782,6 +808,7 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
|||
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
||||
}
|
||||
|
||||
sort.Sort(ingress.EndpointByAddrPort(endps))
|
||||
upstreams = append(upstreams, endps...)
|
||||
break
|
||||
}
|
||||
|
|
@ -790,7 +817,12 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
|||
return upstreams, nil
|
||||
}
|
||||
|
||||
func (ic *GenericController) createServers(data []interface{}, upstreams map[string]*ingress.Backend) map[string]*ingress.Server {
|
||||
// createServers initializes a map that contains information about the list of
|
||||
// FDQN referenced by ingress rules and the common name field in the referenced
|
||||
// SSL certificates. Each server is configured with location / using a default
|
||||
// backend specified by the user or the one inside the ingress spec.
|
||||
func (ic *GenericController) createServers(data []interface{},
|
||||
upstreams map[string]*ingress.Backend) map[string]*ingress.Server {
|
||||
servers := make(map[string]*ingress.Server)
|
||||
|
||||
bdef := ic.GetDefaultBackend()
|
||||
|
|
@ -800,10 +832,10 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
|||
SendTimeout: bdef.ProxySendTimeout,
|
||||
ReadTimeout: bdef.ProxyReadTimeout,
|
||||
BufferSize: bdef.ProxyBufferSize,
|
||||
CookieDomain: bdef.ProxyCookieDomain,
|
||||
CookiePath: bdef.ProxyCookiePath,
|
||||
}
|
||||
|
||||
dun := ic.getDefaultUpstream().Name
|
||||
|
||||
// This adds the Default Certificate to Default Backend and also for vhosts missing the secret
|
||||
var defaultPemFileName, defaultPemSHA string
|
||||
defaultCertificate, err := ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
|
||||
|
|
@ -823,7 +855,7 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
|||
defaultPemSHA = defaultCertificate.PemSHA
|
||||
}
|
||||
|
||||
// default server
|
||||
// initialize the default server
|
||||
servers[defServerName] = &ingress.Server{
|
||||
Hostname: defServerName,
|
||||
SSLCertificate: defaultPemFileName,
|
||||
|
|
@ -832,7 +864,7 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
|||
{
|
||||
Path: rootLocation,
|
||||
IsDefBackend: true,
|
||||
Backend: dun,
|
||||
Backend: ic.getDefaultUpstream().Name,
|
||||
Proxy: ngxProxy,
|
||||
},
|
||||
}}
|
||||
|
|
@ -840,8 +872,20 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
|||
// initialize all the servers
|
||||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
if !IsValidClass(ing, ic.cfg.IngressClass) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if ssl passthrough is configured
|
||||
sslpt := ic.annotations.SSLPassthrough(ing)
|
||||
dun := ic.getDefaultUpstream().Name
|
||||
if ing.Spec.Backend != nil {
|
||||
// replace default backend
|
||||
defUpstream := fmt.Sprintf("%v-%v-%v", ing.GetNamespace(), ing.Spec.Backend.ServiceName, ing.Spec.Backend.ServicePort.String())
|
||||
if backendUpstream, ok := upstreams[defUpstream]; ok {
|
||||
dun = backendUpstream.Name
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
host := rule.Host
|
||||
|
|
@ -868,6 +912,9 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
|||
// configure default location and SSL
|
||||
for _, ingIf := range data {
|
||||
ing := ingIf.(*extensions.Ingress)
|
||||
if !IsValidClass(ing, ic.cfg.IngressClass) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, rule := range ing.Spec.Rules {
|
||||
host := rule.Host
|
||||
|
|
@ -897,22 +944,10 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
|||
servers[host].SSLPemChecksum = cert.PemSHA
|
||||
}
|
||||
} else {
|
||||
|
||||
servers[host].SSLCertificate = defaultPemFileName
|
||||
servers[host].SSLPemChecksum = defaultPemSHA
|
||||
}
|
||||
}
|
||||
|
||||
if ing.Spec.Backend != nil {
|
||||
defUpstream := fmt.Sprintf("%v-%v-%v", ing.GetNamespace(), ing.Spec.Backend.ServiceName, ing.Spec.Backend.ServicePort.String())
|
||||
if backendUpstream, ok := upstreams[defUpstream]; ok {
|
||||
if host == "" || host == defServerName {
|
||||
ic.recorder.Eventf(ing, api.EventTypeWarning, "MAPPING", "error: rules with Spec.Backend are allowed only with hostnames")
|
||||
continue
|
||||
}
|
||||
servers[host].Locations[0].Backend = backendUpstream.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1016,6 +1051,7 @@ func (ic GenericController) Start() {
|
|||
go ic.ingController.Run(ic.stopCh)
|
||||
go ic.endpController.Run(ic.stopCh)
|
||||
go ic.svcController.Run(ic.stopCh)
|
||||
go ic.nodeController.Run(ic.stopCh)
|
||||
go ic.secrController.Run(ic.stopCh)
|
||||
go ic.mapController.Run(ic.stopCh)
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,12 @@ func NewIngressController(backend ingress.Controller) *GenericController {
|
|||
|
||||
updateStatus = flags.Bool("update-status", true, `Indicates if the
|
||||
ingress controller should update the Ingress status IP/hostname. Default is true`)
|
||||
|
||||
electionID = flags.String("election-id", "ingress-controller-leader", `Election id to use for status update.`)
|
||||
)
|
||||
|
||||
backend.OverrideFlags(flags)
|
||||
|
||||
flags.AddGoFlagSet(flag.CommandLine)
|
||||
flags.Parse(os.Args)
|
||||
|
||||
|
|
@ -135,6 +139,7 @@ func NewIngressController(backend ingress.Controller) *GenericController {
|
|||
|
||||
config := &Configuration{
|
||||
UpdateStatus: *updateStatus,
|
||||
ElectionID: *electionID,
|
||||
Client: kubeClient,
|
||||
ResyncPeriod: *resyncPeriod,
|
||||
DefaultService: *defaultSvc,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/parser"
|
||||
"k8s.io/ingress/core/pkg/ingress/errors"
|
||||
)
|
||||
|
||||
// DeniedKeyName name of the key that contains the reason to deny a location
|
||||
|
|
@ -92,7 +93,10 @@ func IsValidClass(ing *extensions.Ingress, class string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
cc, _ := parser.GetStringAnnotation(ingressClassKey, ing)
|
||||
cc, err := parser.GetStringAnnotation(ingressClassKey, ing)
|
||||
if err != nil && !errors.IsMissingAnnotations(err) {
|
||||
glog.Warningf("unexpected error reading ingress annotation: %v", err)
|
||||
}
|
||||
if cc == "" {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,17 +19,18 @@ package controller
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
|
||||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type fakeError struct{}
|
||||
|
|
@ -54,6 +55,7 @@ func TestIsValidClass(t *testing.T) {
|
|||
data := map[string]string{}
|
||||
data[ingressClassKey] = "custom"
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
b = IsValidClass(ing, "custom")
|
||||
if !b {
|
||||
t.Errorf("Expected valid class but %v returned", b)
|
||||
|
|
@ -62,6 +64,10 @@ func TestIsValidClass(t *testing.T) {
|
|||
if b {
|
||||
t.Errorf("Expected invalid class but %v returned", b)
|
||||
}
|
||||
b = IsValidClass(ing, "")
|
||||
if !b {
|
||||
t.Errorf("Expected invalid class but %v returned", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsHostValid(t *testing.T) {
|
||||
|
|
@ -130,7 +136,7 @@ func TestMergeLocationAnnotations(t *testing.T) {
|
|||
"Redirect": rewrite.Redirect{},
|
||||
"Whitelist": ipwhitelist.SourceRange{},
|
||||
"Proxy": proxy.Configuration{},
|
||||
"CertificateAuth": resolver.AuthSSLCert{},
|
||||
"CertificateAuth": authtls.AuthSSLConfig{},
|
||||
"UsePortInRedirects": true,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,16 @@ type Backend struct {
|
|||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size)
|
||||
ProxyBufferSize string `json:"proxy-buffer-size"`
|
||||
|
||||
// Sets a text that should be changed in the path attribute of the “Set-Cookie” header fields of
|
||||
// a proxied server response.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cookie_path
|
||||
ProxyCookiePath string `json:"proxy-cookie-path"`
|
||||
|
||||
// Sets a text that should be changed in the domain attribute of the “Set-Cookie” header fields
|
||||
// of a proxied server response.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cookie_domain
|
||||
ProxyCookieDomain string `json:"proxy-cookie-domain"`
|
||||
|
||||
// Name server/s used to resolve names of upstream servers into IP addresses.
|
||||
// The file /etc/resolv.conf is used as DNS resolution configuration.
|
||||
Resolver []net.IP
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ type Secret interface {
|
|||
// AuthCertificate resolves a given secret name into an SSL certificate.
|
||||
// The secret must contain 3 keys named:
|
||||
// ca.crt: contains the certificate chain used for authentication
|
||||
// tls.crt: (ignored) contains the tls certificate chain, or any other valid base64 data
|
||||
// tls.key: (ignored) contains the tls secret key, or any other valid base64 data
|
||||
type AuthCertificate interface {
|
||||
GetAuthCertificate(string) (*AuthSSLCert, error)
|
||||
}
|
||||
|
|
@ -48,10 +46,6 @@ type AuthCertificate interface {
|
|||
type AuthSSLCert struct {
|
||||
// Secret contains the name of the secret this was fetched from
|
||||
Secret string `json:"secret"`
|
||||
// CertFileName contains the filename the secret's 'tls.crt' was saved to
|
||||
CertFileName string `json:"certFilename"`
|
||||
// KeyFileName contains the path the secret's 'tls.key'
|
||||
KeyFileName string `json:"keyFilename"`
|
||||
// CAFileName contains the path to the secrets 'ca.crt'
|
||||
CAFileName string `json:"caFilename"`
|
||||
// PemSHA contains the SHA1 hash of the 'tls.crt' value
|
||||
|
|
|
|||
130
core/pkg/ingress/status/election_test.go
Normal file
130
core/pkg/ingress/status/election_test.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
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 status
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
tc "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/client/leaderelection/resourcelock"
|
||||
)
|
||||
|
||||
func TestGetCurrentLeaderLeaderExist(t *testing.T) {
|
||||
fkER := resourcelock.LeaderElectionRecord{
|
||||
HolderIdentity: "currentLeader",
|
||||
LeaseDurationSeconds: 30,
|
||||
AcquireTime: unversioned.Now(),
|
||||
RenewTime: unversioned.Now(),
|
||||
LeaderTransitions: 3,
|
||||
}
|
||||
leaderInfo, _ := json.Marshal(fkER)
|
||||
fkEndpoints := api.Endpoints{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "ingress-controller-test",
|
||||
Namespace: api.NamespaceSystem,
|
||||
Annotations: map[string]string{
|
||||
resourcelock.LeaderElectionRecordAnnotationKey: string(leaderInfo),
|
||||
},
|
||||
},
|
||||
}
|
||||
fk := tc.NewSimpleClientset(&api.EndpointsList{Items: []api.Endpoints{fkEndpoints}})
|
||||
identity, endpoints, err := getCurrentLeader("ingress-controller-test", api.NamespaceSystem, fk)
|
||||
if err != nil {
|
||||
t.Fatalf("expected identitiy and endpoints but returned error %s", err)
|
||||
}
|
||||
|
||||
if endpoints == nil {
|
||||
t.Fatalf("returned nil but expected an endpoints")
|
||||
}
|
||||
|
||||
if identity != "currentLeader" {
|
||||
t.Fatalf("returned %v but expected %v", identity, "currentLeader")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentLeaderLeaderNotExist(t *testing.T) {
|
||||
fkEndpoints := api.Endpoints{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "ingress-controller-test",
|
||||
Namespace: api.NamespaceSystem,
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
}
|
||||
fk := tc.NewSimpleClientset(&api.EndpointsList{Items: []api.Endpoints{fkEndpoints}})
|
||||
identity, endpoints, err := getCurrentLeader("ingress-controller-test", api.NamespaceSystem, fk)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpeted error: %v", err)
|
||||
}
|
||||
|
||||
if endpoints == nil {
|
||||
t.Fatalf("returned nil but expected an endpoints")
|
||||
}
|
||||
|
||||
if identity != "" {
|
||||
t.Fatalf("returned %s but expected %s", identity, "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentLeaderAnnotationError(t *testing.T) {
|
||||
fkEndpoints := api.Endpoints{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "ingress-controller-test",
|
||||
Namespace: api.NamespaceSystem,
|
||||
Annotations: map[string]string{
|
||||
resourcelock.LeaderElectionRecordAnnotationKey: "just-test-error-leader-annotation",
|
||||
},
|
||||
},
|
||||
}
|
||||
fk := tc.NewSimpleClientset(&api.EndpointsList{Items: []api.Endpoints{fkEndpoints}})
|
||||
_, _, err := getCurrentLeader("ingress-controller-test", api.NamespaceSystem, fk)
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewElection(t *testing.T) {
|
||||
fk := tc.NewSimpleClientset(&api.EndpointsList{Items: []api.Endpoints{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "ingress-controller-test",
|
||||
Namespace: api.NamespaceSystem,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "ingress-controller-test-020",
|
||||
Namespace: api.NamespaceSystem,
|
||||
},
|
||||
},
|
||||
}})
|
||||
|
||||
ne, err := NewElection("ingress-controller-test", "startLeader", api.NamespaceSystem, 4*time.Second, func(leader string) {
|
||||
// do nothing
|
||||
go t.Logf("execute callback fun, leader is: %s", leader)
|
||||
}, fk)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
if ne == nil {
|
||||
t.Fatalf("unexpected nil")
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ import (
|
|||
|
||||
cache_store "k8s.io/ingress/core/pkg/cache"
|
||||
"k8s.io/ingress/core/pkg/k8s"
|
||||
strings "k8s.io/ingress/core/pkg/strings"
|
||||
"k8s.io/ingress/core/pkg/strings"
|
||||
"k8s.io/ingress/core/pkg/task"
|
||||
)
|
||||
|
||||
|
|
@ -52,6 +52,7 @@ type Config struct {
|
|||
Client clientset.Interface
|
||||
PublishService string
|
||||
IngressLister cache_store.StoreToIngressLister
|
||||
ElectionID string
|
||||
}
|
||||
|
||||
// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
|
||||
|
|
@ -171,7 +172,7 @@ func NewStatusSyncer(config Config) Sync {
|
|||
}
|
||||
st.syncQueue = task.NewCustomTaskQueue(st.sync, st.keyfunc)
|
||||
|
||||
le, err := NewElection("ingress-controller-leader",
|
||||
le, err := NewElection(config.ElectionID,
|
||||
pod.Name, pod.Namespace, 30*time.Second,
|
||||
st.callback, config.Client)
|
||||
if err != nil {
|
||||
|
|
@ -251,7 +252,7 @@ func (s *statusSync) updateStatus(newIPs []api.LoadBalancerIngress) {
|
|||
return
|
||||
}
|
||||
|
||||
curIPs := ing.Status.LoadBalancer.Ingress
|
||||
curIPs := currIng.Status.LoadBalancer.Ingress
|
||||
sort.Sort(loadBalancerIngressByIP(curIPs))
|
||||
if ingressSliceEqual(newIPs, curIPs) {
|
||||
glog.V(3).Infof("skipping update of Ingress %v/%v (there is no change)", currIng.Namespace, currIng.Name)
|
||||
|
|
|
|||
487
core/pkg/ingress/status/status_test.go
Normal file
487
core/pkg/ingress/status/status_test.go
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
/*
|
||||
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 status
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cache_store "k8s.io/ingress/core/pkg/cache"
|
||||
"k8s.io/ingress/core/pkg/k8s"
|
||||
"k8s.io/ingress/core/pkg/task"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
testclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
func buildLoadBalancerIngressByIP() loadBalancerIngressByIP {
|
||||
return []api.LoadBalancerIngress{
|
||||
{
|
||||
IP: "10.0.0.1",
|
||||
Hostname: "foo1",
|
||||
},
|
||||
{
|
||||
IP: "10.0.0.2",
|
||||
Hostname: "foo2",
|
||||
},
|
||||
{
|
||||
IP: "10.0.0.3",
|
||||
Hostname: "",
|
||||
},
|
||||
{
|
||||
IP: "",
|
||||
Hostname: "foo4",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildSimpleClientSet() *testclient.Clientset {
|
||||
return testclient.NewSimpleClientset(
|
||||
&api.PodList{Items: []api.Pod{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo1",
|
||||
Namespace: api.NamespaceDefault,
|
||||
Labels: map[string]string{
|
||||
"lable_sig": "foo_pod",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: "foo_node_2",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo2",
|
||||
Namespace: api.NamespaceDefault,
|
||||
Labels: map[string]string{
|
||||
"lable_sig": "foo_no",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo3",
|
||||
Namespace: api.NamespaceSystem,
|
||||
Labels: map[string]string{
|
||||
"lable_sig": "foo_pod",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
NodeName: "foo_node_2",
|
||||
},
|
||||
},
|
||||
}},
|
||||
&api.ServiceList{Items: []api.Service{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Status: api.ServiceStatus{
|
||||
LoadBalancer: api.LoadBalancerStatus{
|
||||
Ingress: buildLoadBalancerIngressByIP(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo_non_exist",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
},
|
||||
}},
|
||||
&api.NodeList{Items: []api.Node{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo_node_1",
|
||||
},
|
||||
Status: api.NodeStatus{
|
||||
Addresses: []api.NodeAddress{
|
||||
{
|
||||
Type: api.NodeLegacyHostIP,
|
||||
Address: "10.0.0.1",
|
||||
}, {
|
||||
Type: api.NodeExternalIP,
|
||||
Address: "10.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo_node_2",
|
||||
},
|
||||
Status: api.NodeStatus{
|
||||
Addresses: []api.NodeAddress{
|
||||
{
|
||||
Type: api.NodeLegacyHostIP,
|
||||
Address: "11.0.0.1",
|
||||
},
|
||||
{
|
||||
Type: api.NodeExternalIP,
|
||||
Address: "11.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
&api.EndpointsList{Items: []api.Endpoints{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "ingress-controller-leader",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
}}},
|
||||
&extensions.IngressList{Items: buildExtensionsIngresses()},
|
||||
)
|
||||
}
|
||||
|
||||
func fakeSynFn(interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildExtensionsIngresses() []extensions.Ingress {
|
||||
return []extensions.Ingress{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo_ingress_1",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Status: extensions.IngressStatus{
|
||||
LoadBalancer: api.LoadBalancerStatus{
|
||||
Ingress: []api.LoadBalancerIngress{
|
||||
{
|
||||
IP: "10.0.0.1",
|
||||
Hostname: "foo1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo_ingress_2",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Status: extensions.IngressStatus{
|
||||
LoadBalancer: api.LoadBalancerStatus{
|
||||
Ingress: []api.LoadBalancerIngress{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func buildIngressLIstener() cache_store.StoreToIngressLister {
|
||||
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||
ids := sets.NewString("foo_ingress_non_01")
|
||||
for id := range ids {
|
||||
store.Add(&extensions.Ingress{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: id,
|
||||
Namespace: api.NamespaceDefault,
|
||||
}})
|
||||
}
|
||||
store.Add(&extensions.Ingress{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo_ingress_1",
|
||||
Namespace: api.NamespaceDefault,
|
||||
},
|
||||
Status: extensions.IngressStatus{
|
||||
LoadBalancer: api.LoadBalancerStatus{
|
||||
Ingress: buildLoadBalancerIngressByIP(),
|
||||
},
|
||||
},
|
||||
})
|
||||
return cache_store.StoreToIngressLister{Store: store}
|
||||
}
|
||||
|
||||
func buildStatusSync() statusSync {
|
||||
return statusSync{
|
||||
pod: &k8s.PodInfo{
|
||||
Name: "foo_base_pod",
|
||||
Namespace: api.NamespaceDefault,
|
||||
Labels: map[string]string{
|
||||
"lable_sig": "foo_pod",
|
||||
},
|
||||
},
|
||||
runLock: &sync.Mutex{},
|
||||
syncQueue: task.NewTaskQueue(fakeSynFn),
|
||||
Config: Config{
|
||||
Client: buildSimpleClientSet(),
|
||||
PublishService: api.NamespaceDefault + "/" + "foo",
|
||||
IngressLister: buildIngressLIstener(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusActions(t *testing.T) {
|
||||
// make sure election can be created
|
||||
os.Setenv("POD_NAME", "foo1")
|
||||
os.Setenv("POD_NAMESPACE", api.NamespaceDefault)
|
||||
c := Config{
|
||||
Client: buildSimpleClientSet(),
|
||||
PublishService: "",
|
||||
IngressLister: buildIngressLIstener(),
|
||||
}
|
||||
// create object
|
||||
fkSync := NewStatusSyncer(c)
|
||||
if fkSync == nil {
|
||||
t.Fatalf("expected a valid Sync")
|
||||
}
|
||||
|
||||
fk := fkSync.(statusSync)
|
||||
|
||||
ns := make(chan struct{})
|
||||
// start it and wait for the election and syn actions
|
||||
go fk.Run(ns)
|
||||
// wait for the election
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// execute sync
|
||||
fk.sync("just-test")
|
||||
// PublishService is empty, so the running address is: ["11.0.0.2"]
|
||||
// after updated, the ingress's ip should only be "11.0.0.2"
|
||||
newIPs := []api.LoadBalancerIngress{{
|
||||
IP: "11.0.0.2",
|
||||
}}
|
||||
fooIngress1, err1 := fk.Client.Extensions().Ingresses(api.NamespaceDefault).Get("foo_ingress_1")
|
||||
if err1 != nil {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
fooIngress1CurIPs := fooIngress1.Status.LoadBalancer.Ingress
|
||||
if !ingressSliceEqual(fooIngress1CurIPs, newIPs) {
|
||||
t.Fatalf("returned %v but expected %v", fooIngress1CurIPs, newIPs)
|
||||
}
|
||||
|
||||
// execute shutdown
|
||||
fk.Shutdown()
|
||||
// ingress should be empty
|
||||
newIPs2 := []api.LoadBalancerIngress{}
|
||||
fooIngress2, err2 := fk.Client.Extensions().Ingresses(api.NamespaceDefault).Get("foo_ingress_1")
|
||||
if err2 != nil {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
fooIngress2CurIPs := fooIngress2.Status.LoadBalancer.Ingress
|
||||
if !ingressSliceEqual(fooIngress2CurIPs, newIPs2) {
|
||||
t.Fatalf("returned %v but expected %v", fooIngress2CurIPs, newIPs2)
|
||||
}
|
||||
|
||||
// end test
|
||||
ns <- struct{}{}
|
||||
}
|
||||
|
||||
func TestCallback(t *testing.T) {
|
||||
fk := buildStatusSync()
|
||||
// do nothing
|
||||
fk.callback("foo_base_pod")
|
||||
}
|
||||
|
||||
func TestKeyfunc(t *testing.T) {
|
||||
fk := buildStatusSync()
|
||||
i := "foo_base_pod"
|
||||
r, err := fk.keyfunc(i)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
if r != i {
|
||||
t.Errorf("returned %v but expected %v", r, i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningAddresessWithPublishService(t *testing.T) {
|
||||
fk := buildStatusSync()
|
||||
|
||||
r, _ := fk.runningAddresess()
|
||||
if r == nil {
|
||||
t.Fatalf("returned nil but expected valid []string")
|
||||
}
|
||||
rl := len(r)
|
||||
if len(r) != 4 {
|
||||
t.Errorf("returned %v but expected %v", rl, 4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningAddresessWithPods(t *testing.T) {
|
||||
fk := buildStatusSync()
|
||||
fk.PublishService = ""
|
||||
|
||||
r, _ := fk.runningAddresess()
|
||||
if r == nil {
|
||||
t.Fatalf("returned nil but expected valid []string")
|
||||
}
|
||||
rl := len(r)
|
||||
if len(r) != 1 {
|
||||
t.Fatalf("returned %v but expected %v", rl, 1)
|
||||
}
|
||||
rv := r[0]
|
||||
if rv != "11.0.0.2" {
|
||||
t.Errorf("returned %v but expected %v", rv, "11.0.0.2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStatus(t *testing.T) {
|
||||
fk := buildStatusSync()
|
||||
newIPs := buildLoadBalancerIngressByIP()
|
||||
sort.Sort(loadBalancerIngressByIP(newIPs))
|
||||
fk.updateStatus(newIPs)
|
||||
|
||||
fooIngress1, err1 := fk.Client.Extensions().Ingresses(api.NamespaceDefault).Get("foo_ingress_1")
|
||||
if err1 != nil {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
fooIngress1CurIPs := fooIngress1.Status.LoadBalancer.Ingress
|
||||
if !ingressSliceEqual(fooIngress1CurIPs, newIPs) {
|
||||
t.Fatalf("returned %v but expected %v", fooIngress1CurIPs, newIPs)
|
||||
}
|
||||
|
||||
fooIngress2, err2 := fk.Client.Extensions().Ingresses(api.NamespaceDefault).Get("foo_ingress_2")
|
||||
if err2 != nil {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
fooIngress2CurIPs := fooIngress2.Status.LoadBalancer.Ingress
|
||||
if !ingressSliceEqual(fooIngress2CurIPs, []api.LoadBalancerIngress{}) {
|
||||
t.Fatalf("returned %v but expected %v", fooIngress2CurIPs, []api.LoadBalancerIngress{})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceToStatus(t *testing.T) {
|
||||
fkEndpoints := []string{
|
||||
"10.0.0.1",
|
||||
"2001:db8::68",
|
||||
"opensource-k8s-ingress",
|
||||
}
|
||||
|
||||
r := sliceToStatus(fkEndpoints)
|
||||
|
||||
if r == nil {
|
||||
t.Fatalf("returned nil but expected a valid []api.LoadBalancerIngress")
|
||||
}
|
||||
rl := len(r)
|
||||
if rl != 3 {
|
||||
t.Fatalf("returned %v but expected %v", rl, 3)
|
||||
}
|
||||
re1 := r[0]
|
||||
if re1.Hostname != "opensource-k8s-ingress" {
|
||||
t.Fatalf("returned %v but expected %v", re1, api.LoadBalancerIngress{Hostname: "opensource-k8s-ingress"})
|
||||
}
|
||||
re2 := r[1]
|
||||
if re2.IP != "10.0.0.1" {
|
||||
t.Fatalf("returned %v but expected %v", re2, api.LoadBalancerIngress{IP: "10.0.0.1"})
|
||||
}
|
||||
re3 := r[2]
|
||||
if re3.IP != "2001:db8::68" {
|
||||
t.Fatalf("returned %v but expected %v", re3, api.LoadBalancerIngress{IP: "2001:db8::68"})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngressSliceEqual(t *testing.T) {
|
||||
fk1 := buildLoadBalancerIngressByIP()
|
||||
fk2 := append(buildLoadBalancerIngressByIP(), api.LoadBalancerIngress{
|
||||
IP: "10.0.0.5",
|
||||
Hostname: "foo5",
|
||||
})
|
||||
fk3 := buildLoadBalancerIngressByIP()
|
||||
fk3[0].Hostname = "foo_no_01"
|
||||
fk4 := buildLoadBalancerIngressByIP()
|
||||
fk4[2].IP = "11.0.0.3"
|
||||
|
||||
fooTests := []struct {
|
||||
lhs []api.LoadBalancerIngress
|
||||
rhs []api.LoadBalancerIngress
|
||||
er bool
|
||||
}{
|
||||
{fk1, fk1, true},
|
||||
{fk2, fk1, false},
|
||||
{fk3, fk1, false},
|
||||
{fk4, fk1, false},
|
||||
{fk1, nil, false},
|
||||
{nil, nil, true},
|
||||
{[]api.LoadBalancerIngress{}, []api.LoadBalancerIngress{}, true},
|
||||
}
|
||||
|
||||
for _, fooTest := range fooTests {
|
||||
r := ingressSliceEqual(fooTest.lhs, fooTest.rhs)
|
||||
if r != fooTest.er {
|
||||
t.Errorf("returned %v but expected %v", r, fooTest.er)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBalancerIngressByIPLen(t *testing.T) {
|
||||
fooTests := []struct {
|
||||
ips loadBalancerIngressByIP
|
||||
el int
|
||||
}{
|
||||
{[]api.LoadBalancerIngress{}, 0},
|
||||
{buildLoadBalancerIngressByIP(), 4},
|
||||
{nil, 0},
|
||||
}
|
||||
|
||||
for _, fooTest := range fooTests {
|
||||
r := fooTest.ips.Len()
|
||||
if r != fooTest.el {
|
||||
t.Errorf("returned %v but expected %v ", r, fooTest.el)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBalancerIngressByIPSwap(t *testing.T) {
|
||||
fooTests := []struct {
|
||||
ips loadBalancerIngressByIP
|
||||
i int
|
||||
j int
|
||||
}{
|
||||
{buildLoadBalancerIngressByIP(), 0, 1},
|
||||
{buildLoadBalancerIngressByIP(), 2, 1},
|
||||
}
|
||||
|
||||
for _, fooTest := range fooTests {
|
||||
fooi := fooTest.ips[fooTest.i]
|
||||
fooj := fooTest.ips[fooTest.j]
|
||||
fooTest.ips.Swap(fooTest.i, fooTest.j)
|
||||
if fooi.IP != fooTest.ips[fooTest.j].IP ||
|
||||
fooj.IP != fooTest.ips[fooTest.i].IP {
|
||||
t.Errorf("failed to swap for loadBalancerIngressByIP")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBalancerIngressByIPLess(t *testing.T) {
|
||||
fooTests := []struct {
|
||||
ips loadBalancerIngressByIP
|
||||
i int
|
||||
j int
|
||||
er bool
|
||||
}{
|
||||
{buildLoadBalancerIngressByIP(), 0, 1, true},
|
||||
{buildLoadBalancerIngressByIP(), 2, 1, false},
|
||||
}
|
||||
|
||||
for _, fooTest := range fooTests {
|
||||
r := fooTest.ips.Less(fooTest.i, fooTest.j)
|
||||
if r != fooTest.er {
|
||||
t.Errorf("returned %v but expected %v ", r, fooTest.er)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,17 +17,22 @@ limitations under the License.
|
|||
package ingress
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/healthz"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/healthz"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
|
||||
cache_store "k8s.io/ingress/core/pkg/cache"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -81,11 +86,27 @@ type Controller interface {
|
|||
OnUpdate(Configuration) ([]byte, error)
|
||||
// ConfigMap content of --configmap
|
||||
SetConfig(*api.ConfigMap)
|
||||
// SetListers allows the access of store listers present in the generic controller
|
||||
// This avoid the use of the kubernetes client.
|
||||
SetListers(StoreLister)
|
||||
// BackendDefaults returns the minimum settings required to configure the
|
||||
// communication to endpoints
|
||||
BackendDefaults() defaults.Backend
|
||||
// Info returns information about the ingress controller
|
||||
Info() *BackendInfo
|
||||
// OverrideFlags allow the customization of the flags in the backend
|
||||
OverrideFlags(*pflag.FlagSet)
|
||||
}
|
||||
|
||||
// StoreLister returns the configured stores for ingresses, services,
|
||||
// endpoints, secrets and configmaps.
|
||||
type StoreLister struct {
|
||||
Ingress cache_store.StoreToIngressLister
|
||||
Service cache.StoreToServiceLister
|
||||
Node cache.StoreToNodeLister
|
||||
Endpoint cache.StoreToEndpointsLister
|
||||
Secret cache_store.StoreToSecretsLister
|
||||
ConfigMap cache_store.StoreToConfigmapLister
|
||||
}
|
||||
|
||||
// BackendInfo returns information about the backend.
|
||||
|
|
@ -112,10 +133,10 @@ type Configuration struct {
|
|||
Servers []*Server `json:"servers"`
|
||||
// TCPEndpoints contain endpoints for tcp streams handled by this backend
|
||||
// +optional
|
||||
TCPEndpoints []*Location `json:"tcpEndpoints,omitempty"`
|
||||
TCPEndpoints []L4Service `json:"tcpEndpoints,omitempty"`
|
||||
// UDPEndpoints contain endpoints for udp streams handled by this backend
|
||||
// +optional
|
||||
UDPEndpoints []*Location `json:"udpEndpoints,omitempty"`
|
||||
UDPEndpoints []L4Service `json:"udpEndpoints,omitempty"`
|
||||
// PassthroughBackend contains the backends used for SSL passthrough.
|
||||
// It contains information about the associated Server Name Indication (SNI).
|
||||
// +optional
|
||||
|
|
@ -134,9 +155,29 @@ type Backend struct {
|
|||
Secure bool `json:"secure"`
|
||||
// Endpoints contains the list of endpoints currently running
|
||||
Endpoints []Endpoint `json:"endpoints"`
|
||||
// StickySession contains the StickyConfig object with stickness configuration
|
||||
|
||||
SessionAffinity SessionAffinityConfig
|
||||
}
|
||||
|
||||
// Endpoint describes a kubernetes endpoint in an backend
|
||||
// SessionAffinityConfig describes different affinity configurations for new sessions.
|
||||
// Once a session is mapped to a backend based on some affinity setting, it
|
||||
// retains that mapping till the backend goes down, or the ingress controller
|
||||
// restarts. Exactly one of these values will be set on the upstream, since multiple
|
||||
// affinity values are incompatible. Once set, the backend makes no guarantees
|
||||
// about honoring updates.
|
||||
type SessionAffinityConfig struct {
|
||||
AffinityType string `json:"name"`
|
||||
CookieSessionAffinity CookieSessionAffinity
|
||||
}
|
||||
|
||||
// CookieSessionAffinity defines the structure used in Affinity configured by Cookies.
|
||||
type CookieSessionAffinity struct {
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
// Endpoint describes a kubernetes endpoint in a backend
|
||||
type Endpoint struct {
|
||||
// Address IP address of the endpoint
|
||||
Address string `json:"address"`
|
||||
|
|
@ -233,10 +274,13 @@ type Location struct {
|
|||
// CertificateAuth indicates the access to this location requires
|
||||
// external authentication
|
||||
// +optional
|
||||
CertificateAuth resolver.AuthSSLCert `json:"certificateAuth,omitempty"`
|
||||
CertificateAuth authtls.AuthSSLConfig `json:"certificateAuth,omitempty"`
|
||||
// UsePortInRedirects indicates if redirects must specify the port
|
||||
// +optional
|
||||
UsePortInRedirects bool `json:"use-port-in-redirects"`
|
||||
// ConfigurationSnippet contains additional configuration for the backend
|
||||
// to be considered in the configuration of the location
|
||||
ConfigurationSnippet string `json:"configuration-snippet"`
|
||||
}
|
||||
|
||||
// SSLPassthroughBackend describes a SSL upstream server configured
|
||||
|
|
@ -249,3 +293,21 @@ type SSLPassthroughBackend struct {
|
|||
// Hostname returns the FQDN of the server
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
// L4Service describes a L4 Ingress service.
|
||||
type L4Service struct {
|
||||
// Port external port to expose
|
||||
Port int `json:"port"`
|
||||
// Backend of the service
|
||||
Backend L4Backend `json:"backend"`
|
||||
// Endpoints active endpoints of the service
|
||||
Endpoints []Endpoint `json:"endpoins"`
|
||||
}
|
||||
|
||||
// L4Backend describes the kubernetes service behind L4 Ingress service
|
||||
type L4Backend struct {
|
||||
Port intstr.IntOrString `json:"port"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Protocol api.Protocol `json:"protocol"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
|
||||
|
||||
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
|
||||
|
||||
glog.V(3).Infof("Creating temp file %v for Keypair: %v", tempPemFile.Name(), pemName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err)
|
||||
}
|
||||
|
|
@ -64,12 +66,12 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
pembBock, _ := pem.Decode(pemCerts)
|
||||
if pembBock == nil {
|
||||
pemBlock, _ := pem.Decode(pemCerts)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("No valid PEM formatted block found")
|
||||
}
|
||||
|
||||
pemCert, err := x509.ParseCertificate(pembBock.Bytes)
|
||||
pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -97,21 +99,21 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
return nil, errors.New(oe)
|
||||
}
|
||||
|
||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||
caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName)
|
||||
f, err := os.Create(caFileName)
|
||||
caFile, err := os.OpenFile(pemFileName, os.O_RDWR|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create ca pem file %v: %v", caFileName, err)
|
||||
return nil, fmt.Errorf("Could not open file %v for writing additional CA chains: %v", pemFileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(ca)
|
||||
|
||||
defer caFile.Close()
|
||||
_, err = caFile.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create ca pem file %v: %v", caFileName, err)
|
||||
return nil, fmt.Errorf("could not append CA to cert file %v: %v", pemFileName, err)
|
||||
}
|
||||
f.Write([]byte("\n"))
|
||||
caFile.Write(ca)
|
||||
caFile.Write([]byte("\n"))
|
||||
|
||||
return &ingress.SSLCert{
|
||||
CAFileName: caFileName,
|
||||
CAFileName: pemFileName,
|
||||
PemFileName: pemFileName,
|
||||
PemSHA: pemSHA1(pemFileName),
|
||||
CN: cn,
|
||||
|
|
@ -125,6 +127,36 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// AddCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication
|
||||
// If it's already exists, it's clobbered.
|
||||
func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
|
||||
|
||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||
caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName)
|
||||
|
||||
pemCABlock, _ := pem.Decode(ca)
|
||||
if pemCABlock == nil {
|
||||
return nil, fmt.Errorf("No valid PEM formatted block found")
|
||||
}
|
||||
|
||||
_, err := x509.ParseCertificate(pemCABlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(caFileName, ca, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not write CA file %v: %v", caFileName, err)
|
||||
}
|
||||
|
||||
glog.V(3).Infof("Created CA Certificate for authentication: %v", caFileName)
|
||||
return &ingress.SSLCert{
|
||||
CAFileName: caFileName,
|
||||
PemFileName: caFileName,
|
||||
PemSHA: pemSHA1(caFileName),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory
|
||||
// in order to find a file with the name dhparam.pem. If such file exists it will
|
||||
// returns the path. If not it just returns an empty string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue