Update dependencies

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

View file

@ -0,0 +1,191 @@
/*
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 controller
import (
"github.com/golang/glog"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/ingress-nginx/pkg/ingress/annotations/alias"
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
"k8s.io/ingress-nginx/pkg/ingress/annotations/authtls"
"k8s.io/ingress-nginx/pkg/ingress/annotations/clientbodybuffersize"
"k8s.io/ingress-nginx/pkg/ingress/annotations/cors"
"k8s.io/ingress-nginx/pkg/ingress/annotations/defaultbackend"
"k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck"
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/ingress/annotations/portinredirect"
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/pkg/ingress/annotations/redirect"
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
"k8s.io/ingress-nginx/pkg/ingress/annotations/secureupstream"
"k8s.io/ingress-nginx/pkg/ingress/annotations/serversnippet"
"k8s.io/ingress-nginx/pkg/ingress/annotations/serviceupstream"
"k8s.io/ingress-nginx/pkg/ingress/annotations/sessionaffinity"
"k8s.io/ingress-nginx/pkg/ingress/annotations/snippet"
"k8s.io/ingress-nginx/pkg/ingress/annotations/sslpassthrough"
"k8s.io/ingress-nginx/pkg/ingress/annotations/upstreamvhost"
"k8s.io/ingress-nginx/pkg/ingress/annotations/vtsfilterkey"
"k8s.io/ingress-nginx/pkg/ingress/errors"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
type extractorConfig interface {
resolver.AuthCertificate
resolver.DefaultBackend
resolver.Secret
resolver.Service
}
type annotationExtractor struct {
secretResolver resolver.Secret
annotations map[string]parser.IngressAnnotation
}
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
return annotationExtractor{
cfg,
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(cfg),
"Redirect": redirect.NewParser(),
"Rewrite": rewrite.NewParser(cfg),
"SecureUpstream": secureupstream.NewParser(cfg),
"ServiceUpstream": serviceupstream.NewParser(),
"SessionAffinity": sessionaffinity.NewParser(),
"SSLPassthrough": sslpassthrough.NewParser(),
"ConfigurationSnippet": snippet.NewParser(),
"Alias": alias.NewParser(),
"ClientBodyBufferSize": clientbodybuffersize.NewParser(),
"DefaultBackend": defaultbackend.NewParser(cfg),
"UpstreamVhost": upstreamvhost.NewParser(),
"VtsFilterKey": vtsfilterkey.NewParser(),
"ServerSnippet": serversnippet.NewParser(),
},
}
}
func (e *annotationExtractor) Extract(ing *extensions.Ingress) map[string]interface{} {
anns := make(map[string]interface{})
for name, annotationParser := range e.annotations {
val, err := annotationParser.Parse(ing)
glog.V(5).Infof("annotation %v in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), val)
if err != nil {
if errors.IsMissingAnnotations(err) {
continue
}
if !errors.IsLocationDenied(err) {
continue
}
_, alreadyDenied := anns[DeniedKeyName]
if !alreadyDenied {
anns[DeniedKeyName] = err
glog.Errorf("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
continue
}
glog.V(5).Infof("error reading %v annotation in Ingress %v/%v: %v", name, ing.GetNamespace(), ing.GetName(), err)
}
if val != nil {
anns[name] = val
}
}
return anns
}
const (
secureUpstream = "SecureUpstream"
healthCheck = "HealthCheck"
sslPassthrough = "SSLPassthrough"
sessionAffinity = "SessionAffinity"
serviceUpstream = "ServiceUpstream"
serverAlias = "Alias"
clientBodyBufferSize = "ClientBodyBufferSize"
certificateAuth = "CertificateAuth"
serverSnippet = "ServerSnippet"
)
func (e *annotationExtractor) ServiceUpstream(ing *extensions.Ingress) bool {
val, _ := e.annotations[serviceUpstream].Parse(ing)
return val.(bool)
}
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) *secureupstream.Secure {
val, err := e.annotations[secureUpstream].Parse(ing)
if err != nil {
glog.Errorf("error parsing secure upstream: %v", err)
}
secure := val.(*secureupstream.Secure)
return secure
}
func (e *annotationExtractor) HealthCheck(ing *extensions.Ingress) *healthcheck.Upstream {
val, _ := e.annotations[healthCheck].Parse(ing)
return val.(*healthcheck.Upstream)
}
func (e *annotationExtractor) SSLPassthrough(ing *extensions.Ingress) bool {
val, _ := e.annotations[sslPassthrough].Parse(ing)
return val.(bool)
}
func (e *annotationExtractor) Alias(ing *extensions.Ingress) string {
val, _ := e.annotations[serverAlias].Parse(ing)
return val.(string)
}
func (e *annotationExtractor) ClientBodyBufferSize(ing *extensions.Ingress) string {
val, _ := e.annotations[clientBodyBufferSize].Parse(ing)
return val.(string)
}
func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessionaffinity.AffinityConfig {
val, _ := e.annotations[sessionAffinity].Parse(ing)
return val.(*sessionaffinity.AffinityConfig)
}
func (e *annotationExtractor) CertificateAuth(ing *extensions.Ingress) *authtls.AuthSSLConfig {
val, err := e.annotations[certificateAuth].Parse(ing)
if errors.IsMissingAnnotations(err) {
return nil
}
if err != nil {
glog.Errorf("error parsing certificate auth: %v", err)
}
secure := val.(*authtls.AuthSSLConfig)
return secure
}
func (e *annotationExtractor) ServerSnippet(ing *extensions.Ingress) string {
val, _ := e.annotations[serverSnippet].Parse(ing)
return val.(string)
}

View file

@ -0,0 +1,270 @@
/*
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 controller
import (
"testing"
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/ingress-nginx/pkg/ingress/defaults"
"k8s.io/ingress-nginx/pkg/ingress/resolver"
)
const (
annotationSecureUpstream = "ingress.kubernetes.io/secure-backends"
annotationSecureVerifyCACert = "ingress.kubernetes.io/secure-verify-ca-secret"
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 {
MockSecrets map[string]*apiv1.Secret
MockServices map[string]*apiv1.Service
}
func (m mockCfg) GetDefaultBackend() defaults.Backend {
return defaults.Backend{}
}
func (m mockCfg) GetSecret(name string) (*apiv1.Secret, error) {
return m.MockSecrets[name], nil
}
func (m mockCfg) GetService(name string) (*apiv1.Service, error) {
return m.MockServices[name], nil
}
func (m mockCfg) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
if secret, _ := m.GetSecret(name); secret != nil {
return &resolver.AuthSSLCert{
Secret: name,
CAFileName: "/opt/ca.pem",
PemSHA: "123",
}, nil
}
return nil, nil
}
func TestAnnotationExtractor(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress()
m := ec.Extract(ing)
// the map at least should contains HealthCheck and Proxy information (defaults)
if _, ok := m["HealthCheck"]; !ok {
t.Error("expected HealthCheck annotation")
}
if _, ok := m["Proxy"]; !ok {
t.Error("expected Proxy annotation")
}
}
func buildIngress() *extensions.Ingress {
defaultBackend := extensions.IngressBackend{
ServiceName: "default-backend",
ServicePort: intstr.FromInt(80),
}
return &extensions.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: apiv1.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 TestSecureUpstream(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
annotations map[string]string
er bool
}{
{map[string]string{annotationSecureUpstream: "true"}, true},
{map[string]string{annotationSecureUpstream: "false"}, false},
{map[string]string{annotationSecureUpstream + "_no": "true"}, false},
{map[string]string{}, false},
{nil, false},
}
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.SecureUpstream(ing)
if r.Secure != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
}
}
func TestSecureVerifyCACert(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{
MockSecrets: map[string]*apiv1.Secret{
"default/secure-verify-ca": {
ObjectMeta: metav1.ObjectMeta{
Name: "secure-verify-ca",
},
},
},
})
anns := []struct {
it int
annotations map[string]string
exists bool
}{
{1, map[string]string{annotationSecureUpstream: "true", annotationSecureVerifyCACert: "not"}, false},
{2, map[string]string{annotationSecureUpstream: "false", annotationSecureVerifyCACert: "secure-verify-ca"}, false},
{3, map[string]string{annotationSecureUpstream: "true", annotationSecureVerifyCACert: "secure-verify-ca"}, true},
{4, map[string]string{annotationSecureUpstream: "true", annotationSecureVerifyCACert + "_not": "secure-verify-ca"}, false},
{5, map[string]string{annotationSecureUpstream: "true"}, false},
{6, map[string]string{}, false},
{7, nil, false},
}
for _, ann := range anns {
ing := buildIngress()
ing.SetAnnotations(ann.annotations)
res := ec.SecureUpstream(ing)
if (res.CACert.CAFileName != "") != ann.exists {
t.Errorf("Expected exists was %v on iteration %v", ann.exists, ann.it)
}
}
}
func TestHealthCheck(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
annotations map[string]string
eumf int
euft int
}{
{map[string]string{annotationUpsMaxFails: "3", annotationUpsFailTimeout: "10"}, 3, 10},
{map[string]string{annotationUpsMaxFails: "3"}, 3, 0},
{map[string]string{annotationUpsFailTimeout: "10"}, 0, 10},
{map[string]string{}, 0, 0},
{nil, 0, 0},
}
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.HealthCheck(ing)
if r == nil {
t.Errorf("Returned nil but expected a healthcheck.Upstream")
continue
}
if r.FailTimeout != foo.euft {
t.Errorf("Returned %d but expected %d for FailTimeout", r.FailTimeout, foo.euft)
}
if r.MaxFails != foo.eumf {
t.Errorf("Returned %d but expected %d for MaxFails", r.MaxFails, foo.eumf)
}
}
}
func TestSSLPassthrough(t *testing.T) {
ec := newAnnotationExtractor(mockCfg{})
ing := buildIngress()
fooAnns := []struct {
annotations map[string]string
er bool
}{
{map[string]string{annotationPassthrough: "true"}, true},
{map[string]string{annotationPassthrough: "false"}, false},
{map[string]string{annotationPassthrough + "_no": "true"}, false},
{map[string]string{}, false},
{nil, false},
}
for _, foo := range fooAnns {
ing.SetAnnotations(foo.annotations)
r := ec.SSLPassthrough(ing)
if r != foo.er {
t.Errorf("Returned %v but expected %v", r, foo.er)
}
}
}
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)
}
}
}

View file

@ -0,0 +1,173 @@
/*
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 controller
import (
"fmt"
"reflect"
"strings"
"github.com/golang/glog"
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/client-go/tools/cache"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
"k8s.io/ingress-nginx/pkg/net/ssl"
)
// syncSecret keeps in sync Secrets used by Ingress rules with the files on
// disk to allow copy of the content of the secret to disk to be used
// by external processes.
func (ic *GenericController) syncSecret(key string) {
glog.V(3).Infof("starting syncing of secret %v", key)
cert, err := ic.getPemCertificate(key)
if err != nil {
glog.Warningf("error obtaining PEM from secret %v: %v", key, err)
return
}
// create certificates and add or update the item in the store
cur, exists := ic.sslCertTracker.Get(key)
if exists {
s := cur.(*ingress.SSLCert)
if reflect.DeepEqual(s, cert) {
// no need to update
return
}
glog.Infof("updating secret %v in the local store", key)
ic.sslCertTracker.Update(key, cert)
// this update must trigger an update
// (like an update event from a change in Ingress)
ic.syncQueue.Enqueue(&extensions.Ingress{})
return
}
glog.Infof("adding secret %v to the local store", key)
ic.sslCertTracker.Add(key, cert)
// this update must trigger an update
// (like an update event from a change in Ingress)
ic.syncQueue.Enqueue(&extensions.Ingress{})
}
// 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) {
secret, err := ic.listers.Secret.GetByName(secretName)
if err != nil {
return nil, fmt.Errorf("error retrieving secret %v: %v", secretName, err)
}
cert, okcert := secret.Data[apiv1.TLSCertKey]
key, okkey := secret.Data[apiv1.TLSPrivateKeyKey]
ca := secret.Data["ca.crt"]
// namespace/secretName -> namespace-secretName
nsSecName := strings.Replace(secretName, "/", "-", -1)
var s *ingress.SSLCert
if okcert && okkey {
if cert == nil {
return nil, fmt.Errorf("secret %v has no 'tls.crt'", secretName)
}
if key == nil {
return nil, fmt.Errorf("secret %v has no 'tls.key'", secretName)
}
// If 'ca.crt' is also present, it will allow this secret to be used in the
// 'ingress.kubernetes.io/auth-tls-secret' annotation
s, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
if err != nil {
return nil, fmt.Errorf("unexpected error creating pem file: %v", err)
}
glog.V(3).Infof("found 'tls.crt' and 'tls.key', configuring %v as a TLS Secret (CN: %v)", secretName, s.CN)
if ca != nil {
glog.V(3).Infof("found 'ca.crt', secret %v can also be used for Certificate Authentication", secretName)
}
} else if ca != nil {
s, err = ssl.AddCertAuth(nsSecName, ca)
if err != nil {
return nil, fmt.Errorf("unexpected error creating pem file: %v", err)
}
// makes this secret in 'syncSecret' to be used for Certificate Authentication
// this does not enable Certificate Authentication
glog.V(3).Infof("found only 'ca.crt', configuring %v as an Certificate Authentication Secret", secretName)
} else {
return nil, fmt.Errorf("no keypair or CA cert could be found in %v", secretName)
}
s.Name = secret.Name
s.Namespace = secret.Namespace
return s, nil
}
// checkMissingSecrets verify if one or more ingress rules contains a reference
// to a secret that is not present in the local secret store.
// In this case we call syncSecret.
func (ic *GenericController) checkMissingSecrets() {
for _, obj := range ic.listers.Ingress.List() {
ing := obj.(*extensions.Ingress)
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
continue
}
for _, tls := range ing.Spec.TLS {
if tls.SecretName == "" {
continue
}
key := fmt.Sprintf("%v/%v", ing.Namespace, tls.SecretName)
if _, ok := ic.sslCertTracker.Get(key); !ok {
ic.syncSecret(key)
}
}
key, _ := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
if key == "" {
continue
}
if _, ok := ic.sslCertTracker.Get(key); !ok {
ic.syncSecret(key)
}
}
}
// sslCertTracker holds a store of referenced Secrets in Ingress rules
type sslCertTracker struct {
cache.ThreadSafeStore
}
func newSSLCertTracker() *sslCertTracker {
return &sslCertTracker{
cache.NewThreadSafeStore(cache.Indexers{}, cache.Indices{}),
}
}
func (s *sslCertTracker) DeleteAll(key string) {
s.Delete(key)
}

View file

@ -0,0 +1,234 @@
/*
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 controller
import (
"encoding/base64"
"fmt"
"io/ioutil"
"testing"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testclient "k8s.io/client-go/kubernetes/fake"
cache_client "k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/store"
"k8s.io/ingress-nginx/pkg/task"
"k8s.io/kubernetes/pkg/api"
)
const (
// openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
tlsCrt = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQU1KZld6Mm81cWVnTUEwR0NTcUdTSWIzRFFFQkN3VUFNQ1l4RVRBUEJnTlYKQkFNTUNHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0RBaHVaMmx1ZUhOMll6QWVGdzB4TnpBME1URXdNakF3TlRCYQpGdzB5TnpBME1Ea3dNakF3TlRCYU1DWXhFVEFQQmdOVkJBTU1DRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtEQWh1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUgzVTYvY3ArODAKU3hJRjltSnlUcGI5RzBodnhsM0JMaGdQWDBTWjZ3d1lISGJXeTh2dmlCZjVwWTdvVHd0b2FPaTN1VFNsL2RtVwpvUi9XNm9GVWM5a2l6NlNXc3p6YWRXL2l2Q21LMmxOZUFVc2gvaXY0aTAvNXlreDJRNXZUT2tVL1dra2JPOW1OCjdSVTF0QW1KT3M0T1BVc3hZZkw2cnJJUzZPYktHS2UvYUVkek9QS2NPMDJ5NUxDeHM0TFhhWDIzU1l6TG1XYVAKYVZBallrN1NRZm1xUm5mYlF4RWlpaDFQWTFRRXgxWWs0RzA0VmtHUitrSVVMaWF0L291ZjQxY0dXRTZHMTF4NQpkV1BHeS9XcGtqRGlaM0UwekdNZnJBVUZibnErN1dhRTJCRzVoUVV3ZG9SQUtWTnMzaVhLRlRkT3hoRll5bnBwCjA3cDJVNS96ZHRrQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZCL2U5UnVna0Mwc0VNTTZ6enRCSjI1U1JxalMKTUI4R0ExVWRJd1FZTUJhQUZCL2U5UnVna0Mwc0VNTTZ6enRCSjI1U1JxalNNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBRys4MXdaSXRuMmFWSlFnejNkNmJvZW1nUXhSSHpaZDhNc1IrdFRvCnpJLy9ac1Nwc2FDR3F0TkdTaHVGKzB3TVZ4NjlpQ3lJTnJJb2J4K29NTHBsQzFQSk9uektSUUdvZEhYNFZaSUwKVlhxSFd2VStjK3ZtT0QxUEt3UjcwRi9rTXk2Yk4xMVI2amhIZ3RPZGdLKzdRczhRMVlUSC9RS2dMd3RJTFRHRwpTZlYxWFlmbnF1TXlZKzFzck00U3ZRSmRzdmFUQmJkZHE2RllpdjhXZFpIaG51ZGlSODdZcFgzOUlTSlFkOXF2CnR6OGthZTVqQVFEUWFiZnFsVWZNT1hmUnhyei96S2NvN3dMeWFMWTh1eVhEWUVIZmlHRWdablV0RjgxVlhDZUIKeU80UERBR0FuVmlXTndFM0NZcGI4RkNGelMyaVVVMDJaQWJRajlvUnYyUWNON1E9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
tlsKey = "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRREI5MU92M0tmdk5Fc1MKQmZaaWNrNlcvUnRJYjhaZHdTNFlEMTlFbWVzTUdCeDIxc3ZMNzRnWCthV082RThMYUdqb3Q3azBwZjNabHFFZgoxdXFCVkhQWklzK2tsck04Mm5WdjRyd3BpdHBUWGdGTElmNHIrSXRQK2NwTWRrT2IwenBGUDFwSkd6dlpqZTBWCk5iUUppVHJPRGoxTE1XSHkrcTZ5RXVqbXloaW52MmhIY3pqeW5EdE5zdVN3c2JPQzEybDl0MG1NeTVsbWoybFEKSTJKTzBrSDVxa1ozMjBNUklvb2RUMk5VQk1kV0pPQnRPRlpCa2ZwQ0ZDNG1yZjZMbitOWEJsaE9odGRjZVhWagp4c3YxcVpJdzRtZHhOTXhqSDZ3RkJXNTZ2dTFtaE5nUnVZVUZNSGFFUUNsVGJONGx5aFUzVHNZUldNcDZhZE82CmRsT2Y4M2JaQWdNQkFBRUNnZ0VBRGU1WW1XSHN3ZFpzcWQrNXdYcGFRS2Z2SkxXNmRwTmdYeVFEZ0tiWlplWDUKYldPaUFZU3pycDBra2U0SGQxZEphYVdBYk5LYk45eUV1QWUwa2hOaHVxK3dZQzdlc3JreUJCWXgwMzRBamtwTApKMzFLaHhmejBZdXNSdStialg2UFNkZnlBUnd1b1VKN1M3R3V1NXlhbDZBWU1PVmNGcHFBbjVPU0hMbFpLZnNLClN3NXZyM3NKUjNyOENNWVZoUmQ0citGam9lMXlaczJhUHl2bno5c0U3T0ZCSVRGSVBKcE4veG53VUNpWW5vSEMKV2F2TzB5RCtPeTUyN2hBQ1FwaFVMVjRaZXV2bEZwd2ZlWkZveUhnc2YrM1FxeGhpdGtJb3NGakx2Y0xlL2xjZwpSVHNRUnU5OGJNUTdSakJpYU5kaURadjBaWEMvUUMvS054SEw0bXgxTFFLQmdRRHVDY0pUM2JBZmJRY2YvSGh3CjNxRzliNE9QTXpwOTl2ajUzWU1hZHo5Vlk1dm9RR3RGeFlwbTBRVm9MR1lkQ3BHK0lXaDdVVHBMV0JUeGtMSkYKd3EwcEFmRVhmdHB0anhmcyt0OExWVUFtSXJiM2hwUjZDUjJmYjFJWVZRWUJ4dUdzN0hWMmY3NnRZMVAzSEFnNwpGTDJNTnF3ZDd5VmlsVXdSTVptcmJKV3Qwd0tCZ1FEUW1qZlgzc1NWSWZtN1FQaVQvclhSOGJMM1B3V0lNa3NOCldJTVRYeDJmaG0vd0hOL0pNdCtEK2VWbGxjSXhLMmxSYlNTQ1NwU2hzRUVsMHNxWHZUa1FFQnJxN3RFZndRWU0KbGxNbDJQb0ovV2E5c2VYSTAzWWRNeC94Vm5sbzNaUG9MUGg4UmtKekJTWkhnMlB6cCs0VmlnUklBcGdYMXo3TwpMbHg0SEVtaEl3S0JnUURES1RVdVZYL2xCQnJuV3JQVXRuT2RRU1IzNytSeENtQXZYREgxTFBlOEpxTFkxSmdlCjZFc0U2VEtwcWwwK1NrQWJ4b0JIT3QyMGtFNzdqMHJhYnpaUmZNb1NIV3N3a0RWcGtuWDBjTHpiaDNMRGxvOTkKVHFQKzUrSkRHTktIK210a3Y2bStzaFcvU3NTNHdUN3VVWjdtcXB5TEhsdGtiRXVsZlNra3B5NUJDUUtCZ0RmUwpyVk1GZUZINGI1NGV1dWJQNk5Rdi9CYVNOT2JIbnJJSmw3b2RZQTRLcWZYMXBDVnhpY01Gb3MvV2pjc2V0T1puCmNMZTFRYVVyUjZQWmp3R2dUNTd1MEdWQ1Y1QkoxVmFVKzlkTEEwNmRFMXQ4T2VQT1F2TjVkUGplalVyMDBObjIKL3VBeTVTRm1wV0hKMVh1azJ0L0V1WFNUelNQRUpEaUV5NVlRNjl0RkFvR0JBT2tDcW1jVGZGYlpPTjJRK2JqdgpvVmQvSFpLR3YrbEhqcm5maVlhODVxcUszdWJmb0FSNGppR3V3TThqc3hZZW8vb0hQdHJDTkxINndsYlZNTUFGCmlRZG80ZUF3S0xxRHo1MUx4U2hMckwzUUtNQ1FuZVhkT0VjaEdqSW9zRG5Zekd5RTBpSzJGbWNvWHVSQU1QOHgKWDFreUlkazdENDFxSjQ5WlM1OEdBbXlLCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
tlscaName = "ca.crt"
)
type MockQueue struct {
cache_client.Store
Synced bool
}
func (f *MockQueue) HasSynced() bool {
return f.Synced
}
func (f *MockQueue) AddIfNotPresent(obj interface{}) error {
return nil
}
func (f *MockQueue) Pop(process cache_client.PopProcessFunc) (interface{}, error) {
return nil, nil
}
func (f *MockQueue) Close() {
// just mock
}
func buildSimpleClientSetForBackendSSL() *testclient.Clientset {
return testclient.NewSimpleClientset()
}
func buildIngListenerForBackendSSL() store.IngressLister {
ingLister := store.IngressLister{}
ingLister.Store = cache_client.NewStore(cache_client.DeletionHandlingMetaNamespaceKeyFunc)
return ingLister
}
func buildSecretForBackendSSL() *apiv1.Secret {
return &apiv1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo_secret",
Namespace: api.NamespaceDefault,
},
}
}
func buildSecrListerForBackendSSL() store.SecretLister {
secrLister := store.SecretLister{}
secrLister.Store = cache_client.NewStore(cache_client.DeletionHandlingMetaNamespaceKeyFunc)
return secrLister
}
func buildListers() *ingress.StoreLister {
sl := &ingress.StoreLister{}
sl.Ingress.Store = buildIngListenerForBackendSSL()
sl.Secret.Store = buildSecrListerForBackendSSL()
return sl
}
func buildControllerForBackendSSL() cache_client.Controller {
cfg := &cache_client.Config{
Queue: &MockQueue{Synced: true},
}
return cache_client.New(cfg)
}
func buildGenericControllerForBackendSSL() *GenericController {
gc := &GenericController{
syncRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.3, 1),
cfg: &Configuration{
Client: buildSimpleClientSetForBackendSSL(),
},
listers: buildListers(),
sslCertTracker: newSSLCertTracker(),
}
gc.syncQueue = task.NewTaskQueue(gc.syncIngress)
return gc
}
func buildCrtKeyAndCA() ([]byte, []byte, []byte, error) {
// prepare
td, err := ioutil.TempDir("", "ssl")
if err != nil {
return nil, nil, nil, fmt.Errorf("error occurs while creating temp directory: %v", err)
}
ingress.DefaultSSLDirectory = td
dCrt, err := base64.StdEncoding.DecodeString(tlsCrt)
if err != nil {
return nil, nil, nil, err
}
dKey, err := base64.StdEncoding.DecodeString(tlsKey)
if err != nil {
return nil, nil, nil, err
}
dCa := dCrt
return dCrt, dKey, dCa, nil
}
func TestSyncSecret(t *testing.T) {
// prepare for test
dCrt, dKey, dCa, err := buildCrtKeyAndCA()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
foos := []struct {
tn string
secretName string
Data map[string][]byte
expectSuccess bool
}{
{"getPemCertificate_error", "default/foo_secret", map[string][]byte{api.TLSPrivateKeyKey: dKey}, false},
{"normal_test", "default/foo_secret", map[string][]byte{api.TLSCertKey: dCrt, api.TLSPrivateKeyKey: dKey, tlscaName: dCa}, true},
}
for _, foo := range foos {
t.Run(foo.tn, func(t *testing.T) {
ic := buildGenericControllerForBackendSSL()
// init secret for getPemCertificate
secret := buildSecretForBackendSSL()
secret.SetNamespace("default")
secret.SetName("foo_secret")
secret.Data = foo.Data
ic.listers.Secret.Add(secret)
key := "default/foo_secret"
// for add
ic.syncSecret(key)
if foo.expectSuccess {
// validate
_, exist := ic.sslCertTracker.Get(key)
if !exist {
t.Errorf("Failed to sync secret: %s", foo.secretName)
} else {
// for update
ic.syncSecret(key)
}
}
})
}
}
func TestGetPemCertificate(t *testing.T) {
// prepare
dCrt, dKey, dCa, err := buildCrtKeyAndCA()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
foos := []struct {
tn string
secretName string
Data map[string][]byte
eErr bool
}{
{"sceret_not_exist", "default/foo_secret_not_exist", nil, true},
{"data_not_complete_all_not_exist", "default/foo_secret", map[string][]byte{}, true},
{"data_not_complete_TLSCertKey_not_exist", "default/foo_secret", map[string][]byte{api.TLSPrivateKeyKey: dKey, tlscaName: dCa}, false},
{"data_not_complete_TLSCertKeyAndCA_not_exist", "default/foo_secret", map[string][]byte{api.TLSPrivateKeyKey: dKey}, true},
{"data_not_complete_TLSPrivateKeyKey_not_exist", "default/foo_secret", map[string][]byte{api.TLSCertKey: dCrt, tlscaName: dCa}, false},
{"data_not_complete_TLSPrivateKeyKeyAndCA_not_exist", "default/foo_secret", map[string][]byte{api.TLSCertKey: dCrt}, true},
{"data_not_complete_CA_not_exist", "default/foo_secret", map[string][]byte{api.TLSCertKey: dCrt, api.TLSPrivateKeyKey: dKey}, false},
{"normal_test", "default/foo_secret", map[string][]byte{api.TLSCertKey: dCrt, api.TLSPrivateKeyKey: dKey, tlscaName: dCa}, false},
}
for _, foo := range foos {
t.Run(foo.tn, func(t *testing.T) {
ic := buildGenericControllerForBackendSSL()
secret := buildSecretForBackendSSL()
secret.Data = foo.Data
ic.listers.Secret.Add(secret)
sslCert, err := ic.getPemCertificate(foo.secretName)
if foo.eErr {
if err == nil {
t.Fatal("Expected error")
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if sslCert == nil {
t.Error("Expected an ingress.SSLCert")
}
}
})
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,319 @@
package controller
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"net/http/pprof"
"os"
"strings"
"syscall"
"time"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/pflag"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/k8s"
)
// NewIngressController returns a configured Ingress controller
func NewIngressController(backend ingress.Controller) *GenericController {
var (
flags = pflag.NewFlagSet("", pflag.ExitOnError)
apiserverHost = flags.String("apiserver-host", "", "The address of the Kubernetes Apiserver "+
"to connect to in the format of protocol://address:port, e.g., "+
"http://localhost:8080. If not specified, the assumption is that the binary runs inside a "+
"Kubernetes cluster and local discovery is attempted.")
kubeConfigFile = flags.String("kubeconfig", "", "Path to kubeconfig file with authorization and master location information.")
defaultSvc = flags.String("default-backend-service", "",
`Service used to serve a 404 page for the default backend. Takes the form
namespace/name. The controller uses the first node port of this Service for
the default backend.`)
ingressClass = flags.String("ingress-class", "",
`Name of the ingress class to route through this controller.`)
configMap = flags.String("configmap", "",
`Name of the ConfigMap that contains the custom configuration to use`)
publishSvc = flags.String("publish-service", "",
`Service fronting the ingress controllers. Takes the form
namespace/name. The controller will set the endpoint records on the
ingress objects to reflect those on the service.`)
tcpConfigMapName = flags.String("tcp-services-configmap", "",
`Name of the ConfigMap that contains the definition of the TCP services to expose.
The key in the map indicates the external port to be used. The value is the name of the
service with the format namespace/serviceName and the port of the service could be a
number of the name of the port.
The ports 80 and 443 are not allowed as external ports. This ports are reserved for the backend`)
udpConfigMapName = flags.String("udp-services-configmap", "",
`Name of the ConfigMap that contains the definition of the UDP services to expose.
The key in the map indicates the external port to be used. The value is the name of the
service with the format namespace/serviceName and the port of the service could be a
number of the name of the port.`)
resyncPeriod = flags.Duration("sync-period", 600*time.Second,
`Relist and confirm cloud resources this often. Default is 10 minutes`)
watchNamespace = flags.String("watch-namespace", apiv1.NamespaceAll,
`Namespace to watch for Ingress. Default is to watch all namespaces`)
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret
that contains a SSL certificate to be used as default for a HTTPS catch-all server`)
defHealthzURL = flags.String("health-check-path", "/healthz", `Defines
the URL to be used as health check inside in the default server in NGINX.`)
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.`)
forceIsolation = flags.Bool("force-namespace-isolation", false,
`Force namespace isolation. This flag is required to avoid the reference of secrets or
configmaps located in a different namespace than the specified in the flag --watch-namespace.`)
disableNodeList = flags.Bool("disable-node-list", false,
`Disable querying nodes. If --force-namespace-isolation is true, this should also be set.`)
updateStatusOnShutdown = flags.Bool("update-status-on-shutdown", true, `Indicates if the
ingress controller should update the Ingress status IP/hostname when the controller
is being stopped. Default is true`)
sortBackends = flags.Bool("sort-backends", false,
`Defines if backends and it's endpoints should be sorted`)
)
flags.AddGoFlagSet(flag.CommandLine)
backend.ConfigureFlags(flags)
flags.Parse(os.Args)
backend.OverrideFlags(flags)
flag.Set("logtostderr", "true")
glog.Info(backend.Info())
if *ingressClass != "" {
glog.Infof("Watching for ingress class: %s", *ingressClass)
}
if *defaultSvc == "" {
glog.Fatalf("Please specify --default-backend-service")
}
kubeClient, err := createApiserverClient(*apiserverHost, *kubeConfigFile)
if err != nil {
handleFatalInitError(err)
}
ns, name, err := k8s.ParseNameNS(*defaultSvc)
if err != nil {
glog.Fatalf("invalid format for service %v: %v", *defaultSvc, err)
}
_, err = kubeClient.Core().Services(ns).Get(name, metav1.GetOptions{})
if err != nil {
if strings.Contains(err.Error(), "cannot get services in the namespace") {
glog.Fatalf("✖ It seems the cluster it is running with Authorization enabled (like RBAC) and there is no permissions for the ingress controller. Please check the configuration")
}
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
}
glog.Infof("validated %v as the default backend", *defaultSvc)
if *publishSvc != "" {
ns, name, err := k8s.ParseNameNS(*publishSvc)
if err != nil {
glog.Fatalf("invalid service format: %v", err)
}
svc, err := kubeClient.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
if err != nil {
glog.Fatalf("unexpected error getting information about service %v: %v", *publishSvc, err)
}
if len(svc.Status.LoadBalancer.Ingress) == 0 {
if len(svc.Spec.ExternalIPs) > 0 {
glog.Infof("service %v validated as assigned with externalIP", *publishSvc)
} else {
// We could poll here, but we instead just exit and rely on k8s to restart us
glog.Fatalf("service %s does not (yet) have ingress points", *publishSvc)
}
} else {
glog.Infof("service %v validated as source of Ingress status", *publishSvc)
}
}
if *watchNamespace != "" {
_, err = kubeClient.CoreV1().Namespaces().Get(*watchNamespace, metav1.GetOptions{})
if err != nil {
glog.Fatalf("no watchNamespace with name %v found: %v", *watchNamespace, err)
}
}
if resyncPeriod.Seconds() < 10 {
glog.Fatalf("resync period (%vs) is too low", resyncPeriod.Seconds())
}
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
if err != nil {
glog.Errorf("Failed to mkdir SSL directory: %v", err)
}
config := &Configuration{
UpdateStatus: *updateStatus,
ElectionID: *electionID,
Client: kubeClient,
ResyncPeriod: *resyncPeriod,
DefaultService: *defaultSvc,
IngressClass: *ingressClass,
DefaultIngressClass: backend.DefaultIngressClass(),
Namespace: *watchNamespace,
ConfigMapName: *configMap,
TCPConfigMapName: *tcpConfigMapName,
UDPConfigMapName: *udpConfigMapName,
DefaultSSLCertificate: *defSSLCertificate,
DefaultHealthzURL: *defHealthzURL,
PublishService: *publishSvc,
Backend: backend,
ForceNamespaceIsolation: *forceIsolation,
DisableNodeList: *disableNodeList,
UpdateStatusOnShutdown: *updateStatusOnShutdown,
SortBackends: *sortBackends,
}
ic := newIngressController(config)
go registerHandlers(*profiling, *healthzPort, ic)
return ic
}
func registerHandlers(enableProfiling bool, port int, ic *GenericController) {
mux := http.NewServeMux()
// expose health check endpoint (/healthz)
healthz.InstallHandler(mux,
healthz.PingHealthz,
ic.cfg.Backend,
)
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
b, _ := json.Marshal(ic.Info())
w.Write(b)
})
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
if err != nil {
glog.Errorf("unexpected error: %v", err)
}
})
if enableProfiling {
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
}
server := &http.Server{
Addr: fmt.Sprintf(":%v", port),
Handler: mux,
}
glog.Fatal(server.ListenAndServe())
}
const (
// High enough QPS to fit all expected use cases. QPS=0 is not set here, because
// client code is overriding it.
defaultQPS = 1e6
// High enough Burst to fit all expected use cases. Burst=0 is not set here, because
// client code is overriding it.
defaultBurst = 1e6
)
// buildConfigFromFlags builds REST config based on master URL and kubeconfig path.
// If both of them are empty then in cluster config is used.
func buildConfigFromFlags(masterURL, kubeconfigPath string) (*rest.Config, error) {
if kubeconfigPath == "" && masterURL == "" {
kubeconfig, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
return kubeconfig, nil
}
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&clientcmd.ConfigOverrides{
ClusterInfo: clientcmdapi.Cluster{
Server: masterURL,
},
}).ClientConfig()
}
// createApiserverClient creates new Kubernetes Apiserver client. When kubeconfig or apiserverHost param is empty
// the function assumes that it is running inside a Kubernetes cluster and attempts to
// discover the Apiserver. Otherwise, it connects to the Apiserver specified.
//
// apiserverHost param is in the format of protocol://address:port/pathPrefix, e.g.http://localhost:8001.
// kubeConfig location of kubeconfig file
func createApiserverClient(apiserverHost string, kubeConfig string) (*kubernetes.Clientset, error) {
cfg, err := buildConfigFromFlags(apiserverHost, kubeConfig)
if err != nil {
return nil, err
}
cfg.QPS = defaultQPS
cfg.Burst = defaultBurst
cfg.ContentType = "application/vnd.kubernetes.protobuf"
glog.Infof("Creating API client for %s", cfg.Host)
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
v, err := client.Discovery().ServerVersion()
if err != nil {
return nil, err
}
glog.Infof("Running in Kubernetes Cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)
return client, nil
}
/**
* Handles fatal init error that prevents server from doing any work. Prints verbose error
* message and quits the server.
*/
func handleFatalInitError(err error) {
glog.Fatalf("Error while initializing connection to Kubernetes apiserver. "+
"This most likely means that the cluster is misconfigured (e.g., it has "+
"invalid apiserver certificates or service accounts configuration). Reason: %s\n"+
"Refer to the troubleshooting guide for more information: "+
"https://github.com/kubernetes/ingress/blob/master/docs/troubleshooting.md", err)
}

