diff --git a/acme/acme.go b/acme/acme.go index 85599bc3e..492439d83 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -9,6 +9,7 @@ import ( fmtlog "log" "net" "net/http" + "net/url" "reflect" "strings" "time" @@ -183,7 +184,8 @@ func (a *ACME) leadershipListener(elected bool) error { account := object.(*Account) account.Init() // Reset Account values if caServer changed, thus registration URI can be updated - if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) { + if account != nil && account.Registration != nil && !isAccountMatchingCaServer(account.Registration.URI, a.CAServer) { + log.Info("Account URI does not match the current CAServer. The account will be reset") account.reset() } @@ -230,6 +232,20 @@ func (a *ACME) leadershipListener(elected bool) error { return nil } +func isAccountMatchingCaServer(accountURI string, serverURI string) bool { + aru, err := url.Parse(accountURI) + if err != nil { + log.Infof("Unable to parse account.Registration URL : %v", err) + return false + } + cau, err := url.Parse(serverURI) + if err != nil { + log.Infof("Unable to parse CAServer URL : %v", err) + return false + } + return cau.Hostname() == aru.Hostname() +} + func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { domain := types.CanonicalDomain(clientHello.ServerName) account := a.store.Get().(*Account) diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 7c6beb97a..57377cfd2 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" fmtlog "log" + "net/url" "reflect" "strings" "sync" @@ -130,7 +131,8 @@ func (p *Provider) Init(_ types.Constraints) error { } // Reset Account if caServer changed, thus registration URI can be updated - if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) { + if p.account != nil && p.account.Registration != nil && !isAccountMatchingCaServer(p.account.Registration.URI, p.CAServer) { + log.Info("Account URI does not match the current CAServer. The account will be reset") p.account = nil } @@ -142,6 +144,20 @@ func (p *Provider) Init(_ types.Constraints) error { return nil } +func isAccountMatchingCaServer(accountURI string, serverURI string) bool { + aru, err := url.Parse(accountURI) + if err != nil { + log.Infof("Unable to parse account.Registration URL : %v", err) + return false + } + cau, err := url.Parse(serverURI) + if err != nil { + log.Infof("Unable to parse CAServer URL : %v", err) + return false + } + return cau.Hostname() == aru.Hostname() +} + // Provide allows the file provider to provide configurations to traefik // using the given Configuration channel. func (p *Provider) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error { diff --git a/provider/acme/provider_test.go b/provider/acme/provider_test.go index 047f4058f..869b916fb 100644 --- a/provider/acme/provider_test.go +++ b/provider/acme/provider_test.go @@ -429,3 +429,78 @@ func TestDeleteUnnecessaryDomains(t *testing.T) { }) } } + +func TestIsAccountMatchingCaServer(t *testing.T) { + testCases := []struct { + desc string + accountURI string + serverURI string + expected bool + }{ + { + desc: "acme staging with matching account", + accountURI: "https://acme-staging-v02.api.letsencrypt.org/acme/acct/1234567", + serverURI: "https://acme-staging-v02.api.letsencrypt.org/acme/directory", + expected: true, + }, + { + desc: "acme production with matching account", + accountURI: "https://acme-v02.api.letsencrypt.org/acme/acct/1234567", + serverURI: "https://acme-v02.api.letsencrypt.org/acme/directory", + expected: true, + }, + { + desc: "http only acme with matching account", + accountURI: "http://acme.api.letsencrypt.org/acme/acct/1234567", + serverURI: "http://acme.api.letsencrypt.org/acme/directory", + expected: true, + }, + { + desc: "different subdomains for account and server", + accountURI: "https://test1.example.org/acme/acct/1234567", + serverURI: "https://test2.example.org/acme/directory", + expected: false, + }, + { + desc: "different domains for account and server", + accountURI: "https://test.example1.org/acme/acct/1234567", + serverURI: "https://test.example2.org/acme/directory", + expected: false, + }, + { + desc: "different tld for account and server", + accountURI: "https://test.example.com/acme/acct/1234567", + serverURI: "https://test.example.org/acme/directory", + expected: false, + }, + { + desc: "malformed account url", + accountURI: "//|\\/test.example.com/acme/acct/1234567", + serverURI: "https://test.example.com/acme/directory", + expected: false, + }, + { + desc: "malformed server url", + accountURI: "https://test.example.com/acme/acct/1234567", + serverURI: "//|\\/test.example.com/acme/directory", + expected: false, + }, + { + desc: "malformed server and account url", + accountURI: "//|\\/test.example.com/acme/acct/1234567", + serverURI: "//|\\/test.example.com/acme/directory", + expected: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + result := isAccountMatchingCaServer(test.accountURI, test.serverURI) + + assert.Equal(t, test.expected, result) + }) + } +}