1
0
Fork 0

Generate wildcard certificate with SANs in ACME

This commit is contained in:
NicoMen 2018-04-11 17:16:07 +02:00 committed by Traefiker Bot
parent 8168d2fdc1
commit 7109910f46
5 changed files with 271 additions and 96 deletions

View file

@ -42,7 +42,7 @@ type Configuration struct {
EntryPoint string `description:"EntryPoint to use."`
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
OnDemand bool `description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated
DNSChallenge *DNSChallenge `description:"Activate DNS-02 Challenge"`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge"`
Domains []types.Domain `description:"CN and SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='*.main.net'. No SANs for wildcards domain. Wildcard domains only accepted with DNSChallenge"`
}
@ -72,7 +72,7 @@ type Certificate struct {
// DNSChallenge contains DNS challenge Configuration
type DNSChallenge struct {
Provider string `description:"Use a DNS-02 based challenge provider rather than HTTPS."`
Provider string `description:"Use a DNS-01 based challenge provider rather than HTTPS."`
DelayBeforeCheck flaeg.Duration `description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers."`
}
@ -565,16 +565,16 @@ func (p *Provider) getValidDomains(domain types.Domain, wildcardAllowed bool) ([
if p.DNSChallenge == nil {
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME needs a DNSChallenge", strings.Join(domains, ","))
}
if len(domain.SANs) > 0 {
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : SANs are not allowed", strings.Join(domains, ","))
}
} else {
for _, san := range domain.SANs {
if strings.HasPrefix(san, "*") {
return nil, fmt.Errorf("unable to generate a certificate in ACME provider for domains %q: SANs can not be a wildcard domain", strings.Join(domains, ","))
}
if strings.HasPrefix(domain.Main, "*.*") {
return nil, fmt.Errorf("unable to generate a wildcard certificate in ACME provider for domain %q : ACME does not allow '*.*' wildcard domain", strings.Join(domains, ","))
}
}
for _, san := range domain.SANs {
if strings.HasPrefix(san, "*") {
return nil, fmt.Errorf("unable to generate a certificate in ACME provider for domains %q: SAN %q can not be a wildcard domain", strings.Join(domains, ","), san)
}
}
domains = fun.Map(types.CanonicalDomain, domains).([]string)
return domains, nil
}
@ -610,26 +610,31 @@ func (p *Provider) deleteUnnecessaryDomains() {
keepDomain = false
}
break
} else if strings.HasPrefix(domain.Main, "*") && domain.SANs == nil {
// Check if domains can be validated by the wildcard domain
var newDomainsToCheck []string
for _, domainProcessed := range domainToCheck.ToStrArray() {
if isDomainAlreadyChecked(domainProcessed, domain.ToStrArray()) {
log.Warnf("Domain %q will not be processed by ACME provider because it is validated by the wildcard %q", domainProcessed, domain.Main)
continue
}
newDomainsToCheck = append(newDomainsToCheck, domainProcessed)
}
// Delete the domain if both Main and SANs can be validated by the wildcard domain
// otherwise keep the unchecked values
if newDomainsToCheck == nil {
keepDomain = false
break
}
domainToCheck.Set(newDomainsToCheck)
}
// Check if CN or SANS to check already exists
// or can not be checked by a wildcard
var newDomainsToCheck []string
for _, domainProcessed := range domainToCheck.ToStrArray() {
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked(domainProcessed, domain.ToStrArray()) {
// The domain is duplicated in a CN
log.Warnf("Domain %q is duplicated in the configuration or validated by the domain %v. It will be processed once.", domainProcessed, domain)
continue
} else if domain.Main != domainProcessed && strings.HasPrefix(domain.Main, "*") && isDomainAlreadyChecked(domainProcessed, []string{domain.Main}) {
// Check if a wildcard can validate the domain
log.Warnf("Domain %q will not be processed by ACME provider because it is validated by the wildcard %q", domainProcessed, domain.Main)
continue
}
newDomainsToCheck = append(newDomainsToCheck, domainProcessed)
}
// Delete the domain if both Main and SANs can be validated by the wildcard domain
// otherwise keep the unchecked values
if newDomainsToCheck == nil {
keepDomain = false
break
}
domainToCheck.Set(newDomainsToCheck)
}
if keepDomain {

View file

@ -207,11 +207,27 @@ func TestGetValidDomain(t *testing.T) {
expectedDomains: nil,
},
{
desc: "unexpected SANs",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
desc: "unauthorized wildcard with SAN",
domains: types.Domain{Main: "*.*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf,foo.traefik.wtf\" : SANs are not allowed",
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain",
expectedDomains: nil,
},
{
desc: "wildcard and SANs",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"traefik.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "",
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
},
{
desc: "unexpected SANs",
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"*.acme.wtf"}},
dnsChallenge: &DNSChallenge{},
wildcardAllowed: true,
expectedErr: "unable to generate a certificate in ACME provider for domains \"*.traefik.wtf,*.acme.wtf\": SAN \"*.acme.wtf\" can not be a wildcard domain",
expectedDomains: nil,
},
}
@ -251,8 +267,8 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
Main: "*.foo.acme.wtf",
},
{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf", "bar.foo"},
Main: "acme02.wtf",
SANs: []string{"traefik.acme02.wtf", "bar.foo"},
},
},
expectedDomains: []types.Domain{
@ -262,15 +278,38 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
},
{
Main: "*.foo.acme.wtf",
SANs: []string{},
},
{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf", "bar.foo"},
Main: "acme02.wtf",
SANs: []string{"traefik.acme02.wtf", "bar.foo"},
},
},
},
{
desc: "2 domains with same values",
desc: "wildcard and root domain",
domains: []types.Domain{
{
Main: "acme.wtf",
},
{
Main: "*.acme.wtf",
SANs: []string{"acme.wtf"},
},
},
expectedDomains: []types.Domain{
{
Main: "acme.wtf",
SANs: []string{},
},
{
Main: "*.acme.wtf",
SANs: []string{},
},
},
},
{
desc: "2 equals domains",
domains: []types.Domain{
{
Main: "acme.wtf",
@ -288,6 +327,29 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
},
},
},
{
desc: "2 domains with same values",
domains: []types.Domain{
{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf"},
},
{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf", "foo.bar"},
},
},
expectedDomains: []types.Domain{
{
Main: "acme.wtf",
SANs: []string{"traefik.acme.wtf"},
},
{
Main: "foo.bar",
SANs: []string{},
},
},
},
{
desc: "domain totally checked by wildcard",
domains: []types.Domain{
@ -302,6 +364,25 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
expectedDomains: []types.Domain{
{
Main: "*.acme.wtf",
SANs: []string{},
},
},
},
{
desc: "duplicated wildcard",
domains: []types.Domain{
{
Main: "*.acme.wtf",
SANs: []string{"acme.wtf"},
},
{
Main: "*.acme.wtf",
},
},
expectedDomains: []types.Domain{
{
Main: "*.acme.wtf",
SANs: []string{"acme.wtf"},
},
},
},
@ -315,6 +396,10 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
{
Main: "*.acme.wtf",
},
{
Main: "who.acme.wtf",
SANs: []string{"traefik.acme.wtf", "bar.acme.wtf"},
},
},
expectedDomains: []types.Domain{
{
@ -323,6 +408,7 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
},
{
Main: "*.acme.wtf",
SANs: []string{},
},
},
},