View file

@ -0,0 +1,236 @@
/*
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 controller
import (
"fmt"
"reflect"
"github.com/golang/glog"
apiv1 "k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache"
fcache "k8s.io/client-go/tools/cache/testing"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
)
type cacheController struct {
Ingress cache.Controller
Endpoint cache.Controller
Service cache.Controller
Node cache.Controller
Secret cache.Controller
Configmap cache.Controller
}
func (c *cacheController) Run(stopCh chan struct{}) {
go c.Ingress.Run(stopCh)
go c.Endpoint.Run(stopCh)
go c.Service.Run(stopCh)
go c.Node.Run(stopCh)
go c.Secret.Run(stopCh)
go c.Configmap.Run(stopCh)
// Wait for all involved caches to be synced, before processing items from the queue is started
if !cache.WaitForCacheSync(stopCh,
c.Ingress.HasSynced,
c.Endpoint.HasSynced,
c.Service.HasSynced,
c.Node.HasSynced,
c.Secret.HasSynced,
c.Configmap.HasSynced,
) {
runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
}
}
func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.StoreLister, *cacheController) {
// from here to the end of the method all the code is just boilerplate
// required to watch Ingress, Secrets, ConfigMaps and Endoints.
// This is used to detect new content, updates or removals and act accordingly
ingEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
addIng := obj.(*extensions.Ingress)
if !class.IsValid(addIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
a, _ := parser.GetStringAnnotation(class.IngressKey, addIng)
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", addIng.Name, class.IngressKey, a)
return
}
ic.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
ic.syncQueue.Enqueue(obj)
},
DeleteFunc: func(obj interface{}) {
delIng, ok := obj.(*extensions.Ingress)
if !ok {
// If we reached here it means the ingress was deleted but its final state is unrecorded.
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
glog.Errorf("couldn't get object from tombstone %#v", obj)
return
}
delIng, ok = tombstone.Obj.(*extensions.Ingress)
if !ok {
glog.Errorf("Tombstone contained object that is not an Ingress: %#v", obj)
return
}
}
if !class.IsValid(delIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
glog.Infof("ignoring delete for ingress %v based on annotation %v", delIng.Name, class.IngressKey)
return
}
ic.recorder.Eventf(delIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", delIng.Namespace, delIng.Name))
ic.syncQueue.Enqueue(obj)
},
UpdateFunc: func(old, cur interface{}) {
oldIng := old.(*extensions.Ingress)
curIng := cur.(*extensions.Ingress)
validOld := class.IsValid(oldIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass)
validCur := class.IsValid(curIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass)
if !validOld && validCur {
glog.Infof("creating ingress %v based on annotation %v", curIng.Name, class.IngressKey)
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
} else if validOld && !validCur {
glog.Infof("removing ingress %v based on annotation %v", curIng.Name, class.IngressKey)
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
} else if validCur && !reflect.DeepEqual(old, cur) {
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
}
ic.syncQueue.Enqueue(cur)
},
}
secrEventHandler := cache.ResourceEventHandlerFuncs{
UpdateFunc: func(old, cur interface{}) {
if !reflect.DeepEqual(old, cur) {
sec := cur.(*apiv1.Secret)
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
ic.syncSecret(key)
}
},
DeleteFunc: func(obj interface{}) {
sec, ok := obj.(*apiv1.Secret)
if !ok {
// If we reached here it means the secret was deleted but its final state is unrecorded.
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
glog.Errorf("couldn't get object from tombstone %#v", obj)
return
}
sec, ok = tombstone.Obj.(*apiv1.Secret)
if !ok {
glog.Errorf("Tombstone contained object that is not a Secret: %#v", obj)
return
}
}
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
ic.sslCertTracker.DeleteAll(key)
ic.syncQueue.Enqueue(key)
},
}
eventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
ic.syncQueue.Enqueue(obj)
},
DeleteFunc: func(obj interface{}) {
ic.syncQueue.Enqueue(obj)
},
UpdateFunc: func(old, cur interface{}) {
oep := old.(*apiv1.Endpoints)
ocur := cur.(*apiv1.Endpoints)
if !reflect.DeepEqual(ocur.Subsets, oep.Subsets) {
ic.syncQueue.Enqueue(cur)
}
},
}
mapEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
upCmap := obj.(*apiv1.ConfigMap)
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
if mapKey == ic.cfg.ConfigMapName {
glog.V(2).Infof("adding configmap %v to backend", mapKey)
ic.cfg.Backend.SetConfig(upCmap)
ic.setForceReload(true)
}
},
UpdateFunc: func(old, cur interface{}) {
if !reflect.DeepEqual(old, cur) {
upCmap := cur.(*apiv1.ConfigMap)
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
if mapKey == ic.cfg.ConfigMapName {
glog.V(2).Infof("updating configmap backend (%v)", mapKey)
ic.cfg.Backend.SetConfig(upCmap)
ic.setForceReload(true)
}
// updates to configuration configmaps can trigger an update
if mapKey == ic.cfg.ConfigMapName || mapKey == ic.cfg.TCPConfigMapName || mapKey == ic.cfg.UDPConfigMapName {
ic.recorder.Eventf(upCmap, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("ConfigMap %v", mapKey))
ic.syncQueue.Enqueue(cur)
}
}
},
}
watchNs := apiv1.NamespaceAll
if ic.cfg.ForceNamespaceIsolation && ic.cfg.Namespace != apiv1.NamespaceAll {
watchNs = ic.cfg.Namespace
}
lister := &ingress.StoreLister{}
controller := &cacheController{}
lister.Ingress.Store, controller.Ingress = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.ExtensionsV1beta1().RESTClient(), "ingresses", ic.cfg.Namespace, fields.Everything()),
&extensions.Ingress{}, ic.cfg.ResyncPeriod, ingEventHandler)
lister.Endpoint.Store, controller.Endpoint = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "endpoints", ic.cfg.Namespace, fields.Everything()),
&apiv1.Endpoints{}, ic.cfg.ResyncPeriod, eventHandler)
lister.Secret.Store, controller.Secret = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "secrets", watchNs, fields.Everything()),
&apiv1.Secret{}, ic.cfg.ResyncPeriod, secrEventHandler)
lister.ConfigMap.Store, controller.Configmap = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "configmaps", watchNs, fields.Everything()),
&apiv1.ConfigMap{}, ic.cfg.ResyncPeriod, mapEventHandler)
lister.Service.Store, controller.Service = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "services", ic.cfg.Namespace, fields.Everything()),
&apiv1.Service{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
var nodeListerWatcher cache.ListerWatcher
if disableNodeLister {
nodeListerWatcher = fcache.NewFakeControllerSource()
} else {
nodeListerWatcher = cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "nodes", apiv1.NamespaceAll, fields.Everything())
}
lister.Node.Store, controller.Node = cache.NewInformer(
nodeListerWatcher,
&apiv1.Node{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
return lister, controller
}

View file

@ -0,0 +1,84 @@
/*
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 controller
import (
"github.com/prometheus/client_golang/prometheus"
"k8s.io/ingress-nginx/pkg/ingress"
)
const (
ns = "ingress_controller"
operation = "count"
reloadLabel = "reloads"
sslLabelExpire = "ssl_expire_time_seconds"
sslLabelHost = "host"
)
func init() {
prometheus.MustRegister(reloadOperation)
prometheus.MustRegister(reloadOperationErrors)
prometheus.MustRegister(sslExpireTime)
}
var (
reloadOperation = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: ns,
Name: "success",
Help: "Cumulative number of Ingress controller reload operations",
},
[]string{operation},
)
reloadOperationErrors = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: ns,
Name: "errors",
Help: "Cumulative number of Ingress controller errors during reload operations",
},
[]string{operation},
)
sslExpireTime = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: ns,
Name: sslLabelExpire,
Help: "Number of seconds since 1970 to the SSL Certificate expire. An example to check if this " +
"certificate will expire in 10 days is: \"ingress_controller_ssl_expire_time_seconds < (time() + (10 * 24 * 3600))\"",
},
[]string{sslLabelHost},
)
)
func incReloadCount() {
reloadOperation.WithLabelValues(reloadLabel).Inc()
}
func incReloadErrorCount() {
reloadOperationErrors.WithLabelValues(reloadLabel).Inc()
}
func setSSLExpireTime(servers []*ingress.Server) {
for _, s := range servers {
if s.Hostname != defServerName {
sslExpireTime.WithLabelValues(s.Hostname).Set(float64(s.SSLExpireTime.Unix()))
}
}
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"github.com/golang/glog"
"github.com/imdario/mergo"
api "k8s.io/api/core/v1"
"k8s.io/ingress-nginx/pkg/ingress"
)
// DeniedKeyName name of the key that contains the reason to deny a location
const DeniedKeyName = "Denied"
// newUpstream creates an upstream without servers.
func newUpstream(name string) *ingress.Backend {
return &ingress.Backend{
Name: name,
Endpoints: []ingress.Endpoint{},
Service: &api.Service{},
SessionAffinity: ingress.SessionAffinityConfig{
CookieSessionAffinity: ingress.CookieSessionAffinity{
Locations: make(map[string][]string),
},
},
}
}
func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}) {
if _, ok := anns[DeniedKeyName]; ok {
loc.Denied = anns[DeniedKeyName].(error)
}
delete(anns, DeniedKeyName)
err := mergo.Map(loc, anns)
if err != nil {
glog.Errorf("unexpected error merging extracted annotations in location type: %v", err)
}
}

View file

@ -0,0 +1,82 @@
/*
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 controller
import (
"reflect"
"testing"
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
"k8s.io/ingress-nginx/pkg/ingress/annotations/ipwhitelist"
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress-nginx/pkg/ingress/annotations/redirect"
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
)
type fakeError struct{}
func (fe *fakeError) Error() string {
return "fakeError"
}
func TestMergeLocationAnnotations(t *testing.T) {
// initial parameters
loc := ingress.Location{}
annotations := map[string]interface{}{
"Path": "/checkpath",
"IsDefBackend": true,
"Backend": "foo_backend",
"BasicDigestAuth": auth.BasicDigest{},
DeniedKeyName: &fakeError{},
"EnableCORS": true,
"ExternalAuth": authreq.External{},
"RateLimit": ratelimit.RateLimit{},
"Redirect": redirect.Redirect{},
"Rewrite": rewrite.Redirect{},
"Whitelist": ipwhitelist.SourceRange{},
"Proxy": proxy.Configuration{},
"UsePortInRedirects": true,
}
// create test table
type fooMergeLocationAnnotationsStruct struct {
fName string
er interface{}
}
fooTests := []fooMergeLocationAnnotationsStruct{}
for name, value := range annotations {
fva := fooMergeLocationAnnotationsStruct{name, value}
fooTests = append(fooTests, fva)
}
// execute test
mergeLocationAnnotations(&loc, annotations)
// check result
for _, foo := range fooTests {
fv := reflect.ValueOf(loc).FieldByName(foo.fName).Interface()
if !reflect.DeepEqual(fv, foo.er) {
t.Errorf("Returned %v but expected %v for the field %s", fv, foo.er, foo.fName)
}
}
if _, ok := annotations[DeniedKeyName]; ok {
t.Errorf("%s should be removed after mergeLocationAnnotations", DeniedKeyName)
}
}