package acme import ( "encoding/json" "io/ioutil" "os" "regexp" "github.com/containous/traefik/log" "github.com/containous/traefik/provider/acme" ) // LocalStore is a store using a file as storage type LocalStore struct { file string } // NewLocalStore create a LocalStore func NewLocalStore(file string) *LocalStore { return &LocalStore{ file: file, } } // Get loads file into store and returns the Account func (s *LocalStore) Get() (*Account, error) { account := &Account{} hasData, err := acme.CheckFile(s.file) if err != nil { return nil, err } if hasData { f, err := os.Open(s.file) if err != nil { return nil, err } defer f.Close() file, err := ioutil.ReadAll(f) if err != nil { return nil, err } if err := json.Unmarshal(file, &account); err != nil { return nil, err } } return account, nil } // RemoveAccountV1Values removes ACME account V1 values func RemoveAccountV1Values(account *Account) error { // Check if ACME Account is in ACME V1 format if account != nil && account.Registration != nil { isOldRegistration, err := regexp.MatchString(acme.RegistrationURLPathV1Regexp, account.Registration.URI) if err != nil { return err } if isOldRegistration { account.Email = "" account.Registration = nil account.PrivateKey = nil } } return nil } // ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility) func ConvertToNewFormat(fileName string) { localStore := acme.NewLocalStore(fileName) storeAccount, err := localStore.GetAccount() if err != nil { log.Errorf("Failed to read new account, ACME data conversion is not available : %v", err) return } storeCertificates, err := localStore.GetCertificates() if err != nil { log.Errorf("Failed to read new certificates, ACME data conversion is not available : %v", err) return } if storeAccount == nil { localStore := NewLocalStore(fileName) account, err := localStore.Get() if err != nil { log.Errorf("Failed to read old account, ACME data conversion is not available : %v", err) return } // Convert ACME data from old to new format newAccount := &acme.Account{} if account != nil && len(account.Email) > 0 { err = backupACMEFile(fileName, account) if err != nil { log.Errorf("Unable to create a backup for the V1 formatted ACME file: %s", err.Error()) return } err = RemoveAccountV1Values(account) if err != nil { log.Errorf("Unable to remove ACME Account V1 values: %s", err.Error()) return } newAccount = &acme.Account{ PrivateKey: account.PrivateKey, Registration: account.Registration, Email: account.Email, } var newCertificates []*acme.Certificate for _, cert := range account.DomainsCertificate.Certs { newCertificates = append(newCertificates, &acme.Certificate{ Certificate: cert.Certificate.Certificate, Key: cert.Certificate.PrivateKey, Domain: cert.Domains, }) } // If account is in the old format, storeCertificates is nil or empty and has to be initialized storeCertificates = newCertificates } // Store the data in new format into the file even if account is nil // to delete Account in ACME v1 format and keeping the certificates newLocalStore := acme.NewLocalStore(fileName) newLocalStore.SaveDataChan <- &acme.StoredData{Account: newAccount, Certificates: storeCertificates} } } func backupACMEFile(originalFileName string, account interface{}) error { // write account to file data, err := json.MarshalIndent(account, "", " ") if err != nil { return err } return ioutil.WriteFile(originalFileName+".bak", data, 0600) } // FromNewToOldFormat converts new acme account to the old one (used for the backward compatibility) func FromNewToOldFormat(fileName string) (*Account, error) { localStore := acme.NewLocalStore(fileName) storeAccount, err := localStore.GetAccount() if err != nil { return nil, err } storeCertificates, err := localStore.GetCertificates() if err != nil { return nil, err } // Convert ACME Account from new to old format // (Needed by the KV stores) var account *Account if storeAccount != nil { account = &Account{ Email: storeAccount.Email, PrivateKey: storeAccount.PrivateKey, Registration: storeAccount.Registration, DomainsCertificate: DomainsCertificates{}, } } // Convert ACME Certificates from new to old format // (Needed by the KV stores) if len(storeCertificates) > 0 { // Account can be nil if data are migrated from new format // with a ACME V1 Account if account == nil { account = &Account{} } for _, cert := range storeCertificates { _, err := account.DomainsCertificate.addCertificateForDomains(&Certificate{ Domain: cert.Domain.Main, Certificate: cert.Certificate, PrivateKey: cert.Key, }, cert.Domain) if err != nil { return nil, err } } } return account, nil }