Add ACME store
Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
parent
bea5ad3f13
commit
a42845502e
30 changed files with 781 additions and 374 deletions
176
acme/account.go
Normal file
176
acme/account.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Account is used to store lets encrypt registration info
|
||||||
|
type Account struct {
|
||||||
|
Email string
|
||||||
|
Registration *acme.RegistrationResource
|
||||||
|
PrivateKey []byte
|
||||||
|
DomainsCertificate DomainsCertificates
|
||||||
|
ChallengeCerts map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init inits acccount struct
|
||||||
|
func (a *Account) Init() error {
|
||||||
|
err := a.DomainsCertificate.Init()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccount(email string) (*Account, error) {
|
||||||
|
// Create a user. New accounts need an email and private key to start
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
domainsCerts := DomainsCertificates{Certs: []*DomainsCertificate{}}
|
||||||
|
domainsCerts.Init()
|
||||||
|
return &Account{
|
||||||
|
Email: email,
|
||||||
|
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
DomainsCertificate: domainsCerts,
|
||||||
|
ChallengeCerts: map[string][]byte{}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmail returns email
|
||||||
|
func (a Account) GetEmail() string {
|
||||||
|
return a.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRegistration returns lets encrypt registration resource
|
||||||
|
func (a Account) GetRegistration() *acme.RegistrationResource {
|
||||||
|
return a.Registration
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrivateKey returns private key
|
||||||
|
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
||||||
|
return privateKey
|
||||||
|
}
|
||||||
|
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate is used to store certificate info
|
||||||
|
type Certificate struct {
|
||||||
|
Domain string
|
||||||
|
CertURL string
|
||||||
|
CertStableURL string
|
||||||
|
PrivateKey []byte
|
||||||
|
Certificate []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainsCertificates stores a certificate for multiple domains
|
||||||
|
type DomainsCertificates struct {
|
||||||
|
Certs []*DomainsCertificate
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) Init() error {
|
||||||
|
dc.lock.Lock()
|
||||||
|
defer dc.lock.Unlock()
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
domainsCertificate.tlsCert = &tlsCert
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
||||||
|
dc.lock.Lock()
|
||||||
|
defer dc.lock.Unlock()
|
||||||
|
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
||||||
|
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
domainsCertificate.Certificate = acmeCert
|
||||||
|
domainsCertificate.tlsCert = &tlsCert
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
||||||
|
dc.lock.Lock()
|
||||||
|
defer dc.lock.Unlock()
|
||||||
|
|
||||||
|
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
||||||
|
dc.Certs = append(dc.Certs, &cert)
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
||||||
|
dc.lock.RLock()
|
||||||
|
defer dc.lock.RUnlock()
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
domains := []string{}
|
||||||
|
domains = append(domains, domainsCertificate.Domains.Main)
|
||||||
|
domains = append(domains, domainsCertificate.Domains.SANs...)
|
||||||
|
for _, domain := range domains {
|
||||||
|
if domain == domainToFind {
|
||||||
|
return domainsCertificate, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
||||||
|
dc.lock.RLock()
|
||||||
|
defer dc.lock.RUnlock()
|
||||||
|
for _, domainsCertificate := range dc.Certs {
|
||||||
|
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
||||||
|
return domainsCertificate, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// DomainsCertificate contains a certificate for multiple domains
|
||||||
|
type DomainsCertificate struct {
|
||||||
|
Domains Domain
|
||||||
|
Certificate *Certificate
|
||||||
|
tlsCert *tls.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificate) needRenew() bool {
|
||||||
|
for _, c := range dc.tlsCert.Certificate {
|
||||||
|
crt, err := x509.ParseCertificate(c)
|
||||||
|
if err != nil {
|
||||||
|
// If there's an error, we assume the cert is broken, and needs update
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// <= 7 days left, renew certificate
|
||||||
|
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 7 * time.Hour))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
508
acme/acme.go
508
acme/acme.go
|
@ -1,179 +1,36 @@
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/containous/staert"
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"golang.org/x/net/context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/containous/traefik/safe"
|
|
||||||
"github.com/xenolf/lego/acme"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account is used to store lets encrypt registration info
|
|
||||||
type Account struct {
|
|
||||||
Email string
|
|
||||||
Registration *acme.RegistrationResource
|
|
||||||
PrivateKey []byte
|
|
||||||
DomainsCertificate DomainsCertificates
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEmail returns email
|
|
||||||
func (a Account) GetEmail() string {
|
|
||||||
return a.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRegistration returns lets encrypt registration resource
|
|
||||||
func (a Account) GetRegistration() *acme.RegistrationResource {
|
|
||||||
return a.Registration
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrivateKey returns private key
|
|
||||||
func (a Account) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
if privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey); err == nil {
|
|
||||||
return privateKey
|
|
||||||
}
|
|
||||||
log.Errorf("Cannot unmarshall private key %+v", a.PrivateKey)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certificate is used to store certificate info
|
|
||||||
type Certificate struct {
|
|
||||||
Domain string
|
|
||||||
CertURL string
|
|
||||||
CertStableURL string
|
|
||||||
PrivateKey []byte
|
|
||||||
Certificate []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainsCertificates stores a certificate for multiple domains
|
|
||||||
type DomainsCertificates struct {
|
|
||||||
Certs []*DomainsCertificate
|
|
||||||
lock *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) init() error {
|
|
||||||
if dc.lock == nil {
|
|
||||||
dc.lock = &sync.RWMutex{}
|
|
||||||
}
|
|
||||||
dc.lock.Lock()
|
|
||||||
defer dc.lock.Unlock()
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
domainsCertificate.tlsCert = &tlsCert
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) renewCertificates(acmeCert *Certificate, domain Domain) error {
|
|
||||||
dc.lock.Lock()
|
|
||||||
defer dc.lock.Unlock()
|
|
||||||
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
if reflect.DeepEqual(domain, domainsCertificate.Domains) {
|
|
||||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
domainsCertificate.Certificate = acmeCert
|
|
||||||
domainsCertificate.tlsCert = &tlsCert
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.New("Certificate to renew not found for domain " + domain.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) addCertificateForDomains(acmeCert *Certificate, domain Domain) (*DomainsCertificate, error) {
|
|
||||||
dc.lock.Lock()
|
|
||||||
defer dc.lock.Unlock()
|
|
||||||
|
|
||||||
tlsCert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert := DomainsCertificate{Domains: domain, Certificate: acmeCert, tlsCert: &tlsCert}
|
|
||||||
dc.Certs = append(dc.Certs, &cert)
|
|
||||||
return &cert, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) getCertificateForDomain(domainToFind string) (*DomainsCertificate, bool) {
|
|
||||||
dc.lock.RLock()
|
|
||||||
defer dc.lock.RUnlock()
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
domains := []string{}
|
|
||||||
domains = append(domains, domainsCertificate.Domains.Main)
|
|
||||||
domains = append(domains, domainsCertificate.Domains.SANs...)
|
|
||||||
for _, domain := range domains {
|
|
||||||
if domain == domainToFind {
|
|
||||||
return domainsCertificate, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificates) exists(domainToFind Domain) (*DomainsCertificate, bool) {
|
|
||||||
dc.lock.RLock()
|
|
||||||
defer dc.lock.RUnlock()
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
|
||||||
if reflect.DeepEqual(domainToFind, domainsCertificate.Domains) {
|
|
||||||
return domainsCertificate, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainsCertificate contains a certificate for multiple domains
|
|
||||||
type DomainsCertificate struct {
|
|
||||||
Domains Domain
|
|
||||||
Certificate *Certificate
|
|
||||||
tlsCert *tls.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *DomainsCertificate) needRenew() bool {
|
|
||||||
for _, c := range dc.tlsCert.Certificate {
|
|
||||||
crt, err := x509.ParseCertificate(c)
|
|
||||||
if err != nil {
|
|
||||||
// If there's an error, we assume the cert is broken, and needs update
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// <= 30 days left, renew certificate
|
|
||||||
if crt.NotAfter.Before(time.Now().Add(time.Duration(24 * 30 * time.Hour))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACME allows to connect to lets encrypt and retrieve certs
|
// ACME allows to connect to lets encrypt and retrieve certs
|
||||||
type ACME struct {
|
type ACME struct {
|
||||||
Email string `description:"Email address used for registration"`
|
Email string `description:"Email address used for registration"`
|
||||||
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
Domains []Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
|
||||||
StorageFile string `description:"File used for certificates storage."`
|
Storage string `description:"File or key used for certificates storage."`
|
||||||
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
OnDemand bool `description:"Enable on demand certificate. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."`
|
||||||
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
|
||||||
CAServer string `description:"CA server to use."`
|
CAServer string `description:"CA server to use."`
|
||||||
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
|
||||||
storageLock sync.RWMutex
|
client *acme.Client
|
||||||
client *acme.Client
|
defaultCertificate *tls.Certificate
|
||||||
account *Account
|
store cluster.Store
|
||||||
defaultCertificate *tls.Certificate
|
challengeProvider *challengeProvider
|
||||||
|
checkOnDemandDomain func(domain string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//Domains parse []Domain
|
//Domains parse []Domain
|
||||||
|
@ -218,11 +75,7 @@ type Domain struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) init() error {
|
func (a *ACME) init() error {
|
||||||
if len(a.Store) == 0 {
|
|
||||||
a.Store = a.StorageFile
|
|
||||||
}
|
|
||||||
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
acme.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||||
log.Debugf("Generating default certificate...")
|
|
||||||
// no certificates in TLS config, so we add a default one
|
// no certificates in TLS config, so we add a default one
|
||||||
cert, err := generateDefaultCertificate()
|
cert, err := generateDefaultCertificate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -232,78 +85,174 @@ func (a *ACME) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClusterConfig creates a tls.config from using ACME configuration
|
// CreateClusterConfig creates a tls.config using ACME configuration in cluster mode
|
||||||
func (a *ACME) CreateClusterConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
func (a *ACME) CreateClusterConfig(leadership *cluster.Leadership, tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||||
err := a.init()
|
err := a.init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(a.Store) == 0 {
|
if len(a.Storage) == 0 {
|
||||||
return errors.New("Empty Store, please provide a filename/key for certs storage")
|
return errors.New("Empty Store, please provide a key for certs storage")
|
||||||
}
|
}
|
||||||
|
a.checkOnDemandDomain = checkOnDemandDomain
|
||||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||||
|
tlsConfig.GetCertificate = a.getCertificate
|
||||||
|
|
||||||
|
datastore, err := cluster.NewDataStore(
|
||||||
|
staert.KvSource{
|
||||||
|
Store: leadership.Store,
|
||||||
|
Prefix: leadership.Store.Prefix + "/acme/account",
|
||||||
|
},
|
||||||
|
leadership.Pool.Ctx(), &Account{},
|
||||||
|
func(object cluster.Object) error {
|
||||||
|
account := object.(*Account)
|
||||||
|
account.Init()
|
||||||
|
if !leadership.IsLeader() {
|
||||||
|
a.client, err = a.buildACMEClient(account)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error building ACME client %+v: %s", object, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.store = datastore
|
||||||
|
a.challengeProvider = newMemoryChallengeProvider(a.store)
|
||||||
|
|
||||||
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
|
leadership.Pool.AddGoCtx(func(ctx context.Context) {
|
||||||
|
log.Infof("Starting ACME renew job...")
|
||||||
|
defer log.Infof("Stopped ACME renew job...")
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := a.renewCertificates(); err != nil {
|
||||||
|
log.Errorf("Error renewing ACME certificate: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
leadership.AddListener(func(elected bool) error {
|
||||||
|
if elected {
|
||||||
|
object, err := a.store.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account := object.(*Account)
|
||||||
|
account.Init()
|
||||||
|
var needRegister bool
|
||||||
|
if account == nil || len(account.Email) == 0 {
|
||||||
|
account, err = NewAccount(a.Email)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
needRegister = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("buildACMEClient...")
|
||||||
|
a.client, err = a.buildACMEClient(account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if needRegister {
|
||||||
|
// New users will need to register; be sure to save it
|
||||||
|
log.Debugf("Register...")
|
||||||
|
reg, err := a.client.Register()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.Registration = reg
|
||||||
|
}
|
||||||
|
// The client has a URL to the current Let's Encrypt Subscriber
|
||||||
|
// Agreement. The user will need to agree to it.
|
||||||
|
log.Debugf("AgreeToTOS...")
|
||||||
|
err = a.client.AgreeToTOS()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = transaction.Commit(account)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLocalConfig creates a tls.config from using ACME configuration
|
// CreateLocalConfig creates a tls.config using local ACME configuration
|
||||||
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, CheckOnDemandDomain func(domain string) bool) error {
|
func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, checkOnDemandDomain func(domain string) bool) error {
|
||||||
err := a.init()
|
err := a.init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(a.Store) == 0 {
|
if len(a.Storage) == 0 {
|
||||||
return errors.New("Empty Store, please provide a filename/key for certs storage")
|
return errors.New("Empty Store, please provide a filename for certs storage")
|
||||||
}
|
}
|
||||||
|
a.checkOnDemandDomain = checkOnDemandDomain
|
||||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
tlsConfig.Certificates = append(tlsConfig.Certificates, *a.defaultCertificate)
|
||||||
|
tlsConfig.GetCertificate = a.getCertificate
|
||||||
|
|
||||||
|
localStore := NewLocalStore(a.Storage)
|
||||||
|
a.store = localStore
|
||||||
|
a.challengeProvider = newMemoryChallengeProvider(a.store)
|
||||||
|
|
||||||
var needRegister bool
|
var needRegister bool
|
||||||
var err error
|
var account *Account
|
||||||
|
|
||||||
// if certificates in storage, load them
|
if fileInfo, fileErr := os.Stat(a.Storage); fileErr == nil && fileInfo.Size() != 0 {
|
||||||
if fileInfo, fileErr := os.Stat(a.Store); fileErr == nil && fileInfo.Size() != 0 {
|
log.Infof("Loading ACME Account...")
|
||||||
log.Infof("Loading ACME certificates...")
|
|
||||||
// load account
|
// load account
|
||||||
a.account, err = a.loadAccount(a)
|
object, err := localStore.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
account = object.(*Account)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Generating ACME Account...")
|
log.Infof("Generating ACME Account...")
|
||||||
// Create a user. New accounts need an email and private key to start
|
account, err = NewAccount(a.Email)
|
||||||
privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.account = &Account{
|
|
||||||
Email: a.Email,
|
|
||||||
PrivateKey: x509.MarshalPKCS1PrivateKey(privateKey),
|
|
||||||
}
|
|
||||||
a.account.DomainsCertificate = DomainsCertificates{Certs: []*DomainsCertificate{}, lock: &sync.RWMutex{}}
|
|
||||||
needRegister = true
|
needRegister = true
|
||||||
}
|
}
|
||||||
|
|
||||||
a.client, err = a.buildACMEClient()
|
log.Infof("buildACMEClient...")
|
||||||
if err != nil {
|
a.client, err = a.buildACMEClient(account)
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
|
||||||
wrapperChallengeProvider := newWrapperChallengeProvider()
|
|
||||||
err = client.SetChallengeProvider(acme.TLSSNI01, wrapperChallengeProvider)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if needRegister {
|
if needRegister {
|
||||||
// New users will need to register; be sure to save it
|
// New users will need to register; be sure to save it
|
||||||
|
log.Infof("Register...")
|
||||||
reg, err := a.client.Register()
|
reg, err := a.client.Register()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.account.Registration = reg
|
account.Registration = reg
|
||||||
}
|
}
|
||||||
|
|
||||||
// The client has a URL to the current Let's Encrypt Subscriber
|
// The client has a URL to the current Let's Encrypt Subscriber
|
||||||
// Agreement. The user will need to agree to it.
|
// Agreement. The user will need to agree to it.
|
||||||
|
log.Infof("AgreeToTOS...")
|
||||||
err = a.client.AgreeToTOS()
|
err = a.client.AgreeToTOS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Let's Encrypt Subscriber Agreement renew ?
|
// Let's Encrypt Subscriber Agreement renew ?
|
||||||
|
@ -311,45 +260,33 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, CheckOnDemandDomain func
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.account.Registration = reg
|
account.Registration = reg
|
||||||
err = a.client.AgreeToTOS()
|
err = a.client.AgreeToTOS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", a.account, err.Error())
|
log.Errorf("Error sending ACME agreement to TOS: %+v: %s", account, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// save account
|
// save account
|
||||||
err = a.saveAccount()
|
transaction, _, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = transaction.Commit(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
a.retrieveCertificates(a.client)
|
a.retrieveCertificates()
|
||||||
if err := a.renewCertificates(a.client); err != nil {
|
if err := a.renewCertificates(); err != nil {
|
||||||
log.Errorf("Error renewing ACME certificate %+v: %s", a.account, err.Error())
|
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
||||||
if challengeCert, ok := wrapperChallengeProvider.getCertificate(clientHello.ServerName); ok {
|
|
||||||
return challengeCert, nil
|
|
||||||
}
|
|
||||||
if domainCert, ok := a.account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
|
||||||
return domainCert.tlsCert, nil
|
|
||||||
}
|
|
||||||
if a.OnDemand {
|
|
||||||
if CheckOnDemandDomain != nil && !CheckOnDemandDomain(clientHello.ServerName) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return a.loadCertificateOnDemand(clientHello)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(24 * time.Hour)
|
ticker := time.NewTicker(24 * time.Hour)
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := a.renewCertificates(client, account); err != nil {
|
if err := a.renewCertificates(); err != nil {
|
||||||
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
log.Errorf("Error renewing ACME certificate %+v: %s", account, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,26 +295,54 @@ func (a *ACME) CreateLocalConfig(tlsConfig *tls.Config, CheckOnDemandDomain func
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) retrieveCertificates(client *acme.Client) {
|
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
account := a.store.Get().(*Account)
|
||||||
|
if challengeCert, ok := a.challengeProvider.getCertificate(clientHello.ServerName); ok {
|
||||||
|
log.Debugf("ACME got challenge %s", clientHello.ServerName)
|
||||||
|
return challengeCert, nil
|
||||||
|
}
|
||||||
|
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||||
|
log.Debugf("ACME got domaincert %s", clientHello.ServerName)
|
||||||
|
return domainCert.tlsCert, nil
|
||||||
|
}
|
||||||
|
if a.OnDemand {
|
||||||
|
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(clientHello.ServerName) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return a.loadCertificateOnDemand(clientHello)
|
||||||
|
}
|
||||||
|
log.Debugf("ACME got nothing %s", clientHello.ServerName)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACME) retrieveCertificates() {
|
||||||
log.Infof("Retrieving ACME certificates...")
|
log.Infof("Retrieving ACME certificates...")
|
||||||
for _, domain := range a.Domains {
|
for _, domain := range a.Domains {
|
||||||
// check if cert isn't already loaded
|
// check if cert isn't already loaded
|
||||||
if _, exists := a.account.DomainsCertificate.exists(domain); !exists {
|
account := a.store.Get().(*Account)
|
||||||
|
if _, exists := account.DomainsCertificate.exists(domain); !exists {
|
||||||
domains := []string{}
|
domains := []string{}
|
||||||
domains = append(domains, domain.Main)
|
domains = append(domains, domain.Main)
|
||||||
domains = append(domains, domain.SANs...)
|
domains = append(domains, domain.SANs...)
|
||||||
certificateResource, err := a.getDomainsCertificates(client, domains)
|
certificateResource, err := a.getDomainsCertificates(domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
log.Errorf("Error getting ACME certificate for domain %s: %s", domains, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, err = a.account.DomainsCertificate.addCertificateForDomains(certificateResource, domain)
|
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 {
|
if err != nil {
|
||||||
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
log.Errorf("Error adding ACME certificate for domain %s: %s", domains, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = a.saveAccount(); err != nil {
|
|
||||||
log.Errorf("Error Saving ACME account %+v: %s", a.account, err.Error())
|
if err = transaction.Commit(account); err != nil {
|
||||||
|
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -385,12 +350,18 @@ func (a *ACME) retrieveCertificates(client *acme.Client) {
|
||||||
log.Infof("Retrieved ACME certificates")
|
log.Infof("Retrieved ACME certificates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) renewCertificates(client *acme.Client) error {
|
func (a *ACME) renewCertificates() error {
|
||||||
log.Debugf("Testing certificate renew...")
|
log.Debugf("Testing certificate renew...")
|
||||||
for _, certificateResource := range a.account.DomainsCertificate.Certs {
|
account := a.store.Get().(*Account)
|
||||||
|
for _, certificateResource := range account.DomainsCertificate.Certs {
|
||||||
if certificateResource.needRenew() {
|
if certificateResource.needRenew() {
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account = object.(*Account)
|
||||||
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
log.Debugf("Renewing certificate %+v", certificateResource.Domains)
|
||||||
renewedCert, err := client.RenewCertificate(acme.CertificateResource{
|
renewedCert, err := a.client.RenewCertificate(acme.CertificateResource{
|
||||||
Domain: certificateResource.Certificate.Domain,
|
Domain: certificateResource.Certificate.Domain,
|
||||||
CertURL: certificateResource.Certificate.CertURL,
|
CertURL: certificateResource.Certificate.CertURL,
|
||||||
CertStableURL: certificateResource.Certificate.CertStableURL,
|
CertStableURL: certificateResource.Certificate.CertStableURL,
|
||||||
|
@ -409,13 +380,14 @@ func (a *ACME) renewCertificates(client *acme.Client) error {
|
||||||
PrivateKey: renewedCert.PrivateKey,
|
PrivateKey: renewedCert.PrivateKey,
|
||||||
Certificate: renewedCert.Certificate,
|
Certificate: renewedCert.Certificate,
|
||||||
}
|
}
|
||||||
err = a.account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
err = account.DomainsCertificate.renewCertificates(renewedACMECert, certificateResource.Domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error renewing certificate: %v", err)
|
log.Errorf("Error renewing certificate: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err = a.saveAccount(); err != nil {
|
|
||||||
log.Errorf("Error saving ACME account: %v", err)
|
if err = transaction.Commit(account); err != nil {
|
||||||
|
log.Errorf("Error Saving ACME account %+v: %s", account, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,33 +395,45 @@ func (a *ACME) renewCertificates(client *acme.Client) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) buildACMEClient() (*acme.Client, error) {
|
func (a *ACME) buildACMEClient(account *Account) (*acme.Client, error) {
|
||||||
|
log.Debugf("Building ACME client...")
|
||||||
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
caServer := "https://acme-v01.api.letsencrypt.org/directory"
|
||||||
if len(a.CAServer) > 0 {
|
if len(a.CAServer) > 0 {
|
||||||
caServer = a.CAServer
|
caServer = a.CAServer
|
||||||
}
|
}
|
||||||
client, err := acme.NewClient(caServer, a.account, acme.RSA4096)
|
client, err := acme.NewClient(caServer, account, acme.RSA4096)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.DNS01})
|
||||||
|
err = client.SetChallengeProvider(acme.TLSSNI01, a.challengeProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
if certificateResource, ok := a.account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
account := a.store.Get().(*Account)
|
||||||
|
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(clientHello.ServerName); ok {
|
||||||
return certificateResource.tlsCert, nil
|
return certificateResource.tlsCert, nil
|
||||||
}
|
}
|
||||||
certificate, err := a.getDomainsCertificates(a.client, []string{clientHello.ServerName})
|
certificate, err := a.getDomainsCertificates([]string{clientHello.ServerName})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
log.Debugf("Got certificate on demand for domain %s", clientHello.ServerName)
|
||||||
cert, err := a.account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName})
|
|
||||||
|
transaction, object, err := a.store.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err = a.saveAccount(); err != nil {
|
account = object.(*Account)
|
||||||
|
cert, err := account.DomainsCertificate.addCertificateForDomains(certificate, Domain{Main: clientHello.ServerName})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = transaction.Commit(account); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cert.tlsCert, nil
|
return cert.tlsCert, nil
|
||||||
|
@ -458,6 +442,7 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C
|
||||||
// LoadCertificateForDomains loads certificates from ACME for given domains
|
// LoadCertificateForDomains loads certificates from ACME for given domains
|
||||||
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
|
account := a.store.Get().(*Account)
|
||||||
var domain Domain
|
var domain Domain
|
||||||
if len(domains) == 0 {
|
if len(domains) == 0 {
|
||||||
// no domain
|
// no domain
|
||||||
|
@ -468,64 +453,39 @@ func (a *ACME) LoadCertificateForDomains(domains []string) {
|
||||||
} else {
|
} else {
|
||||||
domain = Domain{Main: domains[0]}
|
domain = Domain{Main: domains[0]}
|
||||||
}
|
}
|
||||||
if _, exists := a.account.DomainsCertificate.exists(domain); exists {
|
if _, exists := account.DomainsCertificate.exists(domain); exists {
|
||||||
// domain already exists
|
// domain already exists
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
certificate, err := a.getDomainsCertificates(a.client, domains)
|
certificate, err := a.getDomainsCertificates(domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
log.Errorf("Error getting ACME certificates %+v : %v", domains, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debugf("Got certificate for domains %+v", domains)
|
log.Debugf("Got certificate for domains %+v", domains)
|
||||||
_, err = a.account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
transaction, object, err := a.store.Begin()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error creating transaction %+v : %v", domains, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
account = object.(*Account)
|
||||||
|
_, err = account.DomainsCertificate.addCertificateForDomains(certificate, domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
log.Errorf("Error adding ACME certificates %+v : %v", domains, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = a.saveAccount(); err != nil {
|
if err = transaction.Commit(account); err != nil {
|
||||||
log.Errorf("Error Saving ACME account %+v: %v", a.account, err)
|
log.Errorf("Error Saving ACME account %+v: %v", account, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) loadAccount(acmeConfig *ACME) (*Account, error) {
|
func (a *ACME) getDomainsCertificates(domains []string) (*Certificate, error) {
|
||||||
a.storageLock.RLock()
|
|
||||||
defer a.storageLock.RUnlock()
|
|
||||||
account := Account{
|
|
||||||
DomainsCertificate: DomainsCertificates{},
|
|
||||||
}
|
|
||||||
file, err := ioutil.ReadFile(acmeConfig.Store)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(file, &account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = account.DomainsCertificate.init()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Infof("Loaded ACME config from store %s", acmeConfig.Store)
|
|
||||||
return &account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ACME) saveAccount() error {
|
|
||||||
a.storageLock.Lock()
|
|
||||||
defer a.storageLock.Unlock()
|
|
||||||
// write account to file
|
|
||||||
data, err := json.MarshalIndent(a.account, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(a.Store, data, 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ACME) getDomainsCertificates(client *acme.Client, domains []string) (*Certificate, error) {
|
|
||||||
log.Debugf("Loading ACME certificates %s...", domains)
|
log.Debugf("Loading ACME certificates %s...", domains)
|
||||||
bundle := true
|
bundle := true
|
||||||
certificate, failures := client.ObtainCertificate(domains, bundle, nil)
|
certificate, failures := a.client.ObtainCertificate(domains, bundle, nil)
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
log.Error(failures)
|
log.Error(failures)
|
||||||
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
return nil, fmt.Errorf("Cannot obtain certificates %s+v", failures)
|
||||||
|
|
|
@ -4,33 +4,59 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/gob"
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"github.com/xenolf/lego/acme"
|
"github.com/xenolf/lego/acme"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ acme.ChallengeProvider = (*inMemoryChallengeProvider)(nil)
|
func init() {
|
||||||
|
gob.Register(rsa.PrivateKey{})
|
||||||
type inMemoryChallengeProvider struct {
|
gob.Register(rsa.PublicKey{})
|
||||||
challengeCerts map[string]*tls.Certificate
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWrapperChallengeProvider() *inMemoryChallengeProvider {
|
var _ acme.ChallengeProviderTimeout = (*challengeProvider)(nil)
|
||||||
return &inMemoryChallengeProvider{
|
|
||||||
challengeCerts: map[string]*tls.Certificate{},
|
type challengeProvider struct {
|
||||||
|
store cluster.Store
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMemoryChallengeProvider(store cluster.Store) *challengeProvider {
|
||||||
|
return &challengeProvider{
|
||||||
|
store: store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *inMemoryChallengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
func (c *challengeProvider) getCertificate(domain string) (cert *tls.Certificate, exists bool) {
|
||||||
|
log.Debugf("Challenge GetCertificate %s", domain)
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
if cert, ok := c.challengeCerts[domain]; ok {
|
account := c.store.Get().(*Account)
|
||||||
|
if account.ChallengeCerts == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
if certBinary, ok := account.ChallengeCerts[domain]; ok {
|
||||||
|
cert := &tls.Certificate{}
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.Write(certBinary)
|
||||||
|
dec := gob.NewDecoder(&buffer)
|
||||||
|
err := dec.Decode(cert)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error unmarshaling challenge cert %s", err.Error())
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
return cert, true
|
return cert, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *inMemoryChallengeProvider) Present(domain, token, keyAuth string) error {
|
func (c *challengeProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
log.Debugf("Challenge Present %s", domain)
|
||||||
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
cert, _, err := acme.TLSSNI01ChallengeCert(keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -42,16 +68,40 @@ func (c *inMemoryChallengeProvider) Present(domain, token, keyAuth string) error
|
||||||
|
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
for i := range cert.Leaf.DNSNames {
|
transaction, object, err := c.store.Begin()
|
||||||
c.challengeCerts[cert.Leaf.DNSNames[i]] = &cert
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
account := object.(*Account)
|
||||||
return nil
|
if account.ChallengeCerts == nil {
|
||||||
|
account.ChallengeCerts = map[string][]byte{}
|
||||||
|
}
|
||||||
|
for i := range cert.Leaf.DNSNames {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
enc := gob.NewEncoder(&buffer)
|
||||||
|
err := enc.Encode(cert)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account.ChallengeCerts[cert.Leaf.DNSNames[i]] = buffer.Bytes()
|
||||||
|
log.Debugf("Challenge Present cert: %s", cert.Leaf.DNSNames[i])
|
||||||
|
}
|
||||||
|
return transaction.Commit(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *inMemoryChallengeProvider) CleanUp(domain, token, keyAuth string) error {
|
func (c *challengeProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
log.Debugf("Challenge CleanUp %s", domain)
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
delete(c.challengeCerts, domain)
|
transaction, object, err := c.store.Begin()
|
||||||
return nil
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
account := object.(*Account)
|
||||||
|
delete(account.ChallengeCerts, domain)
|
||||||
|
return transaction.Commit(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *challengeProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
return 60 * time.Second, 5 * time.Second
|
||||||
}
|
}
|
||||||
|
|
97
acme/localStore.go
Normal file
97
acme/localStore.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ cluster.Store = (*LocalStore)(nil)
|
||||||
|
|
||||||
|
// LocalStore is a store using a file as storage
|
||||||
|
type LocalStore struct {
|
||||||
|
file string
|
||||||
|
storageLock sync.RWMutex
|
||||||
|
account *Account
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocalStore create a LocalStore
|
||||||
|
func NewLocalStore(file string) *LocalStore {
|
||||||
|
return &LocalStore{
|
||||||
|
file: file,
|
||||||
|
storageLock: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get atomically a struct from the file storage
|
||||||
|
func (s *LocalStore) Get() cluster.Object {
|
||||||
|
s.storageLock.RLock()
|
||||||
|
defer s.storageLock.RUnlock()
|
||||||
|
return s.account
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads file into store
|
||||||
|
func (s *LocalStore) Load() (cluster.Object, error) {
|
||||||
|
s.storageLock.Lock()
|
||||||
|
defer s.storageLock.Unlock()
|
||||||
|
account := &Account{}
|
||||||
|
file, err := ioutil.ReadFile(s.file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(file, &account); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
account.Init()
|
||||||
|
s.account = account
|
||||||
|
log.Infof("Loaded ACME config from store %s", s.file)
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (s *LocalStore) saveAccount(account *Account) error {
|
||||||
|
// s.storageLock.Lock()
|
||||||
|
// defer s.storageLock.Unlock()
|
||||||
|
// // write account to file
|
||||||
|
// data, err := json.MarshalIndent(account, "", " ")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// return ioutil.WriteFile(s.file, data, 0644)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Begin creates a transaction with the KV store.
|
||||||
|
func (s *LocalStore) Begin() (cluster.Transaction, cluster.Object, error) {
|
||||||
|
s.storageLock.Lock()
|
||||||
|
return &localTransaction{LocalStore: s}, s.account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ cluster.Transaction = (*localTransaction)(nil)
|
||||||
|
|
||||||
|
type localTransaction struct {
|
||||||
|
*LocalStore
|
||||||
|
dirty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit allows to set an object in the file storage
|
||||||
|
func (t *localTransaction) Commit(object cluster.Object) error {
|
||||||
|
t.LocalStore.account = object.(*Account)
|
||||||
|
defer t.storageLock.Unlock()
|
||||||
|
if t.dirty {
|
||||||
|
return fmt.Errorf("Transaction already used. Please begin a new one.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// write account to file
|
||||||
|
data, err := json.MarshalIndent(object, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(t.file, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.dirty = true
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OxyLogger implements oxy Logger interface with logrus.
|
// OxyLogger implements oxy Logger interface with logrus.
|
||||||
|
|
|
@ -1,44 +1,62 @@
|
||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/cenkalti/backoff"
|
|
||||||
"github.com/containous/staert"
|
"github.com/containous/staert"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
|
"github.com/emilevauge/backoff"
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Object is the struct to store
|
|
||||||
type Object interface{}
|
|
||||||
|
|
||||||
// Metadata stores Object plus metadata
|
// Metadata stores Object plus metadata
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Lock string
|
object Object
|
||||||
|
Object []byte
|
||||||
|
Lock string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) marshall() error {
|
||||||
|
var err error
|
||||||
|
m.Object, err = json.Marshal(m.object)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metadata) unmarshall() error {
|
||||||
|
if len(m.Object) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(m.Object, m.object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listener is called when Object has been changed in KV store
|
||||||
|
type Listener func(Object) error
|
||||||
|
|
||||||
|
var _ Store = (*Datastore)(nil)
|
||||||
|
|
||||||
// Datastore holds a struct synced in a KV store
|
// Datastore holds a struct synced in a KV store
|
||||||
type Datastore struct {
|
type Datastore struct {
|
||||||
kv *staert.KvSource
|
kv staert.KvSource
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
localLock *sync.RWMutex
|
localLock *sync.RWMutex
|
||||||
object Object
|
|
||||||
meta *Metadata
|
meta *Metadata
|
||||||
lockKey string
|
lockKey string
|
||||||
|
listener Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDataStore creates a Datastore
|
// NewDataStore creates a Datastore
|
||||||
func NewDataStore(kvSource *staert.KvSource, ctx context.Context, object Object) (*Datastore, error) {
|
func NewDataStore(kvSource staert.KvSource, ctx context.Context, object Object, listener Listener) (*Datastore, error) {
|
||||||
datastore := Datastore{
|
datastore := Datastore{
|
||||||
kv: kvSource,
|
kv: kvSource,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
meta: &Metadata{},
|
meta: &Metadata{object: object},
|
||||||
object: object,
|
|
||||||
lockKey: kvSource.Prefix + "/lock",
|
lockKey: kvSource.Prefix + "/lock",
|
||||||
localLock: &sync.RWMutex{},
|
localLock: &sync.RWMutex{},
|
||||||
|
listener: listener,
|
||||||
}
|
}
|
||||||
err := datastore.watchChanges()
|
err := datastore.watchChanges()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -67,17 +85,24 @@ func (d *Datastore) watchChanges() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.localLock.Lock()
|
d.localLock.Lock()
|
||||||
err := d.kv.LoadConfig(d.object)
|
err := d.kv.LoadConfig(d.meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.localLock.Unlock()
|
d.localLock.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = d.kv.LoadConfig(d.meta)
|
err = d.meta.unmarshall()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.localLock.Unlock()
|
d.localLock.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.localLock.Unlock()
|
d.localLock.Unlock()
|
||||||
|
// log.Debugf("Datastore object change received: %+v", d.object)
|
||||||
|
if d.listener != nil {
|
||||||
|
err := d.listener(d.meta.object)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error calling datastore listener: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,11 +118,12 @@ func (d *Datastore) watchChanges() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin creates a transaction with the KV store.
|
// Begin creates a transaction with the KV store.
|
||||||
func (d *Datastore) Begin() (*Transaction, error) {
|
func (d *Datastore) Begin() (Transaction, Object, error) {
|
||||||
id := uuid.NewV4().String()
|
id := uuid.NewV4().String()
|
||||||
|
log.Debugf("Transaction %s begins", id)
|
||||||
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
remoteLock, err := d.kv.NewLock(d.lockKey, &store.LockOptions{TTL: 20 * time.Second, Value: []byte(id)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
ctx, cancel := context.WithCancel(d.ctx)
|
ctx, cancel := context.WithCancel(d.ctx)
|
||||||
|
@ -109,11 +135,11 @@ func (d *Datastore) Begin() (*Transaction, error) {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
if errLock != nil {
|
if errLock != nil {
|
||||||
return nil, errLock
|
return nil, nil, errLock
|
||||||
}
|
}
|
||||||
case <-d.ctx.Done():
|
case <-d.ctx.Done():
|
||||||
stopCh <- struct{}{}
|
stopCh <- struct{}{}
|
||||||
return nil, d.ctx.Err()
|
return nil, nil, d.ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// we got the lock! Now make sure we are synced with KV store
|
// we got the lock! Now make sure we are synced with KV store
|
||||||
|
@ -131,14 +157,15 @@ func (d *Datastore) Begin() (*Transaction, error) {
|
||||||
ebo.MaxElapsedTime = 60 * time.Second
|
ebo.MaxElapsedTime = 60 * time.Second
|
||||||
err = backoff.RetryNotify(operation, ebo, notify)
|
err = backoff.RetryNotify(operation, ebo, notify)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
return nil, nil, fmt.Errorf("Datastore cannot sync: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we synced with KV store, we can now return Setter
|
// we synced with KV store, we can now return Setter
|
||||||
return &Transaction{
|
return &datastoreTransaction{
|
||||||
Datastore: d,
|
Datastore: d,
|
||||||
remoteLock: remoteLock,
|
remoteLock: remoteLock,
|
||||||
}, nil
|
id: id,
|
||||||
|
}, d.meta.object, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Datastore) get() *Metadata {
|
func (d *Datastore) get() *Metadata {
|
||||||
|
@ -147,28 +174,50 @@ func (d *Datastore) get() *Metadata {
|
||||||
return d.meta
|
return d.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load load atomically a struct from the KV store
|
||||||
|
func (d *Datastore) Load() (Object, error) {
|
||||||
|
d.localLock.Lock()
|
||||||
|
defer d.localLock.Unlock()
|
||||||
|
err := d.kv.LoadConfig(d.meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = d.meta.unmarshall()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.meta.object, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get atomically a struct from the KV store
|
// Get atomically a struct from the KV store
|
||||||
func (d *Datastore) Get() Object {
|
func (d *Datastore) Get() Object {
|
||||||
d.localLock.RLock()
|
d.localLock.RLock()
|
||||||
defer d.localLock.RUnlock()
|
defer d.localLock.RUnlock()
|
||||||
return d.object
|
return d.meta.object
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction allows to set a struct in the KV store
|
var _ Transaction = (*datastoreTransaction)(nil)
|
||||||
type Transaction struct {
|
|
||||||
|
type datastoreTransaction struct {
|
||||||
*Datastore
|
*Datastore
|
||||||
remoteLock store.Locker
|
remoteLock store.Locker
|
||||||
dirty bool
|
dirty bool
|
||||||
|
id string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit allows to set an object in the KV store
|
// Commit allows to set an object in the KV store
|
||||||
func (s *Transaction) Commit(object Object) error {
|
func (s *datastoreTransaction) Commit(object Object) error {
|
||||||
s.localLock.Lock()
|
s.localLock.Lock()
|
||||||
defer s.localLock.Unlock()
|
defer s.localLock.Unlock()
|
||||||
if s.dirty {
|
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.")
|
||||||
}
|
}
|
||||||
err := s.kv.StoreConfig(object)
|
s.Datastore.meta.object = object
|
||||||
|
err := s.Datastore.meta.marshall()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.kv.StoreConfig(s.Datastore.meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -178,7 +227,8 @@ func (s *Transaction) Commit(object Object) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Datastore.object = object
|
|
||||||
s.dirty = true
|
s.dirty = true
|
||||||
|
// log.Debugf("Datastore object saved: %+v", s.object)
|
||||||
|
log.Debugf("Transaction commited %s", s.id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/cenkalti/backoff"
|
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/docker/leadership"
|
"github.com/docker/leadership"
|
||||||
|
"github.com/emilevauge/backoff"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,8 @@ type Leadership struct {
|
||||||
*safe.Pool
|
*safe.Pool
|
||||||
*types.Cluster
|
*types.Cluster
|
||||||
candidate *leadership.Candidate
|
candidate *leadership.Candidate
|
||||||
|
leader safe.Safe
|
||||||
|
listeners []LeaderListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLeadership creates a leadership
|
// NewLeadership creates a leadership
|
||||||
|
@ -23,9 +25,13 @@ func NewLeadership(ctx context.Context, cluster *types.Cluster) *Leadership {
|
||||||
Pool: safe.NewPool(ctx),
|
Pool: safe.NewPool(ctx),
|
||||||
Cluster: cluster,
|
Cluster: cluster,
|
||||||
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
candidate: leadership.NewCandidate(cluster.Store, cluster.Store.Prefix+"/leader", cluster.Node, 20*time.Second),
|
||||||
|
listeners: []LeaderListener{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LeaderListener is called when leadership has changed
|
||||||
|
type LeaderListener func(elected bool) error
|
||||||
|
|
||||||
// Participate tries to be a leader
|
// Participate tries to be a leader
|
||||||
func (l *Leadership) Participate(pool *safe.Pool) {
|
func (l *Leadership) Participate(pool *safe.Pool) {
|
||||||
pool.GoCtx(func(ctx context.Context) {
|
pool.GoCtx(func(ctx context.Context) {
|
||||||
|
@ -46,10 +52,15 @@ func (l *Leadership) Participate(pool *safe.Pool) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddListener adds a leadership listerner
|
||||||
|
func (l *Leadership) AddListener(listener LeaderListener) {
|
||||||
|
l.listeners = append(l.listeners, listener)
|
||||||
|
}
|
||||||
|
|
||||||
// Resign resigns from being a leader
|
// Resign resigns from being a leader
|
||||||
func (l *Leadership) Resign() {
|
func (l *Leadership) Resign() {
|
||||||
l.candidate.Resign()
|
l.candidate.Resign()
|
||||||
log.Infof("Node %s resined", l.Cluster.Node)
|
log.Infof("Node %s resigned", l.Cluster.Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) error {
|
func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) error {
|
||||||
|
@ -70,9 +81,22 @@ func (l *Leadership) run(candidate *leadership.Candidate, ctx context.Context) e
|
||||||
func (l *Leadership) onElection(elected bool) {
|
func (l *Leadership) onElection(elected bool) {
|
||||||
if elected {
|
if elected {
|
||||||
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
log.Infof("Node %s elected leader ♚", l.Cluster.Node)
|
||||||
|
l.leader.Set(true)
|
||||||
l.Start()
|
l.Start()
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Node %s elected slave ♝", l.Cluster.Node)
|
log.Infof("Node %s elected slave ♝", l.Cluster.Node)
|
||||||
|
l.leader.Set(false)
|
||||||
l.Stop()
|
l.Stop()
|
||||||
}
|
}
|
||||||
|
for _, listener := range l.listeners {
|
||||||
|
err := listener(elected)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error calling Leadership listener: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLeader returns true if current node is leader
|
||||||
|
func (l *Leadership) IsLeader() bool {
|
||||||
|
return l.leader.Get().(bool)
|
||||||
}
|
}
|
||||||
|
|
16
cluster/store.go
Normal file
16
cluster/store.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package cluster
|
||||||
|
|
||||||
|
// Object is the struct to store
|
||||||
|
type Object interface{}
|
||||||
|
|
||||||
|
// Store is a generic interface to represents a storage
|
||||||
|
type Store interface {
|
||||||
|
Load() (Object, error)
|
||||||
|
Get() Object
|
||||||
|
Begin() (Transaction, Object, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction allows to set a struct in the KV store
|
||||||
|
type Transaction interface {
|
||||||
|
Commit(object Object) error
|
||||||
|
}
|
|
@ -23,14 +23,14 @@ type TraefikConfiguration struct {
|
||||||
// GlobalConfiguration holds global configuration (with providers, etc.).
|
// GlobalConfiguration holds global configuration (with providers, etc.).
|
||||||
// It's populated from the traefik configuration file passed as an argument to the binary.
|
// It's populated from the traefik configuration file passed as an argument to the binary.
|
||||||
type GlobalConfiguration struct {
|
type GlobalConfiguration struct {
|
||||||
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
|
GraceTimeOut int64 `short:"g" description:"Duration to give active requests a chance to finish during hot-reload"`
|
||||||
Debug bool `short:"d" description:"Enable debug mode"`
|
Debug bool `short:"d" description:"Enable debug mode"`
|
||||||
AccessLogsFile string `description:"Access logs file"`
|
AccessLogsFile string `description:"Access logs file"`
|
||||||
TraefikLogsFile string `description:"Traefik logs file"`
|
TraefikLogsFile string `description:"Traefik logs file"`
|
||||||
LogLevel string `short:"l" description:"Log level"`
|
LogLevel string `short:"l" description:"Log level"`
|
||||||
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
EntryPoints EntryPoints `description:"Entrypoints definition using format: --entryPoints='Name:http Address::8000 Redirect.EntryPoint:https' --entryPoints='Name:https Address::4442 TLS:tests/traefik.crt,tests/traefik.key'"`
|
||||||
Cluster *types.Cluster
|
Cluster *types.Cluster `description:"Enable clustering"`
|
||||||
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags."`
|
Constraints types.Constraints `description:"Filter services by constraint, matching with service tags"`
|
||||||
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
ACME *acme.ACME `description:"Enable ACME (Let's Encrypt): automatic SSL"`
|
||||||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||||
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
ProvidersThrottleDuration time.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||||
|
|
10
glide.lock
generated
10
glide.lock
generated
|
@ -1,3 +1,4 @@
|
||||||
|
<<<<<<< 2fbcca003e6454c848801c859d8563da94ea8aaf
|
||||||
<<<<<<< a13549cc28273ba5c15a739fa4aaeb3e0f7216a4
|
<<<<<<< a13549cc28273ba5c15a739fa4aaeb3e0f7216a4
|
||||||
hash: c0ac205a859d78847e21d3cd63f427ffba985755c6ae84373e4a20364ba39b05
|
hash: c0ac205a859d78847e21d3cd63f427ffba985755c6ae84373e4a20364ba39b05
|
||||||
<<<<<<< 38b62d4ae311e2d5247065cbc2c09421a2bb81ab
|
<<<<<<< 38b62d4ae311e2d5247065cbc2c09421a2bb81ab
|
||||||
|
@ -8,7 +9,14 @@ updated: 2016-09-28T16:50:04.352639437+01:00
|
||||||
hash: 809b3fa812ca88940fdc15530804a4bcd881708e4819fed5aa45c42c871ba5cf
|
hash: 809b3fa812ca88940fdc15530804a4bcd881708e4819fed5aa45c42c871ba5cf
|
||||||
updated: 2016-09-20T14:50:04.029710103+02:00
|
updated: 2016-09-20T14:50:04.029710103+02:00
|
||||||
>>>>>>> Add KV datastore
|
>>>>>>> Add KV datastore
|
||||||
|
<<<<<<< bea5ad3f132bae27b6c1a83adf00154058b484b5
|
||||||
>>>>>>> Add KV datastore
|
>>>>>>> Add KV datastore
|
||||||
|
=======
|
||||||
|
=======
|
||||||
|
hash: 49c7bd0e32b2764248183bda52f168fe22d69e2db5e17c1dbeebbe71be9929b1
|
||||||
|
updated: 2016-08-11T14:33:42.826534934+02:00
|
||||||
|
>>>>>>> Add ACME store
|
||||||
|
>>>>>>> Add ACME store
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/abbot/go-http-auth
|
- name: github.com/abbot/go-http-auth
|
||||||
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
|
version: cb4372376e1e00e9f6ab9ec142e029302c9e7140
|
||||||
|
@ -33,7 +41,7 @@ imports:
|
||||||
- name: github.com/containous/mux
|
- name: github.com/containous/mux
|
||||||
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
version: a819b77bba13f0c0cbe36e437bc2e948411b3996
|
||||||
- name: github.com/containous/staert
|
- name: github.com/containous/staert
|
||||||
version: 044bdfee6c8f5e8fb71f70d5ba1cf4cb11a94e97
|
version: 56058c7d4152831a641764d10ec91132adf061ea
|
||||||
- name: github.com/coreos/etcd
|
- name: github.com/coreos/etcd
|
||||||
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
|
version: 1c9e0a0e33051fed6c05c141e6fcbfe5c7f2a899
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
@ -21,7 +21,7 @@ import:
|
||||||
- stream
|
- stream
|
||||||
- utils
|
- utils
|
||||||
- package: github.com/containous/staert
|
- package: github.com/containous/staert
|
||||||
version: 044bdfee6c8f5e8fb71f70d5ba1cf4cb11a94e97
|
version: 56058c7d4152831a641764d10ec91132adf061ea
|
||||||
- package: github.com/docker/engine-api
|
- package: github.com/docker/engine-api
|
||||||
version: 62043eb79d581a32ea849645277023c550732e52
|
version: 62043eb79d581a32ea849645277023c550732e52
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
@ -2,7 +2,7 @@ package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/abbot/go-http-auth"
|
"github.com/abbot/go-http-auth"
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/streamrail/concurrent-map"
|
"github.com/streamrail/concurrent-map"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package middlewares
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/vulcand/vulcand/plugin/rewrite"
|
"github.com/vulcand/vulcand/plugin/rewrite"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,7 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
@ -270,7 +271,7 @@ func (provider *ConsulCatalog) getNodes(index map[string][]string) ([]catalogUpd
|
||||||
name := strings.ToLower(service)
|
name := strings.ToLower(service)
|
||||||
if !strings.Contains(name, " ") && !visited[name] {
|
if !strings.Contains(name, " ") && !visited[name] {
|
||||||
visited[name] = true
|
visited[name] = true
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(logrus.Fields{
|
||||||
"service": name,
|
"service": name,
|
||||||
}).Debug("Fetching service")
|
}).Debug("Fetching service")
|
||||||
healthy, err := provider.healthyNodes(name)
|
healthy, err := provider.healthyNodes(name)
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"gopkg.in/fsnotify.v1"
|
"gopkg.in/fsnotify.v1"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/parnurzeal/gorequest"
|
"github.com/parnurzeal/gorequest"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
|
@ -2,6 +2,10 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
|
"github.com/containous/traefik/provider/k8s"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -10,7 +14,6 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/provider/k8s"
|
"github.com/containous/traefik/provider/k8s"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/BurntSushi/ty/fun"
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/cenk/backoff"
|
"github.com/cenk/backoff"
|
||||||
"github.com/containous/traefik/job"
|
"github.com/containous/traefik/job"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/mesosphere/mesos-dns/records/state"
|
"github.com/mesosphere/mesos-dns/records/state"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package safe
|
package safe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"log"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -26,18 +26,26 @@ type Pool struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPool creates a Pool
|
// NewPool creates a Pool
|
||||||
func NewPool(baseCtx context.Context) *Pool {
|
func NewPool(parentCtx context.Context) *Pool {
|
||||||
|
baseCtx, _ := context.WithCancel(parentCtx)
|
||||||
ctx, cancel := context.WithCancel(baseCtx)
|
ctx, cancel := context.WithCancel(baseCtx)
|
||||||
return &Pool{
|
return &Pool{
|
||||||
|
baseCtx: baseCtx,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
baseCtx: baseCtx,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctx returns main context
|
// Ctx returns main context
|
||||||
func (p *Pool) Ctx() context.Context {
|
func (p *Pool) Ctx() context.Context {
|
||||||
return p.ctx
|
return p.baseCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddGoCtx adds a recoverable goroutine with a context without starting it
|
||||||
|
func (p *Pool) AddGoCtx(goroutine routineCtx) {
|
||||||
|
p.lock.Lock()
|
||||||
|
p.routinesCtx = append(p.routinesCtx, goroutine)
|
||||||
|
p.lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
//GoCtx starts a recoverable goroutine with a context
|
//GoCtx starts a recoverable goroutine with a context
|
||||||
|
@ -71,6 +79,7 @@ func (p *Pool) Go(goroutine func(stop chan bool)) {
|
||||||
// Stop stops all started routines, waiting for their termination
|
// Stop stops all started routines, waiting for their termination
|
||||||
func (p *Pool) Stop() {
|
func (p *Pool) Stop() {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
p.cancel()
|
p.cancel()
|
||||||
for _, routine := range p.routines {
|
for _, routine := range p.routines {
|
||||||
routine.stop <- true
|
routine.stop <- true
|
||||||
|
@ -79,12 +88,12 @@ func (p *Pool) Stop() {
|
||||||
for _, routine := range p.routines {
|
for _, routine := range p.routines {
|
||||||
close(routine.stop)
|
close(routine.stop)
|
||||||
}
|
}
|
||||||
p.lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts all stoped routines
|
// Start starts all stopped routines
|
||||||
func (p *Pool) Start() {
|
func (p *Pool) Start() {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
p.ctx, p.cancel = context.WithCancel(p.baseCtx)
|
p.ctx, p.cancel = context.WithCancel(p.baseCtx)
|
||||||
for _, routine := range p.routines {
|
for _, routine := range p.routines {
|
||||||
p.waitGroup.Add(1)
|
p.waitGroup.Add(1)
|
||||||
|
@ -102,7 +111,6 @@ func (p *Pool) Start() {
|
||||||
p.waitGroup.Done()
|
p.waitGroup.Done()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
p.lock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go starts a recoverable goroutine
|
// Go starts a recoverable goroutine
|
||||||
|
@ -123,6 +131,6 @@ func GoWithRecover(goroutine func(), customRecover func(err interface{})) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultRecoverGoroutine(err interface{}) {
|
func defaultRecoverGoroutine(err interface{}) {
|
||||||
log.Println(err)
|
log.Errorf("Error in Go routine: %s", err)
|
||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
}
|
}
|
||||||
|
|
39
server.go
39
server.go
|
@ -21,10 +21,10 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/containous/mux"
|
"github.com/containous/mux"
|
||||||
"github.com/containous/traefik/cluster"
|
"github.com/containous/traefik/cluster"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
@ -93,8 +93,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||||
|
|
||||||
// Start starts the server and blocks until server is shutted down.
|
// Start starts the server and blocks until server is shutted down.
|
||||||
func (server *Server) Start() {
|
func (server *Server) Start() {
|
||||||
server.startLeadership()
|
|
||||||
server.startHTTPServers()
|
server.startHTTPServers()
|
||||||
|
server.startLeadership()
|
||||||
server.routinesPool.Go(func(stop chan bool) {
|
server.routinesPool.Go(func(stop chan bool) {
|
||||||
server.listenProviders(stop)
|
server.listenProviders(stop)
|
||||||
})
|
})
|
||||||
|
@ -129,7 +129,7 @@ func (server *Server) Close() {
|
||||||
if ctx.Err() == context.Canceled {
|
if ctx.Err() == context.Canceled {
|
||||||
return
|
return
|
||||||
} else if ctx.Err() == context.DeadlineExceeded {
|
} else if ctx.Err() == context.DeadlineExceeded {
|
||||||
log.Debugf("I love you all :'( ✝")
|
log.Warnf("Timeout while stopping traefik, killing instance ✝")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}(ctx)
|
}(ctx)
|
||||||
|
@ -147,17 +147,17 @@ func (server *Server) Close() {
|
||||||
func (server *Server) startLeadership() {
|
func (server *Server) startLeadership() {
|
||||||
if server.leadership != nil {
|
if server.leadership != nil {
|
||||||
server.leadership.Participate(server.routinesPool)
|
server.leadership.Participate(server.routinesPool)
|
||||||
server.leadership.GoCtx(func(ctx context.Context) {
|
// server.leadership.AddGoCtx(func(ctx context.Context) {
|
||||||
log.Debugf("Started test routine")
|
// log.Debugf("Started test routine")
|
||||||
<-ctx.Done()
|
// <-ctx.Done()
|
||||||
log.Debugf("Stopped test routine")
|
// log.Debugf("Stopped test routine")
|
||||||
})
|
// })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) stopLeadership() {
|
func (server *Server) stopLeadership() {
|
||||||
if server.leadership != nil {
|
if server.leadership != nil {
|
||||||
server.leadership.Resign()
|
server.leadership.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +283,13 @@ func (server *Server) listenConfigurations(stop chan bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) postLoadConfig() {
|
func (server *Server) postLoadConfig() {
|
||||||
if server.globalConfiguration.ACME != nil && server.globalConfiguration.ACME.OnHostRule {
|
if server.globalConfiguration.ACME == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if server.leadership != nil && !server.leadership.IsLeader() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if server.globalConfiguration.ACME.OnHostRule {
|
||||||
currentConfigurations := server.currentConfigurations.Get().(configs)
|
currentConfigurations := server.currentConfigurations.Get().(configs)
|
||||||
for _, configuration := range currentConfigurations {
|
for _, configuration := range currentConfigurations {
|
||||||
for _, frontend := range configuration.Frontends {
|
for _, frontend := range configuration.Frontends {
|
||||||
|
@ -401,9 +407,16 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *TLS, rou
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
err := server.globalConfiguration.ACME.CreateLocalConfig(config, checkOnDemandDomain)
|
if server.leadership == nil {
|
||||||
if err != nil {
|
err := server.globalConfiguration.ACME.CreateLocalConfig(config, checkOnDemandDomain)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := server.globalConfiguration.ACME.CreateClusterConfig(server.leadership, config, checkOnDemandDomain)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,10 +12,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containous/flaeg"
|
"github.com/containous/flaeg"
|
||||||
"github.com/containous/staert"
|
"github.com/containous/staert"
|
||||||
"github.com/containous/traefik/acme"
|
"github.com/containous/traefik/acme"
|
||||||
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/provider"
|
"github.com/containous/traefik/provider"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
@ -208,7 +209,7 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
level, err := log.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
level, err := logrus.ParseLevel(strings.ToLower(globalConfiguration.LogLevel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting level", err)
|
log.Error("Error getting level", err)
|
||||||
}
|
}
|
||||||
|
@ -224,10 +225,10 @@ func run(traefikConfiguration *TraefikConfiguration) {
|
||||||
log.Error("Error opening file", err)
|
log.Error("Error opening file", err)
|
||||||
} else {
|
} else {
|
||||||
log.SetOutput(fi)
|
log.SetOutput(fi)
|
||||||
log.SetFormatter(&log.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
log.SetFormatter(&logrus.TextFormatter{DisableColors: true, FullTimestamp: true, DisableSorting: true})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true, DisableSorting: true})
|
||||||
}
|
}
|
||||||
jsonConf, _ := json.Marshal(globalConfiguration)
|
jsonConf, _ := json.Marshal(globalConfiguration)
|
||||||
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
log.Infof("Traefik version %s built on %s", version.Version, version.BuildDate)
|
||||||
|
|
|
@ -201,7 +201,7 @@ type Store struct {
|
||||||
|
|
||||||
// Cluster holds cluster config
|
// Cluster holds cluster config
|
||||||
type Cluster struct {
|
type Cluster struct {
|
||||||
Node string
|
Node string `description:"Node name"`
|
||||||
Store *Store
|
Store *Store
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
web.go
2
web.go
|
@ -8,7 +8,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/codegangsta/negroni"
|
"github.com/codegangsta/negroni"
|
||||||
"github.com/containous/mux"
|
"github.com/containous/mux"
|
||||||
"github.com/containous/traefik/autogen"
|
"github.com/containous/traefik/autogen"
|
||||||
|
|
Loading…
Reference in a new issue