Merge remote-tracking branch 'upstream/master' into nginx/extauth_headers

# Conflicts:
#	core/pkg/ingress/annotations/authreq/main.go
This commit is contained in:
rsafronov 2017-03-13 15:04:37 -04:00
commit 7034e1de69
82 changed files with 3053 additions and 724 deletions

View file

@ -29,17 +29,19 @@ import (
const (
// external URL that provides the authentication
authURL = "ingress.kubernetes.io/auth-url"
authMethod = "ingress.kubernetes.io/auth-method"
authBody = "ingress.kubernetes.io/auth-send-body"
authURL = "ingress.kubernetes.io/auth-url"
authSigninURL = "ingress.kubernetes.io/auth-signin"
authMethod = "ingress.kubernetes.io/auth-method"
authBody = "ingress.kubernetes.io/auth-send-body"
authHeaders = "ingress.kubernetes.io/auth-response-headers"
)
// External returns external authentication configuration for an Ingress rule
type External struct {
URL string `json:"url"`
Method string `json:"method"`
SendBody bool `json:"sendBody"`
URL string `json:"url"`
SigninURL string `json:"signinUrl"`
Method string `json:"method"`
SendBody bool `json:"sendBody"`
ResponseHeaders []string `json:"responseHeaders"`
}
@ -85,6 +87,8 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, ing_errors.NewLocationDenied("an empty string is not a valid URL")
}
signin, _ := parser.GetStringAnnotation(authSigninURL, ing)
ur, err := url.Parse(str)
if err != nil {
return nil, err
@ -100,11 +104,7 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
return nil, ing_errors.NewLocationDenied("invalid url host")
}
m, err := parser.GetStringAnnotation(authMethod, ing)
if err != nil {
return nil, err
}
m, _ := parser.GetStringAnnotation(authMethod, ing)
if len(m) != 0 && !validMethod(m) {
return nil, ing_errors.NewLocationDenied("invalid HTTP method")
}
@ -128,9 +128,10 @@ func (a authReq) Parse(ing *extensions.Ingress) (interface{}, error) {
sb, _ := parser.GetBoolAnnotation(authBody, ing)
return &External{
URL: str,
Method: m,
SendBody: sb,
URL: str,
SigninURL: signin,
Method: m,
SendBody: sb,
ResponseHeaders: h,
}, nil
}

View file

@ -68,23 +68,25 @@ func TestAnnotations(t *testing.T) {
ing.SetAnnotations(data)
tests := []struct {
title string
url string
method string
sendBody bool
expErr bool
title string
url string
signinURL string
method string
sendBody bool
expErr bool
}{
{"empty", "", "", false, true},
{"no scheme", "bar", "", false, true},
{"invalid host", "http://", "", false, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "", false, true},
{"valid URL", "http://bar.foo.com/external-auth", "", false, false},
{"valid URL - send body", "http://foo.com/external-auth", "POST", true, false},
{"valid URL - send body", "http://foo.com/external-auth", "GET", true, false},
{"empty", "", "", "", false, true},
{"no scheme", "bar", "bar", "", false, true},
{"invalid host", "http://", "http://", "", false, true},
{"invalid host (multiple dots)", "http://foo..bar.com", "http://foo..bar.com", "", false, true},
{"valid URL", "http://bar.foo.com/external-auth", "http://bar.foo.com/external-auth", "", false, false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "POST", true, false},
{"valid URL - send body", "http://foo.com/external-auth", "http://foo.com/external-auth", "GET", true, false},
}
for _, test := range tests {
data[authURL] = test.url
data[authSigninURL] = test.signinURL
data[authBody] = fmt.Sprintf("%v", test.sendBody)
data[authMethod] = fmt.Sprintf("%v", test.method)
@ -102,6 +104,9 @@ func TestAnnotations(t *testing.T) {
if u.URL != test.url {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.url, u.URL)
}
if u.SigninURL != test.signinURL {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.signinURL, u.SigninURL)
}
if u.Method != test.method {
t.Errorf("%v: expected \"%v\" but \"%v\" was returned", test.title, test.method, u.Method)
}

View file

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

View file

@ -0,0 +1,58 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package class
import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
)
func TestIsValidClass(t *testing.T) {
tests := []struct {
ingress string
controller string
defClass string
isValid bool
}{
{"", "", "nginx", true},
{"", "nginx", "nginx", true},
{"nginx", "nginx", "nginx", true},
{"custom", "custom", "nginx", true},
{"", "killer", "nginx", false},
{"", "", "nginx", true},
{"custom", "nginx", "nginx", false},
}
ing := &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
}
data := map[string]string{}
ing.SetAnnotations(data)
for _, test := range tests {
ing.Annotations[IngressKey] = test.ingress
b := IsValid(ing, test.controller, test.defClass)
if b != test.isValid {
t.Errorf("test %v - expected %v but %v was returned", test, test.isValid, b)
}
}
}

