2018-03-26 12:12:03 +00:00
package acme
import (
2018-11-14 09:18:03 +00:00
"context"
2018-03-26 12:12:03 +00:00
"crypto/tls"
"testing"
2021-11-10 11:06:09 +00:00
"time"
2018-03-26 12:12:03 +00:00
2020-09-04 08:52:03 +00:00
"github.com/go-acme/lego/v4/certcrypto"
2018-03-26 12:12:03 +00:00
"github.com/stretchr/testify/assert"
2020-09-16 13:46:04 +00:00
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/types"
2018-03-26 12:12:03 +00:00
)
func TestGetUncheckedCertificates ( t * testing . T ) {
2019-03-14 08:30:04 +00:00
t . Skip ( "Needs TLS Manager" )
2018-03-26 12:12:03 +00:00
wildcardMap := make ( map [ string ] * tls . Certificate )
wildcardMap [ "*.traefik.wtf" ] = & tls . Certificate { }
wildcardSafe := & safe . Safe { }
wildcardSafe . Set ( wildcardMap )
domainMap := make ( map [ string ] * tls . Certificate )
domainMap [ "traefik.wtf" ] = & tls . Certificate { }
domainSafe := & safe . Safe { }
domainSafe . Set ( domainMap )
2018-11-27 16:42:04 +00:00
// FIXME Add a test for DefaultCertificate
2018-03-26 12:12:03 +00:00
testCases := [ ] struct {
desc string
dynamicCerts * safe . Safe
2018-08-20 07:40:03 +00:00
resolvingDomains map [ string ] struct { }
2019-07-19 09:52:04 +00:00
acmeCertificates [ ] * CertAndStore
2018-03-26 12:12:03 +00:00
domains [ ] string
expectedDomains [ ] string
} {
{
desc : "wildcard to generate" ,
domains : [ ] string { "*.traefik.wtf" } ,
expectedDomains : [ ] string { "*.traefik.wtf" } ,
} ,
{
desc : "wildcard already exists in dynamic certificates" ,
domains : [ ] string { "*.traefik.wtf" } ,
dynamicCerts : wildcardSafe ,
expectedDomains : nil ,
} ,
{
desc : "wildcard already exists in ACME certificates" ,
domains : [ ] string { "*.traefik.wtf" } ,
2019-07-19 09:52:04 +00:00
acmeCertificates : [ ] * CertAndStore {
2018-03-26 12:12:03 +00:00
{
2019-07-19 09:52:04 +00:00
Certificate : Certificate {
Domain : types . Domain { Main : "*.traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
} ,
} ,
expectedDomains : nil ,
} ,
{
desc : "domain CN and SANs to generate" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
expectedDomains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
} ,
{
desc : "domain CN already exists in dynamic certificates and SANs to generate" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
dynamicCerts : domainSafe ,
expectedDomains : [ ] string { "foo.traefik.wtf" } ,
} ,
{
desc : "domain CN already exists in ACME certificates and SANs to generate" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
2019-07-19 09:52:04 +00:00
acmeCertificates : [ ] * CertAndStore {
2018-03-26 12:12:03 +00:00
{
2019-07-19 09:52:04 +00:00
Certificate : Certificate {
Domain : types . Domain { Main : "traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
} ,
} ,
expectedDomains : [ ] string { "foo.traefik.wtf" } ,
} ,
{
desc : "domain already exists in dynamic certificates" ,
domains : [ ] string { "traefik.wtf" } ,
dynamicCerts : domainSafe ,
expectedDomains : nil ,
} ,
{
desc : "domain already exists in ACME certificates" ,
domains : [ ] string { "traefik.wtf" } ,
2019-07-19 09:52:04 +00:00
acmeCertificates : [ ] * CertAndStore {
2018-03-26 12:12:03 +00:00
{
2019-07-19 09:52:04 +00:00
Certificate : Certificate {
Domain : types . Domain { Main : "traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
} ,
} ,
expectedDomains : nil ,
} ,
{
desc : "domain matched by wildcard in dynamic certificates" ,
domains : [ ] string { "who.traefik.wtf" , "foo.traefik.wtf" } ,
dynamicCerts : wildcardSafe ,
expectedDomains : nil ,
} ,
{
desc : "domain matched by wildcard in ACME certificates" ,
domains : [ ] string { "who.traefik.wtf" , "foo.traefik.wtf" } ,
2019-07-19 09:52:04 +00:00
acmeCertificates : [ ] * CertAndStore {
2018-03-26 12:12:03 +00:00
{
2019-07-19 09:52:04 +00:00
Certificate : Certificate {
Domain : types . Domain { Main : "*.traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
} ,
} ,
expectedDomains : nil ,
} ,
{
desc : "root domain with wildcard in ACME certificates" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
2019-07-19 09:52:04 +00:00
acmeCertificates : [ ] * CertAndStore {
2018-03-26 12:12:03 +00:00
{
2019-07-19 09:52:04 +00:00
Certificate : Certificate {
Domain : types . Domain { Main : "*.traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
} ,
} ,
expectedDomains : [ ] string { "traefik.wtf" } ,
} ,
2018-08-20 07:40:03 +00:00
{
desc : "all domains already managed by ACME" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
resolvingDomains : map [ string ] struct { } {
"traefik.wtf" : { } ,
"foo.traefik.wtf" : { } ,
} ,
expectedDomains : [ ] string { } ,
} ,
{
desc : "one domain already managed by ACME" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
resolvingDomains : map [ string ] struct { } {
"traefik.wtf" : { } ,
} ,
expectedDomains : [ ] string { "foo.traefik.wtf" } ,
} ,
{
desc : "wildcard domain already managed by ACME checks the domains" ,
domains : [ ] string { "bar.traefik.wtf" , "foo.traefik.wtf" } ,
resolvingDomains : map [ string ] struct { } {
"*.traefik.wtf" : { } ,
} ,
expectedDomains : [ ] string { } ,
} ,
{
desc : "wildcard domain already managed by ACME checks domains and another domain checks one other domain, one domain still unchecked" ,
domains : [ ] string { "traefik.wtf" , "bar.traefik.wtf" , "foo.traefik.wtf" , "acme.wtf" } ,
resolvingDomains : map [ string ] struct { } {
"*.traefik.wtf" : { } ,
"traefik.wtf" : { } ,
} ,
expectedDomains : [ ] string { "acme.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
2018-08-21 09:43:34 +00:00
if test . resolvingDomains == nil {
test . resolvingDomains = make ( map [ string ] struct { } )
}
2018-03-26 12:12:03 +00:00
acmeProvider := Provider {
2019-03-14 08:30:04 +00:00
// certificateStore: &traefiktls.CertificateStore{
// DynamicCerts: test.dynamicCerts,
// },
2018-08-20 07:40:03 +00:00
certificates : test . acmeCertificates ,
resolvingDomains : test . resolvingDomains ,
2018-03-26 12:12:03 +00:00
}
2019-07-19 09:52:04 +00:00
domains := acmeProvider . getUncheckedDomains ( context . Background ( ) , test . domains , "default" )
2018-03-26 12:12:03 +00:00
assert . Equal ( t , len ( test . expectedDomains ) , len ( domains ) , "Unexpected domains." )
} )
}
}
func TestGetValidDomain ( t * testing . T ) {
testCases := [ ] struct {
desc string
domains types . Domain
dnsChallenge * DNSChallenge
expectedErr string
expectedDomains [ ] string
} {
{
desc : "valid wildcard" ,
domains : types . Domain { Main : "*.traefik.wtf" } ,
dnsChallenge : & DNSChallenge { } ,
expectedErr : "" ,
expectedDomains : [ ] string { "*.traefik.wtf" } ,
} ,
{
desc : "no wildcard" ,
domains : types . Domain { Main : "traefik.wtf" , SANs : [ ] string { "foo.traefik.wtf" } } ,
dnsChallenge : & DNSChallenge { } ,
expectedErr : "" ,
expectedDomains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
} ,
{
desc : "no domain" ,
domains : types . Domain { } ,
dnsChallenge : nil ,
expectedErr : "unable to generate a certificate in ACME provider when no domain is given" ,
expectedDomains : nil ,
} ,
{
desc : "no DNSChallenge" ,
domains : types . Domain { Main : "*.traefik.wtf" , SANs : [ ] string { "foo.traefik.wtf" } } ,
dnsChallenge : nil ,
expectedErr : "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge" ,
expectedDomains : nil ,
} ,
2018-04-11 15:16:07 +00:00
{
desc : "unauthorized wildcard with SAN" ,
domains : types . Domain { Main : "*.*.traefik.wtf" , SANs : [ ] string { "foo.traefik.wtf" } } ,
dnsChallenge : & DNSChallenge { } ,
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 { } ,
expectedErr : "" ,
expectedDomains : [ ] string { "*.traefik.wtf" , "traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
{
2019-05-03 16:08:37 +00:00
desc : "wildcard SANs" ,
2018-04-11 15:16:07 +00:00
domains : types . Domain { Main : "*.traefik.wtf" , SANs : [ ] string { "*.acme.wtf" } } ,
2018-03-26 12:12:03 +00:00
dnsChallenge : & DNSChallenge { } ,
2019-05-03 16:08:37 +00:00
expectedErr : "" ,
expectedDomains : [ ] string { "*.traefik.wtf" , "*.acme.wtf" } ,
2018-03-26 12:12:03 +00:00
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
acmeProvider := Provider { Configuration : & Configuration { DNSChallenge : test . dnsChallenge } }
2019-07-19 09:52:04 +00:00
domains , err := acmeProvider . getValidDomains ( context . Background ( ) , test . domains )
2018-03-26 12:12:03 +00:00
if len ( test . expectedErr ) > 0 {
assert . EqualError ( t , err , test . expectedErr , "Unexpected error." )
} else {
assert . Equal ( t , len ( test . expectedDomains ) , len ( domains ) , "Unexpected domains." )
}
} )
}
}
func TestDeleteUnnecessaryDomains ( t * testing . T ) {
testCases := [ ] struct {
desc string
domains [ ] types . Domain
expectedDomains [ ] types . Domain
} {
{
desc : "no domain to delete" ,
domains : [ ] types . Domain {
{
Main : "acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "foo.bar" } ,
} ,
{
Main : "*.foo.acme.wtf" ,
} ,
{
2018-04-11 15:16:07 +00:00
Main : "acme02.wtf" ,
SANs : [ ] string { "traefik.acme02.wtf" , "bar.foo" } ,
2018-03-26 12:12:03 +00:00
} ,
} ,
expectedDomains : [ ] types . Domain {
{
Main : "acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "foo.bar" } ,
} ,
{
Main : "*.foo.acme.wtf" ,
2018-04-11 15:16:07 +00:00
SANs : [ ] string { } ,
} ,
{
Main : "acme02.wtf" ,
SANs : [ ] string { "traefik.acme02.wtf" , "bar.foo" } ,
} ,
} ,
} ,
{
desc : "wildcard and root domain" ,
domains : [ ] types . Domain {
{
Main : "acme.wtf" ,
2018-03-26 12:12:03 +00:00
} ,
2018-04-11 15:16:07 +00:00
{
Main : "*.acme.wtf" ,
SANs : [ ] string { "acme.wtf" } ,
} ,
} ,
expectedDomains : [ ] types . Domain {
2018-03-26 12:12:03 +00:00
{
Main : "acme.wtf" ,
2018-04-11 15:16:07 +00:00
SANs : [ ] string { } ,
} ,
{
Main : "*.acme.wtf" ,
SANs : [ ] string { } ,
2018-03-26 12:12:03 +00:00
} ,
} ,
} ,
{
2018-04-11 15:16:07 +00:00
desc : "2 equals domains" ,
2018-03-26 12:12:03 +00:00
domains : [ ] types . Domain {
{
Main : "acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "foo.bar" } ,
} ,
{
Main : "acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "foo.bar" } ,
} ,
} ,
expectedDomains : [ ] types . Domain {
{
Main : "acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "foo.bar" } ,
} ,
} ,
} ,
2018-04-11 15:16:07 +00:00
{
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 { } ,
} ,
} ,
} ,
2018-03-26 12:12:03 +00:00
{
desc : "domain totally checked by wildcard" ,
domains : [ ] types . Domain {
{
Main : "who.acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "bar.acme.wtf" } ,
} ,
{
Main : "*.acme.wtf" ,
} ,
} ,
expectedDomains : [ ] types . Domain {
{
Main : "*.acme.wtf" ,
2018-04-11 15:16:07 +00:00
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" } ,
2018-03-26 12:12:03 +00:00
} ,
} ,
} ,
{
desc : "domain partially checked by wildcard" ,
domains : [ ] types . Domain {
{
Main : "traefik.acme.wtf" ,
SANs : [ ] string { "acme.wtf" , "foo.bar" } ,
} ,
{
Main : "*.acme.wtf" ,
} ,
2018-04-11 15:16:07 +00:00
{
Main : "who.acme.wtf" ,
SANs : [ ] string { "traefik.acme.wtf" , "bar.acme.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
} ,
expectedDomains : [ ] types . Domain {
{
Main : "acme.wtf" ,
SANs : [ ] string { "foo.bar" } ,
} ,
{
Main : "*.acme.wtf" ,
2018-04-11 15:16:07 +00:00
SANs : [ ] string { } ,
2018-03-26 12:12:03 +00:00
} ,
} ,
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
2019-07-19 09:52:04 +00:00
domains := deleteUnnecessaryDomains ( context . Background ( ) , test . domains )
assert . Equal ( t , test . expectedDomains , domains , "unexpected domain" )
2018-03-26 12:12:03 +00:00
} )
}
}
2018-07-31 09:50:03 +00:00
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 ( )
2018-11-14 09:18:03 +00:00
result := isAccountMatchingCaServer ( context . Background ( ) , test . accountURI , test . serverURI )
2018-07-31 09:50:03 +00:00
assert . Equal ( t , test . expected , result )
} )
}
}
2018-07-31 10:32:04 +00:00
2018-08-08 05:58:03 +00:00
func TestInitAccount ( t * testing . T ) {
testCases := [ ] struct {
desc string
account * Account
email string
keyType string
expectedAccount * Account
} {
{
desc : "Existing account with all information" ,
account : & Account {
Email : "foo@foo.net" ,
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . EC256 ,
2018-08-08 05:58:03 +00:00
} ,
expectedAccount : & Account {
Email : "foo@foo.net" ,
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . EC256 ,
2018-08-08 05:58:03 +00:00
} ,
} ,
{
desc : "Account nil" ,
email : "foo@foo.net" ,
keyType : "EC256" ,
expectedAccount : & Account {
Email : "foo@foo.net" ,
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . EC256 ,
2018-08-08 05:58:03 +00:00
} ,
} ,
{
desc : "Existing account with no email" ,
account : & Account {
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . RSA4096 ,
2018-08-08 05:58:03 +00:00
} ,
email : "foo@foo.net" ,
keyType : "EC256" ,
expectedAccount : & Account {
Email : "foo@foo.net" ,
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . EC256 ,
2018-08-08 05:58:03 +00:00
} ,
} ,
{
desc : "Existing account with no key type" ,
account : & Account {
Email : "foo@foo.net" ,
} ,
email : "bar@foo.net" ,
keyType : "EC256" ,
expectedAccount : & Account {
Email : "foo@foo.net" ,
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . EC256 ,
2018-08-08 05:58:03 +00:00
} ,
} ,
{
desc : "Existing account and provider with no key type" ,
account : & Account {
Email : "foo@foo.net" ,
} ,
email : "bar@foo.net" ,
expectedAccount : & Account {
Email : "foo@foo.net" ,
2019-01-07 17:30:06 +00:00
KeyType : certcrypto . RSA4096 ,
2018-08-08 05:58:03 +00:00
} ,
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
acmeProvider := Provider { account : test . account , Configuration : & Configuration { Email : test . email , KeyType : test . keyType } }
2018-11-14 09:18:03 +00:00
actualAccount , err := acmeProvider . initAccount ( context . Background ( ) )
2018-08-08 05:58:03 +00:00
assert . Nil ( t , err , "Init account in error" )
assert . Equal ( t , test . expectedAccount . Email , actualAccount . Email , "unexpected email account" )
assert . Equal ( t , test . expectedAccount . KeyType , actualAccount . KeyType , "unexpected keyType account" )
} )
}
}
2021-11-10 11:06:09 +00:00
func Test_getCertificateRenewDurations ( t * testing . T ) {
testCases := [ ] struct {
desc string
certificatesDurations int
expectRenewPeriod time . Duration
expectRenewInterval time . Duration
} {
{
desc : "Less than 24 Hours certificates: 20 minutes renew period, 1 minutes renew interval" ,
certificatesDurations : 1 ,
expectRenewPeriod : time . Minute * 20 ,
expectRenewInterval : time . Minute ,
} ,
{
2022-04-26 12:36:08 +00:00
desc : "1 Year certificates: 4 months renew period, 1 week renew interval" ,
2021-11-10 11:06:09 +00:00
certificatesDurations : 24 * 365 ,
expectRenewPeriod : time . Hour * 24 * 30 * 4 ,
expectRenewInterval : time . Hour * 24 * 7 ,
} ,
2022-04-26 12:36:08 +00:00
{
desc : "265 Days certificates: 30 days renew period, 1 day renew interval" ,
certificatesDurations : 24 * 265 ,
expectRenewPeriod : time . Hour * 24 * 30 ,
expectRenewInterval : time . Hour * 24 ,
} ,
2021-11-10 11:06:09 +00:00
{
desc : "90 Days certificates: 30 days renew period, 1 day renew interval" ,
certificatesDurations : 24 * 90 ,
expectRenewPeriod : time . Hour * 24 * 30 ,
expectRenewInterval : time . Hour * 24 ,
} ,
{
desc : "7 Days certificates: 1 days renew period, 1 hour renew interval" ,
certificatesDurations : 24 * 7 ,
expectRenewPeriod : time . Hour * 24 ,
expectRenewInterval : time . Hour ,
} ,
{
desc : "24 Hours certificates: 6 hours renew period, 10 minutes renew interval" ,
certificatesDurations : 24 ,
expectRenewPeriod : time . Hour * 6 ,
expectRenewInterval : time . Minute * 10 ,
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
renewPeriod , renewInterval := getCertificateRenewDurations ( test . certificatesDurations )
assert . Equal ( t , test . expectRenewPeriod , renewPeriod )
assert . Equal ( t , test . expectRenewInterval , renewInterval )
} )
}
}