mirror of
				https://github.com/containous/traefik.git
				synced 2025-10-30 20:24:28 +03:00 
			
		
		
		
	Compare commits
	
		
			16 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a467eb6244 | ||
|  | faae17839d | ||
|  | 00315d3d55 | ||
|  | 146a1016a8 | ||
|  | ca9822cc5f | ||
|  | 71980c9785 | ||
|  | a3a9edc3c6 | ||
|  | 67b25550c0 | ||
|  | fa62ccb0c2 | ||
|  | f5bfe681c2 | ||
|  | 19417d8af5 | ||
|  | 59b3a392f2 | ||
|  | 25bab338c1 | ||
|  | 3f6993f0a0 | ||
|  | 70f39c3326 | ||
|  | 81560827c6 | 
							
								
								
									
										39
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,44 @@ | ||||
| # Change Log | ||||
|  | ||||
| ## [v1.1.2](https://github.com/containous/traefik/tree/v1.1.2) (2016-12-15) | ||||
| [Full Changelog](https://github.com/containous/traefik/compare/v1.1.1...v1.1.2) | ||||
|  | ||||
| **Fixed bugs:** | ||||
|  | ||||
| - Problem during HTTPS redirection [\#952](https://github.com/containous/traefik/issues/952) | ||||
| - nil pointer with kubernetes ingress [\#934](https://github.com/containous/traefik/issues/934) | ||||
| - ConsulCatalog and File not working [\#903](https://github.com/containous/traefik/issues/903) | ||||
| - Traefik can not start [\#902](https://github.com/containous/traefik/issues/902) | ||||
| - Cannot connect to Kubernetes server failed to decode watch event [\#532](https://github.com/containous/traefik/issues/532) | ||||
|  | ||||
| **Closed issues:** | ||||
|  | ||||
| - Updating certificates with configuration file. [\#968](https://github.com/containous/traefik/issues/968) | ||||
| - Let's encrypt retrieving certificate from wrong IP [\#962](https://github.com/containous/traefik/issues/962) | ||||
| - let's encrypt and dashboard? [\#961](https://github.com/containous/traefik/issues/961) | ||||
| - Working HTTPS example for GKE? [\#960](https://github.com/containous/traefik/issues/960) | ||||
| - GKE design pattern [\#958](https://github.com/containous/traefik/issues/958) | ||||
| - Consul Catalog constraints does not seem to work [\#954](https://github.com/containous/traefik/issues/954) | ||||
| - Issue in building traefik from master [\#949](https://github.com/containous/traefik/issues/949) | ||||
| - Proxy http application to https doesn't seem to work correctly for all services [\#937](https://github.com/containous/traefik/issues/937) | ||||
| - Excessive requests to kubernetes apiserver [\#922](https://github.com/containous/traefik/issues/922) | ||||
| - I am getting a connection error while creating traefik with consul backend "dial tcp 127.0.0.1:8500: getsockopt: connection refused" [\#917](https://github.com/containous/traefik/issues/917) | ||||
| - SwarmMode - 1.13 RC2 - DNS RR - Individual IPs not retrieved [\#913](https://github.com/containous/traefik/issues/913) | ||||
| - Panic in kubernetes ingress \(traefik 1.1.0\) [\#910](https://github.com/containous/traefik/issues/910) | ||||
| - Kubernetes updating deployment image requires Ingress to be remade  [\#909](https://github.com/containous/traefik/issues/909) | ||||
| - \[ACME\] Too many currently pending authorizations [\#905](https://github.com/containous/traefik/issues/905) | ||||
| - WEB UI Authentication and Let's Encrypt : error 404 [\#754](https://github.com/containous/traefik/issues/754) | ||||
| - Traefik as ingress controller for SNI based routing in kubernetes [\#745](https://github.com/containous/traefik/issues/745) | ||||
| - Kubernetes Ingress backend: using self-signed certificates [\#486](https://github.com/containous/traefik/issues/486) | ||||
| - Kubernetes Ingress backend: can't find token and ca.crt [\#484](https://github.com/containous/traefik/issues/484) | ||||
|  | ||||
| **Merged pull requests:** | ||||
|  | ||||
| - Fix duplicate acme certificates [\#972](https://github.com/containous/traefik/pull/972) ([emilevauge](https://github.com/emilevauge)) | ||||
| - Fix leadership panic [\#956](https://github.com/containous/traefik/pull/956) ([emilevauge](https://github.com/emilevauge)) | ||||
| - Fix redirect regex [\#947](https://github.com/containous/traefik/pull/947) ([emilevauge](https://github.com/emilevauge)) | ||||
| - Add operation recover [\#944](https://github.com/containous/traefik/pull/944) ([emilevauge](https://github.com/emilevauge)) | ||||
|  | ||||
| ## [v1.1.1](https://github.com/containous/traefik/tree/v1.1.1) (2016-11-29) | ||||
| [Full Changelog](https://github.com/containous/traefik/compare/v1.1.0...v1.1.1) | ||||
|  | ||||
|   | ||||
| @@ -10,6 +10,8 @@ import ( | ||||
| 	"github.com/containous/traefik/log" | ||||
| 	"github.com/xenolf/lego/acme" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -106,6 +108,38 @@ type DomainsCertificates struct { | ||||
| 	lock  sync.RWMutex | ||||
| } | ||||
|  | ||||
| func (dc *DomainsCertificates) Len() int { | ||||
| 	return len(dc.Certs) | ||||
| } | ||||
|  | ||||
| func (dc *DomainsCertificates) Swap(i, j int) { | ||||
| 	dc.Certs[i], dc.Certs[j] = dc.Certs[j], dc.Certs[i] | ||||
| } | ||||
|  | ||||
| func (dc *DomainsCertificates) Less(i, j int) bool { | ||||
| 	if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[j].Domains) { | ||||
| 		return dc.Certs[i].tlsCert.Leaf.NotAfter.After(dc.Certs[j].tlsCert.Leaf.NotAfter) | ||||
| 	} | ||||
| 	if dc.Certs[i].Domains.Main == dc.Certs[j].Domains.Main { | ||||
| 		return strings.Join(dc.Certs[i].Domains.SANs, ",") < strings.Join(dc.Certs[j].Domains.SANs, ",") | ||||
| 	} | ||||
| 	return dc.Certs[i].Domains.Main < dc.Certs[j].Domains.Main | ||||
| } | ||||
|  | ||||
| func (dc *DomainsCertificates) removeDuplicates() { | ||||
| 	sort.Sort(dc) | ||||
| 	for i := 0; i < len(dc.Certs); i++ { | ||||
| 		for i2 := i + 1; i2 < len(dc.Certs); i2++ { | ||||
| 			if reflect.DeepEqual(dc.Certs[i].Domains, dc.Certs[i2].Domains) { | ||||
| 				// delete | ||||
| 				log.Warnf("Remove duplicate cert: %+v, exipration :%s", dc.Certs[i2].Domains, dc.Certs[i2].tlsCert.Leaf.NotAfter.String()) | ||||
| 				dc.Certs = append(dc.Certs[:i2], dc.Certs[i2+1:]...) | ||||
| 				i2-- | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Init inits DomainsCertificates | ||||
| func (dc *DomainsCertificates) Init() error { | ||||
| 	dc.lock.Lock() | ||||
| @@ -116,7 +150,15 @@ func (dc *DomainsCertificates) Init() error { | ||||
| 			return err | ||||
| 		} | ||||
| 		domainsCertificate.tlsCert = &tlsCert | ||||
| 		if domainsCertificate.tlsCert.Leaf == nil { | ||||
| 			leaf, err := x509.ParseCertificate(domainsCertificate.tlsCert.Certificate[0]) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			domainsCertificate.tlsCert.Leaf = leaf | ||||
| 		} | ||||
| 	} | ||||
| 	dc.removeDuplicates() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										186
									
								
								acme/acme.go
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								acme/acme.go
									
									
									
									
									
								
							| @@ -11,6 +11,7 @@ import ( | ||||
| 	"github.com/containous/traefik/log" | ||||
| 	"github.com/containous/traefik/safe" | ||||
| 	"github.com/containous/traefik/types" | ||||
| 	"github.com/eapache/channels" | ||||
| 	"github.com/xenolf/lego/acme" | ||||
| 	"golang.org/x/net/context" | ||||
| 	"io/ioutil" | ||||
| @@ -35,6 +36,7 @@ type ACME struct { | ||||
| 	store               cluster.Store | ||||
| 	challengeProvider   *challengeProvider | ||||
| 	checkOnDemandDomain func(domain string) bool | ||||
| 	jobs                *channels.InfiniteChannel | ||||
| } | ||||
|  | ||||
| //Domains parse []Domain | ||||
| @@ -91,6 +93,7 @@ func (a *ACME) init() error { | ||||
| 		log.Warnf("ACME.StorageFile is deprecated, use ACME.Storage instead") | ||||
| 		a.Storage = a.StorageFile | ||||
| 	} | ||||
| 	a.jobs = channels.NewInfiniteChannel() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -142,9 +145,7 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl | ||||
| 			case <-ctx.Done(): | ||||
| 				return | ||||
| 			case <-ticker.C: | ||||
| 				if err := a.renewCertificates(); err != nil { | ||||
| 					log.Errorf("Error renewing ACME certificate: %s", err.Error()) | ||||
| 				} | ||||
| 				a.renewCertificates() | ||||
| 			} | ||||
| 		} | ||||
| 	}) | ||||
| @@ -205,12 +206,10 @@ func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tl | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			safe.Go(func() { | ||||
| 				a.retrieveCertificates() | ||||
| 				if err := a.renewCertificates(); err != nil { | ||||
| 					log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error()) | ||||
| 				} | ||||
| 			}) | ||||
|  | ||||
| 			a.retrieveCertificates() | ||||
| 			a.renewCertificates() | ||||
| 			a.runJobs() | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| @@ -295,19 +294,14 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	safe.Go(func() { | ||||
| 		a.retrieveCertificates() | ||||
| 		if err := a.renewCertificates(); err != nil { | ||||
| 			log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error()) | ||||
| 		} | ||||
| 	}) | ||||
| 	a.retrieveCertificates() | ||||
| 	a.renewCertificates() | ||||
| 	a.runJobs() | ||||
|  | ||||
| 	ticker := time.NewTicker(24 * time.Hour) | ||||
| 	safe.Go(func() { | ||||
| 		for range ticker.C { | ||||
| 			if err := a.renewCertificates(); err != nil { | ||||
| 				log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error()) | ||||
| 			} | ||||
| 			a.renewCertificates() | ||||
| 		} | ||||
|  | ||||
| 	}) | ||||
| @@ -336,83 +330,87 @@ func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificat | ||||
| } | ||||
|  | ||||
| func (a *ACME) retrieveCertificates() { | ||||
| 	log.Infof("Retrieving ACME certificates...") | ||||
| 	for _, domain := range a.Domains { | ||||
| 		// check if cert isn't already loaded | ||||
| 		account := a.store.Get().(*Account) | ||||
| 		if _, exists := account.DomainsCertificate.exists(domain); !exists { | ||||
| 			domains := []string{} | ||||
| 			domains = append(domains, domain.Main) | ||||
| 			domains = append(domains, domain.SANs...) | ||||
| 			certificateResource, err := a.getDomainsCertificates(domains) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
| 			transaction, object, err := a.store.Begin() | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
| 			account = object.(*Account) | ||||
| 			_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
| 	a.jobs.In() <- func() { | ||||
| 		log.Infof("Retrieving ACME certificates...") | ||||
| 		for _, domain := range a.Domains { | ||||
| 			// check if cert isn't already loaded | ||||
| 			account := a.store.Get().(*Account) | ||||
| 			if _, exists := account.DomainsCertificate.exists(domain); !exists { | ||||
| 				domains := []string{} | ||||
| 				domains = append(domains, domain.Main) | ||||
| 				domains = append(domains, domain.SANs...) | ||||
| 				certificateResource, err := a.getDomainsCertificates(domains) | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error()) | ||||
| 					continue | ||||
| 				} | ||||
| 				transaction, object, err := a.store.Begin() | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error creating ACME store transaction from domain %s: %s", domain, err.Error()) | ||||
| 					continue | ||||
| 				} | ||||
| 				account = object.(*Account) | ||||
| 				_, err = account.DomainsCertificate.addCertificateForDomains(certificateResource, domain) | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error()) | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 			if err = transaction.Commit(account); err != nil { | ||||
| 				log.Errorf("Error Saving ACME account %+v: %s", account, err.Error()) | ||||
| 				continue | ||||
| 				if err = transaction.Commit(account); err != nil { | ||||
| 					log.Errorf("Error Saving ACME account %+v: %s", account, err.Error()) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		log.Infof("Retrieved ACME certificates") | ||||
| 	} | ||||
| 	log.Infof("Retrieved ACME certificates") | ||||
| } | ||||
|  | ||||
| func (a *ACME) renewCertificates() error { | ||||
| 	log.Debugf("Testing certificate renew...") | ||||
| 	account := a.store.Get().(*Account) | ||||
| 	for _, certificateResource := range account.DomainsCertificate.Certs { | ||||
| 		if certificateResource.needRenew() { | ||||
| 			log.Debugf("Renewing certificate %+v", certificateResource.Domains) | ||||
| 			renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{ | ||||
| 				Domain:        certificateResource.Certificate.Domain, | ||||
| 				CertURL:       certificateResource.Certificate.CertURL, | ||||
| 				CertStableURL: certificateResource.Certificate.CertStableURL, | ||||
| 				PrivateKey:    certificateResource.Certificate.PrivateKey, | ||||
| 				Certificate:   certificateResource.Certificate.Certificate, | ||||
| 			}, true) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error renewing certificate: %v", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			log.Debugf("Renewed certificate %+v", certificateResource.Domains) | ||||
| 			renewedACMECert := &Certificate{ | ||||
| 				Domain:        renewedCert.Domain, | ||||
| 				CertURL:       renewedCert.CertURL, | ||||
| 				CertStableURL: renewedCert.CertStableURL, | ||||
| 				PrivateKey:    renewedCert.PrivateKey, | ||||
| 				Certificate:   renewedCert.Certificate, | ||||
| 			} | ||||
| 			transaction, object, err := a.store.Begin() | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			account = object.(*Account) | ||||
| 			err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains) | ||||
| 			if err != nil { | ||||
| 				log.Errorf("Error renewing certificate: %v", err) | ||||
| 				continue | ||||
| 			} | ||||
| func (a *ACME) renewCertificates() { | ||||
| 	a.jobs.In() <- func() { | ||||
| 		log.Debugf("Testing certificate renew...") | ||||
| 		account := a.store.Get().(*Account) | ||||
| 		for _, certificateResource := range account.DomainsCertificate.Certs { | ||||
| 			if certificateResource.needRenew() { | ||||
| 				log.Debugf("Renewing certificate %+v", certificateResource.Domains) | ||||
| 				renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{ | ||||
| 					Domain:        certificateResource.Certificate.Domain, | ||||
| 					CertURL:       certificateResource.Certificate.CertURL, | ||||
| 					CertStableURL: certificateResource.Certificate.CertStableURL, | ||||
| 					PrivateKey:    certificateResource.Certificate.PrivateKey, | ||||
| 					Certificate:   certificateResource.Certificate.Certificate, | ||||
| 				}, true) | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error renewing certificate: %v", err) | ||||
| 					continue | ||||
| 				} | ||||
| 				log.Debugf("Renewed certificate %+v", certificateResource.Domains) | ||||
| 				renewedACMECert := &Certificate{ | ||||
| 					Domain:        renewedCert.Domain, | ||||
| 					CertURL:       renewedCert.CertURL, | ||||
| 					CertStableURL: renewedCert.CertStableURL, | ||||
| 					PrivateKey:    renewedCert.PrivateKey, | ||||
| 					Certificate:   renewedCert.Certificate, | ||||
| 				} | ||||
| 				transaction, object, err := a.store.Begin() | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error renewing certificate: %v", err) | ||||
| 					continue | ||||
| 				} | ||||
| 				account = object.(*Account) | ||||
| 				err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains) | ||||
| 				if err != nil { | ||||
| 					log.Errorf("Error renewing certificate: %v", err) | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 			if err = transaction.Commit(account); err != nil { | ||||
| 				log.Errorf("Error Saving ACME account %+v: %s", account, err.Error()) | ||||
| 				continue | ||||
| 				if err = transaction.Commit(account); err != nil { | ||||
| 					log.Errorf("Error Saving ACME account %+v: %s", account, err.Error()) | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) { | ||||
| @@ -462,8 +460,9 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C | ||||
|  | ||||
| // LoadCertificateForDomains loads certificates from ACME for given domains | ||||
| func (a *ACME) LoadCertificateForDomains(domains []string) { | ||||
| 	domains = fun.Map(types.CanonicalDomain, domains).([]string) | ||||
| 	safe.Go(func() { | ||||
| 	a.jobs.In() <- func() { | ||||
| 		log.Debugf("LoadCertificateForDomains %s...", domains) | ||||
| 		domains = fun.Map(types.CanonicalDomain, domains).([]string) | ||||
| 		operation := func() error { | ||||
| 			if a.client == nil { | ||||
| 				return fmt.Errorf("ACME client still not built") | ||||
| @@ -475,7 +474,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) { | ||||
| 		} | ||||
| 		ebo := backoff.NewExponentialBackOff() | ||||
| 		ebo.MaxElapsedTime = 30 * time.Second | ||||
| 		err := backoff.RetryNotify(operation, ebo, notify) | ||||
| 		err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Error getting ACME client: %v", err) | ||||
| 			return | ||||
| @@ -517,7 +516,7 @@ func (a *ACME) LoadCertificateForDomains(domains []string) { | ||||
| 			log.Errorf("Error Saving ACME account %+v: %v", account, err) | ||||
| 			return | ||||
| 		} | ||||
| 	}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) { | ||||
| @@ -538,3 +537,12 @@ func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) { | ||||
| 		Certificate:   certificate.Certificate, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (a *ACME) runJobs() { | ||||
| 	safe.Go(func() { | ||||
| 		for job := range a.jobs.Out() { | ||||
| 			function := job.(func()) | ||||
| 			function() | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import ( | ||||
| 	"reflect" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func TestDomainsSet(t *testing.T) { | ||||
| @@ -62,6 +63,8 @@ func TestDomainsSetAppend(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestCertificatesRenew(t *testing.T) { | ||||
| 	foo1Cert, foo1Key, _ := generateKeyPair("foo1.com", time.Now()) | ||||
| 	foo2Cert, foo2Key, _ := generateKeyPair("foo2.com", time.Now()) | ||||
| 	domainsCertificates := DomainsCertificates{ | ||||
| 		lock: sync.RWMutex{}, | ||||
| 		Certs: []*DomainsCertificate{ | ||||
| @@ -73,55 +76,8 @@ func TestCertificatesRenew(t *testing.T) { | ||||
| 					Domain:        "foo1.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey: []byte(` | ||||
| -----BEGIN RSA PRIVATE KEY----- | ||||
| MIIEowIBAAKCAQEA6OqHGdwGy20+3Jcz9IgfN4IR322X2Hhwk6n8Hss/Ws7FeTZo | ||||
| PvXW8uHeI1bmQJsy9C6xo3odzO64o7prgMZl5eDw5fk1mmUij3J3nM3gwtc/Cc+8 | ||||
| ADXGldauASdHBFTRvWQge0Pv/Q5U0fyL2VCHoR9mGv4CQ7nRNKPus0vYJMbXoTbO | ||||
| 8z4sIbNz3Ov9o/HGMRb8D0rNPTMdC62tHSbiO1UoxLXr9dcBOGt786AsiRTJ8bq9 | ||||
| GCVQgzd0Wftb8z6ddW2YuWrmExlkHdfC4oG0D5SU1QB4ldPyl7fhVWlfHwC1NX+c | ||||
| RnDSEeYkAcdvvIekdM/yH+z62XhwToM0E9TCzwIDAQABAoIBACq3EC3S50AZeeTU | ||||
| qgeXizoP1Z1HKQjfFa5PB1jSZ30M3LRdIQMi7NfASo/qmPGSROb5RUS42YxC34PP | ||||
| ZXXJbNiaxzM13/m/wHXURVFxhF3XQc1X1p+nPRMvutulS2Xk9E4qdbaFgBbFsRKN | ||||
| oUwqc6U97+jVWq72/gIManNhXnNn1n1SRLBEkn+WStMPn6ZvWRlpRMjhy0c1mpwg | ||||
| u6em92HvMvfKPQ60naUhdKp+q0rsLp2YKWjiytos9ENSYI5gAGLIDhKeqiD8f92E | ||||
| 4FGPmNRipwxCE2SSvZFlM26tRloWVcBPktRN79hUejE8iopiqVS0+4h/phZ2wG0D | ||||
| 18cqVpECgYEA+qmagnhm0LLvwVkUN0B2nRARQEFinZDM4Hgiv823bQvc9I8dVTqJ | ||||
| aIQm5y4Y5UA3xmyDsRoO7GUdd0oVeh9GwTONzMRCOny/mOuOC51wXPhKHhI0O22u | ||||
| sfbOHszl+bxl6ZQMUJa2/I8YIWBLU5P+fTgrfNwBEgZ3YPwUV5tyHNcCgYEA7eAv | ||||
| pjQkbJNRq/fv/67sojN7N9QoH84egN5cZFh5d8PJomnsvy5JDV4WaG1G6mJpqjdD | ||||
| YRVdFw5oZ4L8yCVdCeK9op896Uy51jqvfSe3+uKmNqE0qDHgaLubQNI8yYc5sacW | ||||
| fYJBmDR6rNIeE7Q2240w3CdKfREuXdDnhyTTEskCgYBFeAnFTP8Zqe2+hSSQJ4J4 | ||||
| BwLw7u4Yww+0yja/N5E1XItRD/TOMRnx6GYrvd/ScVjD2kEpLRKju2ZOMC8BmHdw | ||||
| hgwvitjcAsTK6cWFPI3uhjVsXhkxuzUmR0Naz+iQrQEFmi1LjGmMV1AVt+1IbYSj | ||||
| SZTr1sFJMJeXPmWY3hDjIwKBgQC4H9fCJoorIL0PB5NVreishHzT8fw84ibqSTPq | ||||
| 2DDtazcf6C3AresN1c4ydqN1uUdg4fXdp9OujRBzTwirQ4CIrmFrBye89g7CrBo6 | ||||
| Hgxivh06G/3OUw0JBG5f9lvnAiy+Pj9CVxi+36A1NU7ioZP0zY0MW71koW/qXlFY | ||||
| YkCfQQKBgBqwND/c3mPg7iY4RMQ9XjrKfV9o6FMzA51lAinjujHlNgsBmqiR951P | ||||
| NA3kWZQ73D3IxeLEMaGHpvS7andPN3Z2qPhe+FbJKcF6ZZNTrFQkh/Fpz3wmYPo1 | ||||
| GIL4+09kNgMRWapaROqI+/3+qJQ+GVJZIPfYC0poJOO6vYqifWe8 | ||||
| -----END RSA PRIVATE KEY----- | ||||
| `), | ||||
| 					Certificate: []byte(` | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIC+TCCAeGgAwIBAgIJAK78ukR/Qu4rMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV | ||||
| BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMDMyM1oXDTI2MDYxNzIyMDMyM1owEzER | ||||
| MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB | ||||
| AQDo6ocZ3AbLbT7clzP0iB83ghHfbZfYeHCTqfweyz9azsV5Nmg+9dby4d4jVuZA | ||||
| mzL0LrGjeh3M7rijumuAxmXl4PDl+TWaZSKPcneczeDC1z8Jz7wANcaV1q4BJ0cE | ||||
| VNG9ZCB7Q+/9DlTR/IvZUIehH2Ya/gJDudE0o+6zS9gkxtehNs7zPiwhs3Pc6/2j | ||||
| 8cYxFvwPSs09Mx0Lra0dJuI7VSjEtev11wE4a3vzoCyJFMnxur0YJVCDN3RZ+1vz | ||||
| Pp11bZi5auYTGWQd18LigbQPlJTVAHiV0/KXt+FVaV8fALU1f5xGcNIR5iQBx2+8 | ||||
| h6R0z/If7PrZeHBOgzQT1MLPAgMBAAGjUDBOMB0GA1UdDgQWBBRFLH1wF6BT51uq | ||||
| yWNqBnCrPFIglzAfBgNVHSMEGDAWgBRFLH1wF6BT51uqyWNqBnCrPFIglzAMBgNV | ||||
| HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAr7aH3Db6TeAZkg4Zd7SoF2q11 | ||||
| erzv552PgQUyezMZcRBo2q1ekmUYyy2600CBiYg51G+8oUqjJKiKnBuaqbMX7pFa | ||||
| FsL7uToZCGA57cBaVejeB+p24P5bxoJGKCMeZcEBe5N93Tqu5WBxNEX7lQUo6TSs | ||||
| gSN2Olf3/grNKt5V4BduSIQZ+YHlPUWLTaz5B1MXKSUqjmabARP9lhjO14u9USvi | ||||
| dMBDFskJySQ6SUfz3fyoXELoDOVbRZETuSodpw+aFCbEtbcQCLT3A0FG+BEPayZH | ||||
| tt19zKUlr6e+YFpyjQPGZ7ZkY7iMgHEkhKrXx2DiZ1+cif3X1xfXWQr0S5+E | ||||
| -----END CERTIFICATE----- | ||||
| `), | ||||
| 					PrivateKey:    foo1Key, | ||||
| 					Certificate:   foo1Cert, | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| @@ -132,113 +88,19 @@ tt19zKUlr6e+YFpyjQPGZ7ZkY7iMgHEkhKrXx2DiZ1+cif3X1xfXWQr0S5+E | ||||
| 					Domain:        "foo2.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey: []byte(` | ||||
| -----BEGIN RSA PRIVATE KEY----- | ||||
| MIIEogIBAAKCAQEA7rIVuSrZ3FfYXhR3qaWwfVcgiqKS//yXFzNqkJS6mz9nRCNT | ||||
| lPawvrCFIRKdR7UO7xD7A5VTcbrGOAaTvrEaH7mB/4FGL+gN4AiTbVFpKXngAYEW | ||||
| A3//zeBZ7XUSWaQ+CNC+l796JeoDvQD++KwCke4rVD1pGN1hpVEeGhwzyKOYPKLo | ||||
| 4+AGVe1LFWw4U/v8Iil1/gBBehZBILuhASpXy4W132LJPl76/EbGqh0nVz2UlFqU | ||||
| HRxO+2U2ba4YIpI+0/VOQ9Cq/TzHSUdTTLfBHE/Qb+aDBfptMWTRvAngLqUglOcZ | ||||
| Fi6SAljxEkJO6z6btmoVUWsoKBpbIHDC5++dZwIDAQABAoIBAAD8rYhRfAskNdnV | ||||
| vdTuwXcTOCg6md8DHWDULpmgc9EWhwfKGZthFcQEGNjVKd9VCVXFvTP7lxe+TPmI | ||||
| VW4Rb2k4LChxUWf7TqthfbKTBptMTLfU39Ft4xHn3pdTx5qlSjhhHJimCwxDFnbe | ||||
| nS9MDsqpsHYtttSKfc/gMP6spS4sNPZ/r9zseT3eWkBEhn+FQABxJiuPcQ7q7S+Q | ||||
| uOghmr7f3FeYvizQOhBtULsLrK/hsmQIIB4amS1QlpNWKbIoiUPNPjCA5PVQyAER | ||||
| waYjuc7imBbeD98L/z8bRTlEskSKjtPSEXGVHa9OYdBU+02Ci6TjKztUp6Ho7JE9 | ||||
| tcHj+eECgYEA+9Ntv6RqIdpT/4/52JYiR+pOem3U8tweCOmUqm/p/AWyfAJTykqt | ||||
| cJ8RcK1MfM+uoa5Sjm8hIcA2XPVEqH2J50PC4w04Q3xtfsz3xs7KJWXQCoha8D0D | ||||
| ZIFNroEPnld0qOuJzpIIteXTrCLhSu17ZhN+Wk+5gJ7Ewu/QMM5OPjECgYEA8qbw | ||||
| zfwSjE6jkrqO70jzqSxgi2yjo0vMqv+BNBuhxhDTBXnKQI1KsHoiS0FkSLSJ9+DS | ||||
| CT3WEescD2Lumdm2s9HXvaMmnDSKBY58NqCGsNzZifSgmj1H/yS9FX8RXfSjXcxq | ||||
| RDvTbD52/HeaCiOxHZx8JjmJEb+ZKJC4MDvjtxcCgYBM516GvgEjYXdxfliAiijh | ||||
| 6W4Z+Vyk5g/ODPc3rYG5U0wUjuljx7Z7xDghPusy2oGsIn5XvRxTIE35yXU0N1Jb | ||||
| 69eiWzEpeuA9bv7kGdal4RfNf6K15wwYL1y3w/YvFuorg/LLwNEkK5Ge6e//X9Ll | ||||
| c2KM1fgCjXntRitAHGDMoQKBgDnkgodioLpA+N3FDN0iNqAiKlaZcOFA8G/LzfO0 | ||||
| tAAhe3dO+2YzT6KTQSNbUqXWDSTKytHRowVbZrJ1FCA4xVJZunNQPaH/Fv8EY7ZU | ||||
| zk3cIzq61qZ2AHtrNIGwc2BLQb7bSm9FJsgojxLlJidNJLC/6Q7lo0JMyCnZfVhk | ||||
| sYu5AoGAZt/MfyFTKm674UddSNgGEt86PyVYbLMnRoAXOaNB38AE12kaYHPil1tL | ||||
| FnL8OQLpbX5Qo2JGgeZRlpMJ4Jxw2zzvUKr/n+6khaLxHmtX48hMu2QM7ZvnkZCs | ||||
| Kkgz6v+Wcqm94ugtl3HSm+u9xZzVQxN6gu/jZQv3VpQiAZHjPYc= | ||||
| -----END RSA PRIVATE KEY----- | ||||
| `), | ||||
| 					Certificate: []byte(` | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIC+TCCAeGgAwIBAgIJAK25/Z9Jz6IBMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV | ||||
| BAMMCGZvbzIuY29tMB4XDTE2MDYyMDA5MzUyNloXDTI2MDYxODA5MzUyNlowEzER | ||||
| MA8GA1UEAwwIZm9vMi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB | ||||
| AQDushW5KtncV9heFHeppbB9VyCKopL//JcXM2qQlLqbP2dEI1OU9rC+sIUhEp1H | ||||
| tQ7vEPsDlVNxusY4BpO+sRofuYH/gUYv6A3gCJNtUWkpeeABgRYDf//N4FntdRJZ | ||||
| pD4I0L6Xv3ol6gO9AP74rAKR7itUPWkY3WGlUR4aHDPIo5g8oujj4AZV7UsVbDhT | ||||
| +/wiKXX+AEF6FkEgu6EBKlfLhbXfYsk+Xvr8RsaqHSdXPZSUWpQdHE77ZTZtrhgi | ||||
| kj7T9U5D0Kr9PMdJR1NMt8EcT9Bv5oMF+m0xZNG8CeAupSCU5xkWLpICWPESQk7r | ||||
| Ppu2ahVRaygoGlsgcMLn751nAgMBAAGjUDBOMB0GA1UdDgQWBBQ6FZWqB9qI4NN+ | ||||
| 2jFY6xH8uoUTnTAfBgNVHSMEGDAWgBQ6FZWqB9qI4NN+2jFY6xH8uoUTnTAMBgNV | ||||
| HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCRhuf2dQhIEOmSOGgtRELF2wB6 | ||||
| NWXt0lCty9x4u+zCvITXV8Z0C34VQGencO3H2bgyC3ZxNpPuwZfEc2Pxe8W6bDc/ | ||||
| OyLckk9WLo00Tnr2t7rDOeTjEGuhXFZkhIbJbKdAH8cEXrxKR8UXWtZgTv/b8Hv/ | ||||
| g6tbeH6TzBsdMoFtUCsyWxygYwnLU+quuYvE2s9FiCegf2mdYTCh/R5J5n/51gfB | ||||
| uC+NakKMfaCvNg3mOAFSYC/0r0YcKM/5ldKGTKTCVJAMhnmBnyRc/70rKkVRFy2g | ||||
| iIjUFs+9aAgfCiL0WlyyXYAtIev2gw4FHUVlcT/xKks+x8Kgj6e5LTIrRRwW | ||||
| -----END CERTIFICATE----- | ||||
| `), | ||||
| 					PrivateKey:    foo2Key, | ||||
| 					Certificate:   foo2Cert, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	foo1Cert, foo1Key, _ = generateKeyPair("foo1.com", time.Now()) | ||||
| 	newCertificate := &Certificate{ | ||||
| 		Domain:        "foo1.com", | ||||
| 		CertURL:       "url", | ||||
| 		CertStableURL: "url", | ||||
| 		PrivateKey: []byte(` | ||||
| -----BEGIN RSA PRIVATE KEY----- | ||||
| MIIEowIBAAKCAQEA1OdSuXK2zeSLf0UqgrI4pjkpaqhra++pnda4Li4jXo151svi | ||||
| Sn7DSynJOoq1jbfRJAoyDhxsBC4S4RuD54U5elJ4wLPZXmHRsvb+NwiHs9VmDqwu | ||||
| It21btuqeNMebkab5cnDnC6KKufMhXRcRAlluYXyCkQe/+N+LlUQd6Js34TixMpk | ||||
| eQOX4/OVrokSyVRnIq4u+o0Ufe7z5+41WVH63tcy7Hwi7244aLUzZCs+QQa2Dw6f | ||||
| qEwjbonr974fM68UxDjTZEQy9u24yDzajhDBp1OTAAklh7U+li3g9dSyNVBFXqEu | ||||
| nW2fyBvLqeJOSTihqfcrACB/YYhYOX94vMXELQIDAQABAoIBAFYK3t3fxI1VTiMz | ||||
| WsjTKh3TgC+AvVkz1ILbojfXoae22YS7hUrCDD82NgMYx+LsZPOBw1T8m5Lc4/hh | ||||
| 3F8W8nHDHtYSWUjRk6QWOgsXwXAmUEahw0uH+qlA0ZZfDC9ZDexCLHHURTat03Qj | ||||
| 4J4GhjwCLB2GBlk4IWisLCmNVR7HokrpfIw4oM1aB5E21Tl7zh/x7ikRijEkUsKw | ||||
| 7YhaMeLJqBnMnAdV63hhF7FaDRjl8P2s/3octz/6pqDIABrDrUW3KAkNYCZIWdhF | ||||
| Kk0wRMbZ/WrYT9GIGoJe7coQC7ezTrlrEkAFEIPGHCLkgXB/0TyuSy0yY59e4zmi | ||||
| VvHoWUECgYEA/rOL2KJ/p+TZW7+YbsUzs0+F+M+G6UCr0nWfYN9MKmNtmns3eLDG | ||||
| +pIpBMc5mjqeJR/sCCdkD8OqHC202Y8e4sr0pKSBeBofh2BmXtpyu3QQ50Pa63RS | ||||
| SK6mYUrFqPmFFDbNGpFI4sIeI+Vf6hm96FQPnyPtUTGqk39m0RbWM/UCgYEA1f04 | ||||
| Nf3wbqwqIHZjYpPmymfjleyMn3hGUjpi7pmI6inXGMk3nkeG1cbOhnfPxL5BWD12 | ||||
| 3RqHI2B4Z4r0BMyjctDNb1TxhMIpm5+PKm5KeeKfoYA85IS0mEeq6VdMm3mL1x/O | ||||
| 3LYvcUvAEVf6pWX/+ZFLMudqhF3jbTrdNOC6ZFkCgYBKpEeJdyW+CD0CvEVpwPUD | ||||
| yXxTjE3XMZKpHLtWYlop2fWW3iFFh1jouci3k8L3xdHuw0oioZibXhYOJ/7l+yFs | ||||
| CVpknakrj0xKGiAmEBKriLojbClN80rh7fzoakc+29D6OY0mCgm4GndGwcO4EU8s | ||||
| NOZXFupHbyy0CRQSloSzuQKBgQC1Z/MtIlefGuijmHlsakGuuR+gS2ZzEj1bHBAe | ||||
| gZ4mFM46PuqdjblqpR0TtaI3AarXqVOI4SJLBU9NR+jR4MF3Zjeh9/q/NvKa8Usn | ||||
| B1Svu0TkXphAiZenuKnVIqLY8tNvzZFKXlAd1b+/dDwR10SHR3rebnxINmfEg7Bf | ||||
| UVvyEQKBgAEjI5O6LSkLNpbVn1l2IO8u8D2RkFqs/Sbx78uFta3f9Gddzb4wMnt3 | ||||
| jVzymghCLp4Qf1ump/zC5bcQ8L97qmnjJ+H8X9HwmkqetuI362JNnz+12YKVDIWi | ||||
| wI7SJ8BwDqYMrLw6/nE+degn39KedGDH8gz5cZcdlKTZLjbuBOfU | ||||
| -----END RSA PRIVATE KEY----- | ||||
| `), | ||||
| 		Certificate: []byte(` | ||||
| -----BEGIN CERTIFICATE----- | ||||
| MIIC+TCCAeGgAwIBAgIJAPQiOiQcwYaRMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV | ||||
| BAMMCGZvbzEuY29tMB4XDTE2MDYxOTIyMTE1NFoXDTI2MDYxNzIyMTE1NFowEzER | ||||
| MA8GA1UEAwwIZm9vMS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB | ||||
| AQDU51K5crbN5It/RSqCsjimOSlqqGtr76md1rguLiNejXnWy+JKfsNLKck6irWN | ||||
| t9EkCjIOHGwELhLhG4PnhTl6UnjAs9leYdGy9v43CIez1WYOrC4i3bVu26p40x5u | ||||
| RpvlycOcLooq58yFdFxECWW5hfIKRB7/434uVRB3omzfhOLEymR5A5fj85WuiRLJ | ||||
| VGciri76jRR97vPn7jVZUfre1zLsfCLvbjhotTNkKz5BBrYPDp+oTCNuiev3vh8z | ||||
| rxTEONNkRDL27bjIPNqOEMGnU5MACSWHtT6WLeD11LI1UEVeoS6dbZ/IG8up4k5J | ||||
| OKGp9ysAIH9hiFg5f3i8xcQtAgMBAAGjUDBOMB0GA1UdDgQWBBQPfkS5ehpstmSb | ||||
| 8CGJE7GxSCxl2DAfBgNVHSMEGDAWgBQPfkS5ehpstmSb8CGJE7GxSCxl2DAMBgNV | ||||
| HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA99A+itS9ImdGRGgHZ5fSusiEq | ||||
| wkK5XxGyagL1S0f3VM8e78VabSvC0o/xdD7DHVg6Az8FWxkkksH6Yd7IKfZZUzvs | ||||
| kXQhlOwWpxgmguSmAs4uZTymIoMFRVj3nG664BcXkKu4Yd9UXKNOWP59zgvrCJMM | ||||
| oIsmYiq5u0MFpM31BwfmmW3erqIcfBI9OJrmr1XDzlykPZNWtUSSfVuNQ8d4bim9 | ||||
| XH8RfVLeFbqDydSTCHIFvYthH/ESbpRCiGJHoJ8QLfOkhD1k2fI0oJZn5RVtG2W8 | ||||
| bZME3gHPYCk1QFZUptriMCJ5fMjCgxeOTR+FAkstb/lTRuCc4UyILJguIMar | ||||
| -----END CERTIFICATE----- | ||||
| `), | ||||
| 		PrivateKey:    foo1Key, | ||||
| 		Certificate:   foo1Cert, | ||||
| 	} | ||||
|  | ||||
| 	err := domainsCertificates.renewCertificates( | ||||
| @@ -256,3 +118,94 @@ bZME3gHPYCk1QFZUptriMCJ5fMjCgxeOTR+FAkstb/lTRuCc4UyILJguIMar | ||||
| 		t.Errorf("Expected new certificate %+v \nGot %+v", newCertificate, domainsCertificates.Certs[0].Certificate) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRemoveDuplicates(t *testing.T) { | ||||
| 	now := time.Now() | ||||
| 	fooCert, fooKey, _ := generateKeyPair("foo.com", now) | ||||
| 	foo24Cert, foo24Key, _ := generateKeyPair("foo.com", now.Add(24*time.Hour)) | ||||
| 	foo48Cert, foo48Key, _ := generateKeyPair("foo.com", now.Add(48*time.Hour)) | ||||
| 	barCert, barKey, _ := generateKeyPair("bar.com", now) | ||||
| 	domainsCertificates := DomainsCertificates{ | ||||
| 		lock: sync.RWMutex{}, | ||||
| 		Certs: []*DomainsCertificate{ | ||||
| 			{ | ||||
| 				Domains: Domain{ | ||||
| 					Main: "foo.com", | ||||
| 					SANs: []string{}}, | ||||
| 				Certificate: &Certificate{ | ||||
| 					Domain:        "foo.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey:    foo24Key, | ||||
| 					Certificate:   foo24Cert, | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Domains: Domain{ | ||||
| 					Main: "foo.com", | ||||
| 					SANs: []string{}}, | ||||
| 				Certificate: &Certificate{ | ||||
| 					Domain:        "foo.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey:    foo48Key, | ||||
| 					Certificate:   foo48Cert, | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Domains: Domain{ | ||||
| 					Main: "foo.com", | ||||
| 					SANs: []string{}}, | ||||
| 				Certificate: &Certificate{ | ||||
| 					Domain:        "foo.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey:    fooKey, | ||||
| 					Certificate:   fooCert, | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Domains: Domain{ | ||||
| 					Main: "bar.com", | ||||
| 					SANs: []string{}}, | ||||
| 				Certificate: &Certificate{ | ||||
| 					Domain:        "bar.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey:    barKey, | ||||
| 					Certificate:   barCert, | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| 				Domains: Domain{ | ||||
| 					Main: "foo.com", | ||||
| 					SANs: []string{}}, | ||||
| 				Certificate: &Certificate{ | ||||
| 					Domain:        "foo.com", | ||||
| 					CertURL:       "url", | ||||
| 					CertStableURL: "url", | ||||
| 					PrivateKey:    foo48Key, | ||||
| 					Certificate:   foo48Cert, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	domainsCertificates.Init() | ||||
|  | ||||
| 	if len(domainsCertificates.Certs) != 2 { | ||||
| 		t.Errorf("Expected domainsCertificates length %d %+v\nGot %+v", 2, domainsCertificates.Certs, len(domainsCertificates.Certs)) | ||||
| 	} | ||||
|  | ||||
| 	for _, cert := range domainsCertificates.Certs { | ||||
| 		switch cert.Domains.Main { | ||||
| 		case "bar.com": | ||||
| 			continue | ||||
| 		case "foo.com": | ||||
| 			if !cert.tlsCert.Leaf.NotAfter.Equal(now.Add(48 * time.Hour).Truncate(1 * time.Second)) { | ||||
| 				t.Errorf("Bad expiration %s date for domain %+v, now %s", cert.tlsCert.Leaf.NotAfter.String(), cert, now.Add(48*time.Hour).Truncate(1*time.Second).String()) | ||||
| 			} | ||||
| 		default: | ||||
| 			t.Errorf("Unkown domain %+v", cert) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import ( | ||||
| 	"github.com/cenk/backoff" | ||||
| 	"github.com/containous/traefik/cluster" | ||||
| 	"github.com/containous/traefik/log" | ||||
| 	"github.com/containous/traefik/safe" | ||||
| 	"github.com/xenolf/lego/acme" | ||||
| 	"time" | ||||
| ) | ||||
| @@ -49,7 +50,7 @@ func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate | ||||
| 	} | ||||
| 	ebo := backoff.NewExponentialBackOff() | ||||
| 	ebo.MaxElapsedTime = 60 * time.Second | ||||
| 	err := backoff.RetryNotify(operation, ebo, notify) | ||||
| 	err := backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Error getting cert: %v", err) | ||||
| 		return nil, false | ||||
|   | ||||
| @@ -17,34 +17,44 @@ import ( | ||||
| ) | ||||
|  | ||||
| func generateDefaultCertificate() (*tls.Certificate, error) { | ||||
| 	rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	rsaPrivPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)}) | ||||
|  | ||||
| 	randomBytes := make([]byte, 100) | ||||
| 	_, err = rand.Read(randomBytes) | ||||
| 	_, err := rand.Read(randomBytes) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	zBytes := sha256.Sum256(randomBytes) | ||||
| 	z := hex.EncodeToString(zBytes[:sha256.Size]) | ||||
| 	domain := fmt.Sprintf("%s.%s.traefik.default", z[:32], z[32:]) | ||||
| 	tempCertPEM, err := generatePemCert(rsaPrivKey, domain) | ||||
|  | ||||
| 	certPEM, keyPEM, err := generateKeyPair(domain, time.Time{}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM) | ||||
| 	certificate, err := tls.X509KeyPair(certPEM, keyPEM) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &certificate, nil | ||||
| } | ||||
| func generatePemCert(privKey *rsa.PrivateKey, domain string) ([]byte, error) { | ||||
| 	derBytes, err := generateDerCert(privKey, time.Time{}, domain) | ||||
|  | ||||
| func generateKeyPair(domain string, expiration time.Time) ([]byte, []byte, error) { | ||||
| 	rsaPrivKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rsaPrivKey)}) | ||||
|  | ||||
| 	certPEM, err := generatePemCert(rsaPrivKey, domain, expiration) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return certPEM, keyPEM, nil | ||||
| } | ||||
|  | ||||
| func generatePemCert(privKey *rsa.PrivateKey, domain string, expiration time.Time) ([]byte, error) { | ||||
| 	derBytes, err := generateDerCert(privKey, expiration, domain) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -93,7 +103,7 @@ func TLSSNI01ChallengeCert(keyAuth string) (ChallengeCert, string, error) { | ||||
| 	zBytes := sha256.Sum256([]byte(keyAuth)) | ||||
| 	z := hex.EncodeToString(zBytes[:sha256.Size]) | ||||
| 	domain := fmt.Sprintf("%s.%s.acme.invalid", z[:32], z[32:]) | ||||
| 	tempCertPEM, err := generatePemCert(rsaPrivKey, domain) | ||||
| 	tempCertPEM, err := generatePemCert(rsaPrivKey, domain, time.Time{}) | ||||
| 	if err != nil { | ||||
| 		return ChallengeCert{}, "", err | ||||
| 	} | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import ( | ||||
| 	"github.com/containous/staert" | ||||
| 	"github.com/containous/traefik/job" | ||||
| 	"github.com/containous/traefik/log" | ||||
| 	"github.com/containous/traefik/safe" | ||||
| 	"github.com/docker/libkv/store" | ||||
| 	"github.com/satori/go.uuid" | ||||
| 	"golang.org/x/net/context" | ||||
| @@ -108,7 +109,7 @@ func (d *Datastore) watchChanges() error { | ||||
| 		notify := func(err error, time time.Duration) { | ||||
| 			log.Errorf("Error in watch datastore: %+v, retrying in %s", err, time) | ||||
| 		} | ||||
| 		err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Error in watch datastore: %v", err) | ||||
| 		} | ||||
| @@ -175,7 +176,7 @@ func (d *Datastore) Begin() (Transaction, Object, error) { | ||||
| 	} | ||||
| 	ebo := backoff.NewExponentialBackOff() | ||||
| 	ebo.MaxElapsedTime = 60 * time.Second | ||||
| 	err = backoff.RetryNotify(operation, ebo, notify) | ||||
| 	err = backoff.RetryNotify(safe.OperationWithRecover(operation), ebo, notify) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err) | ||||
| 	} | ||||
| @@ -230,21 +231,21 @@ func (s *datastoreTransaction) Commit(object Object) error { | ||||
| 	s.localLock.Lock() | ||||
| 	defer s.localLock.Unlock() | ||||
| 	if s.dirty { | ||||
| 		return fmt.Errorf("transaction already used, please begin a new one") | ||||
| 		return fmt.Errorf("Transaction already used, please begin a new one") | ||||
| 	} | ||||
| 	s.Datastore.meta.object = object | ||||
| 	err := s.Datastore.meta.Marshall() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return fmt.Errorf("Marshall error: %s", err) | ||||
| 	} | ||||
| 	err = s.kv.StoreConfig(s.Datastore.meta) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return fmt.Errorf("StoreConfig error: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = s.remoteLock.Unlock() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return fmt.Errorf("Unlock error: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	s.dirty = true | ||||
|   | ||||
| @@ -15,7 +15,7 @@ type Leadership struct { | ||||
| 	*safe.Pool | ||||
| 	*types.Cluster | ||||
| 	candidate *leadership.Candidate | ||||
| 	leader    safe.Safe | ||||
| 	leader    *safe.Safe | ||||
| 	listeners []LeaderListener | ||||
| } | ||||
|  | ||||
| @@ -26,6 +26,7 @@ func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership { | ||||
| 		Cluster:   cluster, | ||||
| 		candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second), | ||||
| 		listeners: []LeaderListener{}, | ||||
| 		leader:    safe.New(false), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -45,7 +46,7 @@ func (l *Leadership) Participate(pool *safe.Pool) { | ||||
| 		notify := func(err error, time time.Duration) { | ||||
| 			log.Errorf("Leadership election error %+v, retrying in %s", err, time) | ||||
| 		} | ||||
| 		err := backoff.RetryNotify(operation, backOff, notify) | ||||
| 		err := backoff.RetryNotify(safe.OperationWithRecover(operation), backOff, notify) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Cannot elect leadership %+v", err) | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										10
									
								
								docs.Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docs.Dockerfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| FROM alpine:3.7 | ||||
|  | ||||
| ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin | ||||
|  | ||||
| COPY requirements.txt /mkdocs/ | ||||
| WORKDIR /mkdocs | ||||
| VOLUME /mkdocs | ||||
|  | ||||
| RUN apk --no-cache --no-progress add py-pip \ | ||||
|   && pip install --trusted-host pypi.python.org --user -r requirements.txt | ||||
							
								
								
									
										8
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								glide.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| hash: 1bbeb842ee639ccc6e2edf8cc13fc2759cb96e3d839a1aec7b7f6af4fb89c8e1 | ||||
| updated: 2016-11-09T19:24:00.762904389+01:00 | ||||
| hash: fa6c0ac899b3c9296d83f1d4110186b339101b800b241e08cdcd2e3d49270562 | ||||
| updated: 2016-12-09T13:43:16.816754682+01:00 | ||||
| imports: | ||||
| - name: github.com/abbot/go-http-auth | ||||
|   version: cb4372376e1e00e9f6ab9ec142e029302c9e7140 | ||||
| @@ -156,6 +156,10 @@ imports: | ||||
|   - store/zookeeper | ||||
| - name: github.com/donovanhide/eventsource | ||||
|   version: fd1de70867126402be23c306e1ce32828455d85b | ||||
| - name: github.com/eapache/channels | ||||
|   version: 47238d5aae8c0fefd518ef2bee46290909cf8263 | ||||
| - name: github.com/eapache/queue | ||||
|   version: 44cc805cf13205b55f69e14bcb69867d1ae92f98 | ||||
| - name: github.com/elazarl/go-bindata-assetfs | ||||
|   version: 9a6736ed45b44bf3835afeebb3034b57ed329f3e | ||||
| - name: github.com/gambol99/go-marathon | ||||
|   | ||||
| @@ -109,4 +109,5 @@ import: | ||||
|   subpackages: | ||||
|   - daemon | ||||
| - package: github.com/google/go-github | ||||
| - package: github.com/hashicorp/go-version | ||||
| - package: github.com/hashicorp/go-version | ||||
| - package: github.com/eapache/channels | ||||
| @@ -334,7 +334,7 @@ func (provider *ConsulCatalog) Provide(configurationChan chan<- types.ConfigMess | ||||
| 		operation := func() error { | ||||
| 			return provider.watch(configurationChan, stop) | ||||
| 		} | ||||
| 		err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Cannot connect to consul server %+v", err) | ||||
| 		} | ||||
|   | ||||
| @@ -230,7 +230,7 @@ func (provider *Docker) Provide(configurationChan chan<- types.ConfigMessage, po | ||||
| 		notify := func(err error, time time.Duration) { | ||||
| 			log.Errorf("Docker connection error %+v, retrying in %s", err, time) | ||||
| 		} | ||||
| 		err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Cannot connect to docker server %+v", err) | ||||
| 		} | ||||
|   | ||||
| @@ -150,7 +150,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage | ||||
| 		notify := func(err error, time time.Duration) { | ||||
| 			log.Errorf("Kubernetes connection error %+v, retrying in %s", err, time) | ||||
| 		} | ||||
| 		err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 		if err != nil { | ||||
| 			log.Errorf("Cannot connect to Kubernetes server %+v", err) | ||||
| 		} | ||||
| @@ -196,6 +196,10 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur | ||||
| 	PassHostHeader := provider.getPassHostHeader() | ||||
| 	for _, i := range ingresses { | ||||
| 		for _, r := range i.Spec.Rules { | ||||
| 			if r.HTTP == nil { | ||||
| 				log.Warnf("Error in ingress: HTTP is nil") | ||||
| 				continue | ||||
| 			} | ||||
| 			for _, pa := range r.HTTP.Paths { | ||||
| 				if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists { | ||||
| 					templateObjects.Backends[r.Host+pa.Path] = &types.Backend{ | ||||
|   | ||||
| @@ -76,7 +76,7 @@ func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix | ||||
| 	notify := func(err error, time time.Duration) { | ||||
| 		log.Errorf("KV connection error: %+v, retrying in %s", err, time) | ||||
| 	} | ||||
| 	err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Cannot connect to KV server: %v", err) | ||||
| 	} | ||||
| @@ -107,7 +107,7 @@ func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool * | ||||
| 	notify := func(err error, time time.Duration) { | ||||
| 		log.Errorf("KV connection error: %+v, retrying in %s", err, time) | ||||
| 	} | ||||
| 	err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Cannot connect to KV server: %v", err) | ||||
| 	} | ||||
|   | ||||
| @@ -113,7 +113,7 @@ func (provider *Marathon) Provide(configurationChan chan<- types.ConfigMessage, | ||||
| 	notify := func(err error, time time.Duration) { | ||||
| 		log.Errorf("Marathon connection error %+v, retrying in %s", err, time) | ||||
| 	} | ||||
| 	err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Cannot connect to Marathon server %+v", err) | ||||
| 	} | ||||
|   | ||||
| @@ -113,7 +113,7 @@ func (provider *Mesos) Provide(configurationChan chan<- types.ConfigMessage, poo | ||||
| 	notify := func(err error, time time.Duration) { | ||||
| 		log.Errorf("mesos connection error %+v, retrying in %s", err, time) | ||||
| 	} | ||||
| 	err := backoff.RetryNotify(operation, job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("Cannot connect to mesos server %+v", err) | ||||
| 	} | ||||
|   | ||||
| @@ -1 +1,2 @@ | ||||
| mkdocs>=0.9.0 | ||||
| mkdocs==0.16.0 | ||||
| mkdocs-bootswatch==0.4.0 | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package safe | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/cenk/backoff" | ||||
| 	"github.com/containous/traefik/log" | ||||
| 	"golang.org/x/net/context" | ||||
| 	"runtime/debug" | ||||
| @@ -134,3 +136,16 @@ func defaultRecoverGoroutine(err interface{}) { | ||||
| 	log.Errorf("Error in Go routine: %s", err) | ||||
| 	debug.PrintStack() | ||||
| } | ||||
|  | ||||
| // OperationWithRecover wrap a backoff operation in a Recover | ||||
| func OperationWithRecover(operation backoff.Operation) backoff.Operation { | ||||
| 	return func() (err error) { | ||||
| 		defer func() { | ||||
| 			if res := recover(); res != nil { | ||||
| 				defaultRecoverGoroutine(res) | ||||
| 				err = fmt.Errorf("Panic in operation: %s", err) | ||||
| 			} | ||||
| 		}() | ||||
| 		return operation() | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										37
									
								
								safe/routine_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								safe/routine_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| package safe | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/cenk/backoff" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestOperationWithRecover(t *testing.T) { | ||||
| 	operation := func() error { | ||||
| 		return nil | ||||
| 	} | ||||
| 	err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Error in OperationWithRecover: %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestOperationWithRecoverPanic(t *testing.T) { | ||||
| 	operation := func() error { | ||||
| 		panic("BOOM") | ||||
| 	} | ||||
| 	err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("Error in OperationWithRecover: %s", err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestOperationWithRecoverError(t *testing.T) { | ||||
| 	operation := func() error { | ||||
| 		return fmt.Errorf("ERROR") | ||||
| 	} | ||||
| 	err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{}) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("Error in OperationWithRecover: %s", err) | ||||
| 	} | ||||
| } | ||||
| @@ -725,7 +725,7 @@ func (server *Server) loadEntryPointConfig(entryPointName string, entryPoint *En | ||||
| 	regex := entryPoint.Redirect.Regex | ||||
| 	replacement := entryPoint.Redirect.Replacement | ||||
| 	if len(entryPoint.Redirect.EntryPoint) > 0 { | ||||
| 		regex = "^(?:https?:\\/\\/)?([\\da-z\\.-]+)(?::\\d+)?(.*)$" | ||||
| 		regex = "^(?:https?:\\/\\/)?([\\w\\._-]+)(?::\\d+)?(.*)$" | ||||
| 		if server.globalConfiguration.EntryPoints[entryPoint.Redirect.EntryPoint] == nil { | ||||
| 			return nil, errors.New("Unknown entrypoint " + entryPoint.Redirect.EntryPoint) | ||||
| 		} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user