View file

@ -36,10 +36,10 @@ const (
defSharedSize = 5
)
// RateLimit returns rate limit configuration for an Ingress rule
// Is possible to limit the number of connections per IP address or
// connections per second.
// Note: Is possible to specify both limits
// RateLimit returns rate limit configuration for an Ingress rule limiting the
// number of connections per IP address and/or connections per second.
// If you both annotations are specified in a single Ingress rule, RPS limits
// takes precedence
type RateLimit struct {
// Connections indicates a limit with the number of connections per IP address
Connections Zone `json:"connections"`

View file

@ -24,9 +24,10 @@ import (
)
const (
rewriteTo = "ingress.kubernetes.io/rewrite-target"
addBaseURL = "ingress.kubernetes.io/add-base-url"
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
rewriteTo = "ingress.kubernetes.io/rewrite-target"
addBaseURL = "ingress.kubernetes.io/add-base-url"
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
)
// Redirect describes the per location redirect config
@ -38,6 +39,8 @@ type Redirect struct {
AddBaseURL bool `json:"addBaseUrl"`
// SSLRedirect indicates if the location section is accessible SSL only
SSLRedirect bool `json:"sslRedirect"`
// ForceSSLRedirect indicates if the location section is accessible SSL only
ForceSSLRedirect bool `json:"forceSSLRedirect"`
}
type rewrite struct {
@ -57,10 +60,15 @@ func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
if err != nil {
sslRe = a.backendResolver.GetDefaultBackend().SSLRedirect
}
fSslRe, err := parser.GetBoolAnnotation(forceSSLRedirect, ing)
if err != nil {
fSslRe = a.backendResolver.GetDefaultBackend().ForceSSLRedirect
}
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
return &Redirect{
Target: rt,
AddBaseURL: abu,
SSLRedirect: sslRe,
Target: rt,
AddBaseURL: abu,
SSLRedirect: sslRe,
ForceSSLRedirect: fSslRe,
}, nil
}

View file

@ -117,10 +117,6 @@ func TestSSLRedirect(t *testing.T) {
t.Errorf("Expected true but returned false")
}
if !redirect.SSLRedirect {
t.Errorf("Expected true but returned false")
}
data[sslRedirect] = "false"
ing.SetAnnotations(data)
@ -133,3 +129,32 @@ func TestSSLRedirect(t *testing.T) {
t.Errorf("Expected false but returned true")
}
}
func TestForceSSLRedirect(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[rewriteTo] = defRoute
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if redirect.ForceSSLRedirect {
t.Errorf("Expected false but returned true")
}
data[forceSSLRedirect] = "true"
ing.SetAnnotations(data)
i, _ = NewParser(mockBackend{false}).Parse(ing)
redirect, ok = i.(*Redirect)
if !ok {
t.Errorf("expected a Redirect type")
}
if !redirect.ForceSSLRedirect {
t.Errorf("Expected true but returned false")
}
}

View file

