-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added IgnoreFilter for ACME domain cert generation #1260
Added IgnoreFilter for ACME domain cert generation #1260
Conversation
acme/acme.go
Outdated
for k := range a.TLSConfig.NameToCertificate { | ||
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$" | ||
match, _ := regexp.MatchString(selector, domain) | ||
if match { | ||
return a.TLSConfig.NameToCertificate[k], nil | ||
log.Debugf("Found a wildcard certificate to use as fallback: %v", k) | ||
wildcardCertificate = a.TLSConfig.NameToCertificate[k] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is the only time wildcardCertificate
is used, why not just return it directly? Why store it, and check if it is empty later?
Seems like more code execution unnecessarily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. Will move this part.
acme/acme.go
Outdated
return | ||
} | ||
|
||
// Check if our domains are matching our ingoreFilters | ||
if len(a.IgnoreFilters) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is loadCertificateOnDemand
the correct location for this code?
This means that the certificate will be requested from acme, but not returned correct?
Wouldn't this be better served in getCertificate
before a potential cert request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case we are in a 'onDemand' for now there is no check with ignoreFilters
, this can be added if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dtomcej please advise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be good.
1f075a0
to
1797ab9
Compare
0f60a6b
to
f5c05b6
Compare
This feature will be really usefull. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrilPeponnet Few comments but, overall, looks good to me :)
Do you think you could add some tests on this?
acme/acme.go
Outdated
} | ||
|
||
// Check for existing challenge cert | ||
log.Debugf("Checking ACME challenge for %v", domain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would change this debug log to Getting ACME certificate for %s
acme/acme.go
Outdated
return | ||
} | ||
|
||
// Check if our domains are matching our ingoreFilters | ||
if len(a.IgnoreFilters) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to put all the IgnoreFilters
code into a function? It seems to look like https://github.com/containous/traefik/pull/1260/files#diff-faa8f9dd50dafacfcb04a8203ce3deebR542 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM 👼
/cc @containous/traefik for reviews
|
||
// Check if we have a wildcard cert into TLSConfig that could be used | ||
log.Debugf("Checking wildcard certificate matching for %v", domain) | ||
for k := range a.TLSConfig.NameToCertificate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By having this block here, it will never get executed on-demand, since the on demand domain will have already been requested from LE.
I think that this should be modified to check IF match AND not blacklisted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it will be executed if the ondemand failed to get a cert (due to a filter for instance)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dtomcej ping...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrilPeponnet, the inversion of certificates checkings you want do has a real interest for the use cases described into the PR but it seems to break the backward compatibily.
If it becomes the default behavior, all users will have to specify a IgnoreFilters
value.
That's why it may be good to invers the behaviour ONLY IF IgnoreFilters
is specified, so users who will not fill the field will keep the current behavior.
In the way to do this, the wildcard certificate checking could be done in a new function, called at the beginning or the end of getCertificate
in function of the size of the IgnoreFilters
list.
The code should look like this :
func (a *ACME) getWildCardCertificate(domain string) (*tls.Certificate, nool) {
// Check if we have a wildcard cert into TLSConfig that could be used
log.Debugf("Checking wildcard certificate matching for %v", domain)
for k := range a.TLSConfig.NameToCertificate {
selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$"
match, _ := regexp.MatchString(selector, domain)
if match {
log.Debugf("Found a wildcard certificate to use as fallback: %v", k)
return a.TLSConfig.NameToCertificate[k], true
}
}
return nil, false
}
func (a *ACME) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domain := types.CanonicalDomain(clientHello.ServerName)
account := a.store.Get().(*Account)
if a.IgnoreFilters == nil || a.IgnoreFilters.len == 0 {
if wildcardCert, ok := a.getWildCardCertificate(domain); ok {
return wildcardCert, nil
}
}
// Check for existing challenge cert
log.Debugf("Checking ACME challenge for %s", domain)
if challengeCert, ok := a.challengeProvider.getCertificate(domain); ok {
log.Debugf("Returning ACME challenge cert for %s", domain)
return challengeCert, nil
}
// Check for existing ACME domain cert
log.Debugf("Checking ACME domain cert for %v", domain)
if domainCert, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok {
log.Debugf("Returning ACME domain cert for %s", domain)
return domainCert.tlsCert, nil
}
if a.OnDemand {
if a.checkOnDemandDomain != nil && !a.checkOnDemandDomain(domain) {
return nil, nil
}
cert, err := a.loadCertificateOnDemand(clientHello)
if cert != nil && err == nil {
return cert, nil
} else if err != nil {
log.Errorf("On demand certificate retrieval failed for %v due to %v", domain, err)
}
}
if a.IgnoreFilters != nil && a.IgnoreFilters.len > 0 {
if wildcardCert, ok := a.getWildCardCertificate(domain); ok {
return wildcardCert, nil
}
}
log.Debugf("No certificate found to return for %s", domain)
return nil, nil
}
What is your mind about this suggestion?
cc @dtomcej
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the use case... Sorry it as been a while... Can you please explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CyrilPeponnet, any thoughts on this ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I've been busy this week. Will try to get a better look later on the day.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well the issue I tried to address with this PR was also the fact that if I have both a self signed wildcard cert and an ACME cert, only the wildcard will be returned (case 2 and 3 in PR description).
The purpose of the IgnoreFilters
was to filter out some (sub)domains for LE certificate generation. (case 1 in PR description).
So to sum up I'm trying to address:
1 - if LE and wildcard are present, return LE
2 - the IgnoreFilters
can be used to filter the LE cert generation
I don't think the part 2 is changing any behavior. Part 1 is.
Now I can see 2 uses cases for Part 1.
- Current behavior, always return wildcard if any
- if I have a signed wildcard, it makes sense to use it instead of LE cert (but the LE cert will still exists, hidden, and will be renewed and not used).
- New behavior, return LE over wildcard,
- make sense if I use a self signed wildcard cert.
I'm not sure on how to address those two cases as they can both exists in the same traefik instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many thanks for you reply.
As you just said, the Part1 of your response changes deeply the current behavior if the IgnoreFilter
field is used to filter domains checked by LE.
But I believe the suggestion we did (use IgnoreFilter
to filter (sub)domains checked by a wildcard certificate) allows to respect the 2 use cases you described :
- I have a signed wildcard and I want to use it instead of LE certificate : I don't use
IgnoreFilter
field, the behavior will remain the same as now. - I have a self-signed wildcard and I prefer to use LE for any (all?) (sub)domains : I add the list of (sub)domains which haven't to be checked by the wildcard certificate into the
IgnoreFilter
field and the LE certificate will be used.
@CyrilPeponnet Are you agree with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if by doing this it will conflict with the IgnoreFilter
used to allow or not LE certificate requests.
Example:
I have *.domain.tld
which is self signed. I want fourme.domain.tld
to use LE.
If I set the IgnoreFilter
to fourme.domain.tld
, given your statement:
- it will not use the self-signed cert.
- it will not request a LE as per
LoadCertificateForDomains
func.
The only way to make it works is to define the domain inside the toml configuration (which is not filtered AFAIK).
The main usage for IgnoreFilter
was meant to filter out domains for which I don't want any LE generation.
Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thanks @CyrilPeponnet
Could rebase one more time before merge?
ping @containous/traefik |
c7bbc88
to
0ac510d
Compare
otherwise fallback to wildcard if any.
0ac510d
to
3a05e5d
Compare
acme/acme.go
Outdated
return | ||
} | ||
|
||
// Check if our domains are matching our ingoreFilters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ingoreFilters => ignoreFilters
acme/acme.go
Outdated
@@ -496,11 +538,18 @@ func (a *ACME) loadCertificateOnDemand(clientHello *tls.ClientHelloInfo) (*tls.C | |||
if certificateResource, ok := account.DomainsCertificate.getCertificateForDomain(domain); ok { | |||
return certificateResource.tlsCert, nil | |||
} | |||
|
|||
// Check if our domain is matching our ingoreFilters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ingoreFilters => ignoreFilters
traefik.sample.toml
Outdated
# Optional | ||
# | ||
# Example to ignore certificate generation for domains that are not like *.foo.domain.tld | ||
# IngnoreFilters = ['.*\.?[^foo]\.domain\.tld'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IngnoreFilters => IgnoreFilters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, thanks for spotting those.
Due to SemaphoreCI, I close and reopen the PR. |
acme/acme.go
Outdated
if cert != nil && err == nil { | ||
return cert, nil | ||
} | ||
log.Errorf("On demand certicate retrival failed for %v due to %v", domain, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
certicate retrival => certificate retrieval
…eponnet/traefik into acme_fix_and_improvements
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
acme/acme.go
Outdated
if cert != nil && err == nil { | ||
return cert, nil | ||
} | ||
log.Errorf("On demand certificate retrieval failed for %v due to %v", domain, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An error log is created even if err == nil
, it should be nice to add a test before to insert the log.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, but, cert can be nil if not cert found without raising an error. Will try to improve the logging.
ping @CyrilPeponnet |
As discussed with @CyrilPeponnet on Slack, the PR is closed for the moment. |
This Pull Request fixes the following:
And add the a new feature
ignoreFilters
to filter out domains for ACME certificate generation.The static declaration of domains in KV/TOML will override the filters.
Test cases:
case 1: I have wildcard certificate for
domain.tld
and I want acme to generate acme certs for only a subset of it.Use the
ignoreFilters
with a regular expression like.*\.?[^f][^o][^o]\.domain\.tld
(negative lookahead are not supported by golang), to generate acme cert for domains except those like*.foo.domain.tld
.case 2: I have wildcard for
domain.tld
but I want acme to take precedence over it for statically defined domains.Ignore the entire domain with a filter like
.*\.domain\.tld
, and define your domains in ACME configuration (don't forget to restart traefik as this part is not reloaded dynamicaly).case 3: I have wildcard certificate for
domain.tld
and I want it to be used instead of already defined acme certs.You will need to revoke and clean the acme cert from your configuration (if from KV, you will need to base64decode it, strip the cert part you want to remove, reencode, update it and restart traefik).
Let me know if I forgot something.
Fixes #1197