@ -43,28 +43,9 @@ func (ic *GenericController) syncSecret(k interface{}) error {
return fmt.Errorf("deferring sync till endpoints controller has synced")
}
// check if the default certificate is configured
key := fmt.Sprintf("default/%v", defServerName)
_, exists := ic.sslCertTracker.Get(key)
var key string
var cert *ingress.SSLCert
var err error
if !exists {
if ic.cfg.DefaultSSLCertificate != "" {
cert, err = ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
if err != nil {
return err
}
} else {
defCert, defKey := ssl.GetFakeSSLCert()
cert, err = ssl.AddOrUpdateCertAndKey("system-snake-oil-certificate", defCert, defKey, []byte{})
if err != nil {
return nil
}
}
cert.Name = defServerName
cert.Namespace = api.NamespaceDefault
ic.sslCertTracker.Add(key, cert)
}
key = k.(string)

View file

@ -18,6 +18,7 @@ package controller
import (
"fmt"
"os"
"reflect"
"sort"
"strconv"
@ -40,6 +41,7 @@ import (
cache_store "k8s.io/ingress/core/pkg/cache"
"k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/class"
"k8s.io/ingress/core/pkg/ingress/annotations/healthcheck"
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
"k8s.io/ingress/core/pkg/ingress/annotations/service"
@ -57,11 +59,6 @@ const (
defServerName = "_"
podStoreSyncedPollPeriod = 1 * time.Second
rootLocation = "/"
// ingressClassKey picks a specific "class" for the Ingress. The controller
// only processes Ingresses with this annotation either unset, or set
// to either the configured value or the empty string.
ingressClassKey = "kubernetes.io/ingress.class"
)
var (
@ -127,6 +124,7 @@ type Configuration struct {
UDPConfigMapName string
DefaultSSLCertificate string
DefaultHealthzURL string
DefaultIngressClass string
// optional
PublishService string
// Backend is the particular implementation to be used.
@ -166,8 +164,8 @@ func newIngressController(config *Configuration) *GenericController {
ingEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
addIng := obj.(*extensions.Ingress)
if !IsValidClass(addIng, config.IngressClass) {
glog.Infof("ignoring add for ingress %v based on annotation %v", addIng.Name, ingressClassKey)
if !class.IsValid(addIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
glog.Infof("ignoring add for ingress %v based on annotation %v", addIng.Name, class.IngressKey)
return
}
ic.recorder.Eventf(addIng, api.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
@ -175,8 +173,8 @@ func newIngressController(config *Configuration) *GenericController {
},
DeleteFunc: func(obj interface{}) {
delIng := obj.(*extensions.Ingress)
if !IsValidClass(delIng, config.IngressClass) {
glog.Infof("ignoring delete for ingress %v based on annotation %v", delIng.Name, ingressClassKey)
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, api.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", delIng.Namespace, delIng.Name))
@ -185,7 +183,8 @@ func newIngressController(config *Configuration) *GenericController {
UpdateFunc: func(old, cur interface{}) {
oldIng := old.(*extensions.Ingress)
curIng := cur.(*extensions.Ingress)
if !IsValidClass(curIng, config.IngressClass) && !IsValidClass(oldIng, config.IngressClass) {
if !class.IsValid(curIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) &&
!class.IsValid(oldIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
return
}
@ -301,10 +300,12 @@ func newIngressController(config *Configuration) *GenericController {
if config.UpdateStatus {
ic.syncStatus = status.NewStatusSyncer(status.Config{
Client: config.Client,
PublishService: ic.cfg.PublishService,
IngressLister: ic.ingLister,
ElectionID: config.ElectionID,
Client: config.Client,
PublishService: ic.cfg.PublishService,
IngressLister: ic.ingLister,
ElectionID: config.ElectionID,
IngressClass: config.IngressClass,
DefaultIngressClass: config.DefaultIngressClass,
})
} else {
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
@ -588,7 +589,7 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
for _, ingIf := range ings {
ing := ingIf.(*extensions.Ingress)
if !IsValidClass(ing, ic.cfg.IngressClass) {
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
continue
}
@ -711,7 +712,7 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
for _, ingIf := range data {
ing := ingIf.(*extensions.Ingress)
if !IsValidClass(ing, ic.cfg.IngressClass) {
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
continue
}
@ -836,19 +837,30 @@ func (ic *GenericController) createServers(data []interface{},
CookiePath: bdef.ProxyCookiePath,
}
// This adds the Default Certificate to Default Backend and also for vhosts missing the secret
// This adds the Default Certificate to Default Backend (or generates a new self signed one)
var defaultPemFileName, defaultPemSHA string
// Tries to fetch the default Certificate. If it does not exists, generate a new self signed one.
defaultCertificate, err := ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
// If no default Certificate was supplied, tries to generate a new dumb one
if err != nil {
var cert *ingress.SSLCert
defCert, defKey := ssl.GetFakeSSLCert()
cert, err = ssl.AddOrUpdateCertAndKey("system-snake-oil-certificate", defCert, defKey, []byte{})
// This means the Default Secret does not exists, so we will create a new one.
fakeCertificate := "default-fake-certificate"
fakeCertificatePath := fmt.Sprintf("%v/%v.pem", ingress.DefaultSSLDirectory, fakeCertificate)
// Only generates a new certificate if it doesn't exists physically
_, err := os.Stat(fakeCertificatePath)
if err != nil {
glog.Fatalf("Error generating self signed certificate: %v", err)
glog.V(3).Infof("No Default SSL Certificate found. Generating a new one")
defCert, defKey := ssl.GetFakeSSLCert()
defaultCertificate, err = ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
if err != nil {
glog.Fatalf("Error generating self signed certificate: %v", err)
}
defaultPemFileName = defaultCertificate.PemFileName
defaultPemSHA = defaultCertificate.PemSHA
} else {
defaultPemFileName = cert.PemFileName
defaultPemSHA = cert.PemSHA
defaultPemFileName = fakeCertificatePath
defaultPemSHA = ssl.PemSHA1(fakeCertificatePath)
}
} else {
defaultPemFileName = defaultCertificate.PemFileName
@ -872,7 +884,7 @@ func (ic *GenericController) createServers(data []interface{},
// initialize all the servers
for _, ingIf := range data {
ing := ingIf.(*extensions.Ingress)
if !IsValidClass(ing, ic.cfg.IngressClass) {
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
continue
}
@ -912,7 +924,7 @@ func (ic *GenericController) createServers(data []interface{},
// configure default location and SSL
for _, ingIf := range data {
ing := ingIf.(*extensions.Ingress)
if !IsValidClass(ing, ic.cfg.IngressClass) {
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
continue
}
@ -943,9 +955,6 @@ func (ic *GenericController) createServers(data []interface{},
servers[host].SSLCertificate = cert.PemFileName
servers[host].SSLPemChecksum = cert.PemSHA
}
} else {
servers[host].SSLCertificate = defaultPemFileName
servers[host].SSLPemChecksum = defaultPemSHA
}
}
}

View file

@ -135,7 +135,10 @@ func NewIngressController(backend ingress.Controller) *GenericController {
}
}
os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
if err != nil {
glog.Errorf("Failed to mkdir SSL directory: %v", err)
}
config := &Configuration{
UpdateStatus: *updateStatus,
@ -144,6 +147,7 @@ func NewIngressController(backend ingress.Controller) *GenericController {
ResyncPeriod: *resyncPeriod,
DefaultService: *defaultSvc,
IngressClass: *ingressClass,
DefaultIngressClass: backend.DefaultIngressClass(),
Namespace: *watchNamespace,
ConfigMapName: *configMap,
TCPConfigMapName: *tcpConfigMapName,

View file

@ -22,11 +22,7 @@ import (
"github.com/golang/glog"
"github.com/imdario/mergo"
"k8s.io/kubernetes/pkg/apis/extensions"
"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
@ -85,25 +81,6 @@ func matchHostnames(pattern, host string) bool {
return true
}
// IsValidClass returns true if the given Ingress either doesn't specify
// the ingress.class annotation, or it's set to the configured in the
// ingress controller.
func IsValidClass(ing *extensions.Ingress, class string) bool {
if class == "" {
return true
}
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
}
return cc == class
}
func mergeLocationAnnotations(loc *ingress.Location, anns map[string]interface{}) {
if _, ok := anns[DeniedKeyName]; ok {
loc.Denied = anns[DeniedKeyName].(error)

View file

@ -29,8 +29,6 @@ import (
"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/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
)
type fakeError struct{}
@ -39,37 +37,6 @@ func (fe *fakeError) Error() string {
return "fakeError"
}
func TestIsValidClass(t *testing.T) {
ing := &extensions.Ingress{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
}
b := IsValidClass(ing, "")
if !b {
t.Error("Expected a valid class (missing annotation)")
}
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)
}
b = IsValidClass(ing, "nginx")
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) {
fkCert := &ingress.SSLCert{
CAFileName: "foo",

View file

@ -59,6 +59,10 @@ type Backend struct {
// Enables or disables the redirect (301) to the HTTPS port
SSLRedirect bool `json:"ssl-redirect"`
// Enables or disables the redirect (301) to the HTTPS port even without TLS cert
// This is useful if doing SSL offloading outside of cluster eg AWS ELB
ForceSSLRedirect bool `json:"force-ssl-redirect"`
// Enables or disables the specification of port in redirects
// Default: false
UsePortInRedirects bool `json:"use-port-in-redirects"`

View file

@ -54,10 +54,6 @@ type ServerByName []*Server
func (c ServerByName) Len() int { return len(c) }
func (c ServerByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c ServerByName) Less(i, j int) bool {
// special case for catch all server
if c[j].Hostname == "_" {
return false
}
return c[i].Hostname < c[j].Hostname
}

View file

@ -32,6 +32,7 @@ import (
"k8s.io/kubernetes/pkg/util/wait"
cache_store "k8s.io/ingress/core/pkg/cache"
"k8s.io/ingress/core/pkg/ingress/annotations/class"
"k8s.io/ingress/core/pkg/k8s"
"k8s.io/ingress/core/pkg/strings"
"k8s.io/ingress/core/pkg/task"
@ -53,6 +54,9 @@ type Config struct {
PublishService string
IngressLister cache_store.StoreToIngressLister
ElectionID string
DefaultIngressClass string
IngressClass string
}
// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
@ -243,6 +247,11 @@ func (s *statusSync) updateStatus(newIPs []api.LoadBalancerIngress) {
wg.Add(len(ings))
for _, cur := range ings {
ing := cur.(*extensions.Ingress)
if !class.IsValid(ing, s.Config.IngressClass, s.Config.DefaultIngressClass) {
continue
}
go func(wg *sync.WaitGroup) {
defer wg.Done()
ingClient := s.Client.Extensions().Ingresses(ing.Namespace)

View file

@ -96,6 +96,8 @@ type Controller interface {
Info() *BackendInfo
// OverrideFlags allow the customization of the flags in the backend
OverrideFlags(*pflag.FlagSet)
// DefaultIngressClass just return the default ingress class
DefaultIngressClass() string
}
// StoreLister returns the configured stores for ingresses, services,

View file

@ -17,14 +17,19 @@ limitations under the License.
package ssl
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"time"
"github.com/golang/glog"
@ -63,21 +68,25 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}
pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("No valid PEM formatted block found")
}
// If the file does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
if pemBlock.Type != "CERTIFICATE" {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("Certificate %v contains invalid data, and must be created with 'kubectl create secret tls'", name)
}
pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
_ = os.Remove(tempPemFile.Name())
return nil, err
}
@ -120,14 +129,14 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
return &ingress.SSLCert{
CAFileName: pemFileName,
PemFileName: pemFileName,
PemSHA: pemSHA1(pemFileName),
PemSHA: PemSHA1(pemFileName),
CN: cn,
}, nil
}
return &ingress.SSLCert{
PemFileName: pemFileName,
PemSHA: pemSHA1(pemFileName),
PemSHA: PemSHA1(pemFileName),
CN: cn,
}, nil
}
@ -162,34 +171,61 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
return &ingress.SSLCert{
CAFileName: caFileName,
PemFileName: caFileName,
PemSHA: pemSHA1(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
func SearchDHParamFile(baseDir string) string {
files, _ := ioutil.ReadDir(baseDir)
for _, file := range files {
if !file.IsDir() {
continue
}
// AddOrUpdateDHParam creates a dh parameters file with the specified name
func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
pemName := fmt.Sprintf("%v.pem", name)
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
dhPath := fmt.Sprintf("%v/%v/dhparam.pem", baseDir, file.Name())
if _, err := os.Stat(dhPath); err == nil {
glog.Infof("using file '%v' for parameter ssl_dhparam", dhPath)
return dhPath
}
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
glog.V(3).Infof("Creating temp file %v for DH param: %v", tempPemFile.Name(), pemName)
if err != nil {
return "", fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err)
}
glog.Warning("no file dhparam.pem found in secrets")
return ""
_, err = tempPemFile.Write(dh)
if err != nil {
return "", fmt.Errorf("could not write to pem file %v: %v", tempPemFile.Name(), err)
}
err = tempPemFile.Close()
if err != nil {
return "", fmt.Errorf("could not close temp pem file %v: %v", tempPemFile.Name(), err)
}
pemCerts, err := ioutil.ReadFile(tempPemFile.Name())
if err != nil {
_ = os.Remove(tempPemFile.Name())
return "", err
}
pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("No valid PEM formatted block found")
}
// If the file does not start with 'BEGIN DH PARAMETERS' it's invalid and must not be used.
if pemBlock.Type != "DH PARAMETERS" {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("Certificate %v contains invalid data", name)
}
err = os.Rename(tempPemFile.Name(), pemFileName)
if err != nil {
return "", fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempPemFile.Name(), pemFileName, err)
}
return pemFileName, nil
}
// pemSHA1 returns the SHA1 of a pem file. This is used to
// PemSHA1 returns the SHA1 of a pem file. This is used to
// reload NGINX in case a secret with a SSL certificate changed.
func pemSHA1(filename string) string {
func PemSHA1(filename string) string {
hasher := sha1.New()
s, err := ioutil.ReadFile(filename)
if err != nil {
@ -200,23 +236,52 @@ func pemSHA1(filename string) string {
return hex.EncodeToString(hasher.Sum(nil))
}
const (
snakeOilPem = "/etc/ssl/certs/ssl-cert-snakeoil.pem"
snakeOilKey = "/etc/ssl/private/ssl-cert-snakeoil.key"
)
// GetFakeSSLCert returns the snake oil ssl certificate created by the command
// make-ssl-cert generate-default-snakeoil --force-overwrite
// GetFakeSSLCert creates a Self Signed Certificate
// Based in the code https://golang.org/src/crypto/tls/generate_cert.go
func GetFakeSSLCert() ([]byte, []byte) {
cert, err := ioutil.ReadFile(snakeOilPem)
var priv interface{}
var err error
priv, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil
glog.Fatalf("failed to generate fake private key: %s", err)
}
key, err := ioutil.ReadFile(snakeOilKey)
notBefore := time.Now()
// This certificate is valid for 365 days
notAfter := notBefore.Add(365 * 24 * time.Hour)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, nil
glog.Fatalf("failed to generate fake serial number: %s", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
CommonName: "Kubernetes Ingress Controller Fake Certificate",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{"ingress.local"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.(*rsa.PrivateKey).PublicKey, priv)
if err != nil {
glog.Fatalf("Failed to create fake certificate: %s", err)
}
cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
key := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv.(*rsa.PrivateKey))})
return cert, key
}