2016-05-27 09:13:34 +00:00
package acme
import (
2017-06-19 11:22:41 +00:00
"crypto/tls"
2016-10-14 00:33:01 +00:00
"encoding/base64"
"net/http"
"net/http/httptest"
2016-05-27 09:13:34 +00:00
"reflect"
2016-06-20 11:55:50 +00:00
"sync"
2016-05-27 09:13:34 +00:00
"testing"
2016-12-14 17:10:10 +00:00
"time"
2016-12-30 08:21:13 +00:00
2018-03-05 19:54:04 +00:00
acmeprovider "github.com/containous/traefik/provider/acme"
2017-11-09 11:16:03 +00:00
"github.com/containous/traefik/tls/generate"
2018-03-05 19:54:04 +00:00
"github.com/containous/traefik/types"
2017-06-19 11:22:41 +00:00
"github.com/stretchr/testify/assert"
2018-04-06 15:04:03 +00:00
acme "github.com/xenolf/lego/acmev2"
2016-05-27 09:13:34 +00:00
)
func TestDomainsSet ( t * testing . T ) {
2018-03-05 19:54:04 +00:00
testCases := [ ] struct {
input string
expected types . Domains
} {
{
input : "" ,
expected : types . Domains { } ,
} ,
{
input : "foo1.com" ,
expected : types . Domains {
types . Domain { Main : "foo1.com" } ,
} ,
} ,
{
input : "foo2.com,bar.net" ,
expected : types . Domains {
types . Domain {
Main : "foo2.com" ,
SANs : [ ] string { "bar.net" } ,
} ,
} ,
} ,
{
input : "foo3.com,bar1.net,bar2.net,bar3.net" ,
expected : types . Domains {
types . Domain {
Main : "foo3.com" ,
SANs : [ ] string { "bar1.net" , "bar2.net" , "bar3.net" } ,
} ,
} ,
} ,
2016-05-27 09:13:34 +00:00
}
2018-03-05 19:54:04 +00:00
for _ , test := range testCases {
test := test
t . Run ( test . input , func ( t * testing . T ) {
t . Parallel ( )
domains := types . Domains { }
domains . Set ( test . input )
assert . Exactly ( t , test . expected , domains )
} )
2016-05-27 09:13:34 +00:00
}
}
func TestDomainsSetAppend ( t * testing . T ) {
2018-03-05 19:54:04 +00:00
testCases := [ ] struct {
input string
expected types . Domains
} {
2016-05-27 09:13:34 +00:00
{
2018-03-05 19:54:04 +00:00
input : "" ,
expected : types . Domains { } ,
} ,
2016-05-27 09:13:34 +00:00
{
2018-03-05 19:54:04 +00:00
input : "foo1.com" ,
expected : types . Domains {
types . Domain { Main : "foo1.com" } ,
} ,
} ,
{
input : "foo2.com,bar.net" ,
expected : types . Domains {
types . Domain { Main : "foo1.com" } ,
types . Domain {
Main : "foo2.com" ,
SANs : [ ] string { "bar.net" } ,
} ,
} ,
} ,
2016-05-27 09:13:34 +00:00
{
2018-03-05 19:54:04 +00:00
input : "foo3.com,bar1.net,bar2.net,bar3.net" ,
expected : types . Domains {
types . Domain { Main : "foo1.com" } ,
types . Domain {
Main : "foo2.com" ,
SANs : [ ] string { "bar.net" } ,
} ,
types . Domain {
Main : "foo3.com" ,
SANs : [ ] string { "bar1.net" , "bar2.net" , "bar3.net" } ,
} ,
} ,
} ,
2016-05-27 09:13:34 +00:00
}
2018-03-05 19:54:04 +00:00
// append to
domains := types . Domains { }
for _ , test := range testCases {
t . Run ( test . input , func ( t * testing . T ) {
domains . Set ( test . input )
assert . Exactly ( t , test . expected , domains )
} )
2016-05-27 09:13:34 +00:00
}
}
2016-06-20 11:55:50 +00:00
func TestCertificatesRenew ( t * testing . T ) {
2017-11-09 11:16:03 +00:00
foo1Cert , foo1Key , _ := generate . KeyPair ( "foo1.com" , time . Now ( ) )
foo2Cert , foo2Key , _ := generate . KeyPair ( "foo2.com" , time . Now ( ) )
2018-03-05 19:54:04 +00:00
2016-06-20 11:55:50 +00:00
domainsCertificates := DomainsCertificates {
2016-09-23 16:27:01 +00:00
lock : sync . RWMutex { } ,
2016-06-20 11:55:50 +00:00
Certs : [ ] * DomainsCertificate {
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "foo1.com" } ,
2016-06-20 11:55:50 +00:00
Certificate : & Certificate {
Domain : "foo1.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
2016-12-14 17:10:10 +00:00
PrivateKey : foo1Key ,
Certificate : foo1Cert ,
2016-06-20 11:55:50 +00:00
} ,
} ,
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "foo2.com" } ,
2016-06-20 11:55:50 +00:00
Certificate : & Certificate {
Domain : "foo2.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
2016-12-14 17:10:10 +00:00
PrivateKey : foo2Key ,
Certificate : foo2Cert ,
2016-06-20 11:55:50 +00:00
} ,
} ,
} ,
}
2018-03-05 19:54:04 +00:00
2017-11-09 11:16:03 +00:00
foo1Cert , foo1Key , _ = generate . KeyPair ( "foo1.com" , time . Now ( ) )
2016-06-20 11:55:50 +00:00
newCertificate := & Certificate {
Domain : "foo1.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
2016-12-14 17:10:10 +00:00
PrivateKey : foo1Key ,
Certificate : foo1Cert ,
2016-06-20 11:55:50 +00:00
}
2018-03-05 19:54:04 +00:00
err := domainsCertificates . renewCertificates ( newCertificate , types . Domain { Main : "foo1.com" } )
2016-06-20 11:55:50 +00:00
if err != nil {
t . Errorf ( "Error in renewCertificates :%v" , err )
}
2018-03-05 19:54:04 +00:00
2016-06-20 11:55:50 +00:00
if len ( domainsCertificates . Certs ) != 2 {
t . Errorf ( "Expected domainsCertificates length %d %+v\nGot %+v" , 2 , domainsCertificates . Certs , len ( domainsCertificates . Certs ) )
}
2018-03-05 19:54:04 +00:00
2016-06-20 11:55:50 +00:00
if ! reflect . DeepEqual ( domainsCertificates . Certs [ 0 ] . Certificate , newCertificate ) {
t . Errorf ( "Expected new certificate %+v \nGot %+v" , newCertificate , domainsCertificates . Certs [ 0 ] . Certificate )
}
}
2016-10-14 00:33:01 +00:00
2016-12-14 17:10:10 +00:00
func TestRemoveDuplicates ( t * testing . T ) {
now := time . Now ( )
2017-11-09 11:16:03 +00:00
fooCert , fooKey , _ := generate . KeyPair ( "foo.com" , now )
foo24Cert , foo24Key , _ := generate . KeyPair ( "foo.com" , now . Add ( 24 * time . Hour ) )
foo48Cert , foo48Key , _ := generate . KeyPair ( "foo.com" , now . Add ( 48 * time . Hour ) )
barCert , barKey , _ := generate . KeyPair ( "bar.com" , now )
2016-12-14 17:10:10 +00:00
domainsCertificates := DomainsCertificates {
lock : sync . RWMutex { } ,
Certs : [ ] * DomainsCertificate {
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "foo.com" } ,
2016-12-14 17:10:10 +00:00
Certificate : & Certificate {
Domain : "foo.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
PrivateKey : foo24Key ,
Certificate : foo24Cert ,
} ,
} ,
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "foo.com" } ,
2016-12-14 17:10:10 +00:00
Certificate : & Certificate {
Domain : "foo.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
PrivateKey : foo48Key ,
Certificate : foo48Cert ,
} ,
} ,
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "foo.com" } ,
2016-12-14 17:10:10 +00:00
Certificate : & Certificate {
Domain : "foo.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
PrivateKey : fooKey ,
Certificate : fooCert ,
} ,
} ,
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "bar.com" } ,
2016-12-14 17:10:10 +00:00
Certificate : & Certificate {
Domain : "bar.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
PrivateKey : barKey ,
Certificate : barCert ,
} ,
} ,
{
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
Main : "foo.com" } ,
2016-12-14 17:10:10 +00:00
Certificate : & Certificate {
Domain : "foo.com" ,
CertURL : "url" ,
CertStableURL : "url" ,
PrivateKey : foo48Key ,
Certificate : foo48Cert ,
} ,
} ,
} ,
}
domainsCertificates . Init ( )
if len ( domainsCertificates . Certs ) != 2 {
t . Errorf ( "Expected domainsCertificates length %d %+v\nGot %+v" , 2 , domainsCertificates . Certs , len ( domainsCertificates . Certs ) )
}
for _ , cert := range domainsCertificates . Certs {
switch cert . Domains . Main {
case "bar.com" :
continue
case "foo.com" :
if ! cert . tlsCert . Leaf . NotAfter . Equal ( now . Add ( 48 * time . Hour ) . Truncate ( 1 * time . Second ) ) {
t . Errorf ( "Bad expiration %s date for domain %+v, now %s" , cert . tlsCert . Leaf . NotAfter . String ( ) , cert , now . Add ( 48 * time . Hour ) . Truncate ( 1 * time . Second ) . String ( ) )
}
default :
2017-02-02 20:07:44 +00:00
t . Errorf ( "Unknown domain %+v" , cert )
2016-12-14 17:10:10 +00:00
}
}
}
2016-10-14 00:33:01 +00:00
func TestNoPreCheckOverride ( t * testing . T ) {
acme . PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
err := dnsOverrideDelay ( 0 )
if err != nil {
t . Errorf ( "Error in dnsOverrideDelay :%v" , err )
}
if acme . PreCheckDNS != nil {
2017-05-26 15:03:14 +00:00
t . Error ( "Unexpected change to acme.PreCheckDNS when leaving DNS verification as is." )
2016-10-14 00:33:01 +00:00
}
}
func TestSillyPreCheckOverride ( t * testing . T ) {
err := dnsOverrideDelay ( - 5 )
if err == nil {
2017-05-26 15:03:14 +00:00
t . Error ( "Missing expected error in dnsOverrideDelay!" )
2016-10-14 00:33:01 +00:00
}
}
func TestPreCheckOverride ( t * testing . T ) {
acme . PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
err := dnsOverrideDelay ( 5 )
if err != nil {
t . Errorf ( "Error in dnsOverrideDelay :%v" , err )
}
if acme . PreCheckDNS == nil {
2017-05-26 15:03:14 +00:00
t . Error ( "No change to acme.PreCheckDNS when meant to be adding enforcing override function." )
2016-10-14 00:33:01 +00:00
}
}
func TestAcmeClientCreation ( t * testing . T ) {
acme . PreCheckDNS = nil // Irreversable - but not expecting real calls into this during testing process
// Lengthy setup to avoid external web requests - oh for easier golang testing!
account := & Account { Email : "f@f" }
account . PrivateKey , _ = base64 . StdEncoding . DecodeString ( `
MIIBPAIBAAJBAMp2Ni92FfEur + CAvFkgC12LT4l9D53ApbBpDaXaJkzzks + KsLw9zyAxvlrfAyTCQ
7 tDnEnIltAXyQ0uOFUUdcMCAwEAAQJAK1FbipATZcT9cGVa5x7KD7usytftLW14heQUPXYNV80r / 3
lmnpvjL06dffRpwkYeN8DATQF / QOcy3NNNGDw / 4 QIhAPAKmiZFxA / qmRXsuU8Zhlzf16WrNZ68K64
asn / h3qZrAiEA1 + wFR3WXCPIolOvd7AHjfgcTKQNkoMPywU4FYUNQ1AkCIQDv8yk0qPjckD6HVCPJ
llJh9MC0svjevGtNlxJoE3lmEQIhAKXy1wfZ32 / XtcrnENPvi6lzxI0T94X7s5pP3aCoPPoJAiEAl
cijFkALeQp / qyeXdFld2v9gUN3eCgljgcl0QweRoIc = -- - ` )
ts := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Write ( [ ] byte ( ` {
2018-03-26 12:12:03 +00:00
"GPHhmRVEDas" : "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417" ,
"keyChange" : "https://foo/acme/key-change" ,
"meta" : {
"termsOfService" : "https://boulder:4431/terms/v7"
} ,
"newAccount" : "https://foo/acme/new-acct" ,
"newNonce" : "https://foo/acme/new-nonce" ,
"newOrder" : "https://foo/acme/new-order" ,
"revokeCert" : "https://foo/acme/revoke-cert"
2016-10-14 00:33:01 +00:00
} ` ) )
} ) )
defer ts . Close ( )
2018-03-05 19:54:04 +00:00
a := ACME { DNSChallenge : & acmeprovider . DNSChallenge { Provider : "manual" , DelayBeforeCheck : 10 } , CAServer : ts . URL }
2016-10-14 00:33:01 +00:00
client , err := a . buildACMEClient ( account )
if err != nil {
t . Errorf ( "Error in buildACMEClient: %v" , err )
}
if client == nil {
2017-05-26 15:03:14 +00:00
t . Error ( "No client from buildACMEClient!" )
2016-10-14 00:33:01 +00:00
}
if acme . PreCheckDNS == nil {
2017-05-26 15:03:14 +00:00
t . Error ( "No change to acme.PreCheckDNS when meant to be adding enforcing override function." )
2016-10-14 00:33:01 +00:00
}
}
2017-06-19 11:22:41 +00:00
2018-02-26 10:38:03 +00:00
func TestAcme_getUncheckedCertificates ( t * testing . T ) {
2017-06-19 11:22:41 +00:00
mm := make ( map [ string ] * tls . Certificate )
mm [ "*.containo.us" ] = & tls . Certificate { }
mm [ "traefik.acme.io" ] = & tls . Certificate { }
a := ACME { TLSConfig : & tls . Config { NameToCertificate : mm } }
domains := [ ] string { "traefik.containo.us" , "trae.containo.us" }
2018-02-26 10:38:03 +00:00
uncheckedDomains := a . getUncheckedDomains ( domains , nil )
assert . Empty ( t , uncheckedDomains )
2017-06-19 11:22:41 +00:00
domains = [ ] string { "traefik.acme.io" , "trae.acme.io" }
2018-02-26 10:38:03 +00:00
uncheckedDomains = a . getUncheckedDomains ( domains , nil )
assert . Len ( t , uncheckedDomains , 1 )
domainsCertificates := DomainsCertificates { Certs : [ ] * DomainsCertificate {
{
tlsCert : & tls . Certificate { } ,
2018-03-05 19:54:04 +00:00
Domains : types . Domain {
2018-02-26 10:38:03 +00:00
Main : "*.acme.wtf" ,
SANs : [ ] string { "trae.acme.io" } ,
} ,
} ,
} }
account := Account { DomainsCertificate : domainsCertificates }
uncheckedDomains = a . getUncheckedDomains ( domains , & account )
assert . Empty ( t , uncheckedDomains )
}
func TestAcme_getProvidedCertificate ( t * testing . T ) {
mm := make ( map [ string ] * tls . Certificate )
mm [ "*.containo.us" ] = & tls . Certificate { }
mm [ "traefik.acme.io" ] = & tls . Certificate { }
a := ACME { TLSConfig : & tls . Config { NameToCertificate : mm } }
domain := "traefik.containo.us"
certificate := a . getProvidedCertificate ( domain )
assert . NotNil ( t , certificate )
domain = "trae.acme.io"
certificate = a . getProvidedCertificate ( domain )
2017-06-19 11:22:41 +00:00
assert . Nil ( t , certificate )
}
2018-03-26 12:12:03 +00:00
func TestAcme_getValidDomain ( t * testing . T ) {
testCases := [ ] struct {
desc string
domains [ ] string
wildcardAllowed bool
dnsChallenge * acmeprovider . DNSChallenge
expectedErr string
expectedDomains [ ] string
} {
{
desc : "valid wildcard" ,
domains : [ ] string { "*.traefik.wtf" } ,
dnsChallenge : & acmeprovider . DNSChallenge { } ,
wildcardAllowed : true ,
expectedErr : "" ,
expectedDomains : [ ] string { "*.traefik.wtf" } ,
} ,
{
desc : "no wildcard" ,
domains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
dnsChallenge : & acmeprovider . DNSChallenge { } ,
expectedErr : "" ,
wildcardAllowed : true ,
expectedDomains : [ ] string { "traefik.wtf" , "foo.traefik.wtf" } ,
} ,
{
desc : "unauthorized wildcard" ,
domains : [ ] string { "*.traefik.wtf" } ,
dnsChallenge : & acmeprovider . DNSChallenge { } ,
wildcardAllowed : false ,
expectedErr : "unable to generate a wildcard certificate for domain \"*.traefik.wtf\" from a 'Host' rule" ,
expectedDomains : nil ,
} ,
{
desc : "no domain" ,
domains : [ ] string { } ,
dnsChallenge : nil ,
wildcardAllowed : true ,
expectedErr : "unable to generate a certificate when no domain is given" ,
expectedDomains : nil ,
} ,
{
desc : "no DNSChallenge" ,
domains : [ ] string { "*.traefik.wtf" , "foo.traefik.wtf" } ,
dnsChallenge : nil ,
wildcardAllowed : true ,
expectedErr : "unable to generate a wildcard certificate 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 : [ ] string { "*.*.traefik.wtf" , "foo.traefik.wtf" } ,
dnsChallenge : & acmeprovider . DNSChallenge { } ,
wildcardAllowed : true ,
expectedErr : "unable to generate a wildcard certificate for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain" ,
expectedDomains : nil ,
} ,
{
desc : "wildcard with SANs" ,
domains : [ ] string { "*.traefik.wtf" , "traefik.wtf" } ,
dnsChallenge : & acmeprovider . DNSChallenge { } ,
wildcardAllowed : true ,
expectedErr : "" ,
expectedDomains : [ ] string { "*.traefik.wtf" , "traefik.wtf" } ,
} ,
2018-03-26 12:12:03 +00:00
{
desc : "unexpected SANs" ,
2018-04-11 15:16:07 +00:00
domains : [ ] string { "*.traefik.wtf" , "*.acme.wtf" } ,
2018-03-26 12:12:03 +00:00
dnsChallenge : & acmeprovider . DNSChallenge { } ,
wildcardAllowed : true ,
2018-04-11 15:16:07 +00:00
expectedErr : "unable to generate a certificate for domains \"*.traefik.wtf,*.acme.wtf\": SANs can not be a wildcard domain" ,
2018-03-26 12:12:03 +00:00
expectedDomains : nil ,
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
a := ACME { }
if test . dnsChallenge != nil {
a . DNSChallenge = test . dnsChallenge
}
domains , err := a . getValidDomains ( test . domains , test . wildcardAllowed )
if len ( test . expectedErr ) > 0 {
assert . EqualError ( t , err , test . expectedErr , "Unexpected error." )
} else {
assert . Equal ( t , len ( test . expectedDomains ) , len ( domains ) , "Unexpected domains." )
}
} )
}
}
2018-03-27 14:18:03 +00:00
func TestAcme_getCertificateForDomain ( t * testing . T ) {
testCases := [ ] struct {
desc string
domain string
dc * DomainsCertificates
expected * DomainsCertificate
expectedFound bool
} {
{
desc : "non-wildcard exact match" ,
domain : "foo.traefik.wtf" ,
dc : & DomainsCertificates {
Certs : [ ] * DomainsCertificate {
{
Domains : types . Domain {
Main : "foo.traefik.wtf" ,
} ,
} ,
} ,
} ,
expected : & DomainsCertificate {
Domains : types . Domain {
Main : "foo.traefik.wtf" ,
} ,
} ,
expectedFound : true ,
} ,
{
desc : "non-wildcard no match" ,
domain : "bar.traefik.wtf" ,
dc : & DomainsCertificates {
Certs : [ ] * DomainsCertificate {
{
Domains : types . Domain {
Main : "foo.traefik.wtf" ,
} ,
} ,
} ,
} ,
expected : nil ,
expectedFound : false ,
} ,
{
desc : "wildcard match" ,
domain : "foo.traefik.wtf" ,
dc : & DomainsCertificates {
Certs : [ ] * DomainsCertificate {
{
Domains : types . Domain {
Main : "*.traefik.wtf" ,
} ,
} ,
} ,
} ,
expected : & DomainsCertificate {
Domains : types . Domain {
Main : "*.traefik.wtf" ,
} ,
} ,
expectedFound : true ,
} ,
{
desc : "wildcard no match" ,
domain : "foo.traefik.wtf" ,
dc : & DomainsCertificates {
Certs : [ ] * DomainsCertificate {
{
Domains : types . Domain {
Main : "*.bar.traefik.wtf" ,
} ,
} ,
} ,
} ,
expected : nil ,
expectedFound : false ,
} ,
}
for _ , test := range testCases {
test := test
t . Run ( test . desc , func ( t * testing . T ) {
t . Parallel ( )
got , found := test . dc . getCertificateForDomain ( test . domain )
assert . Equal ( t , test . expectedFound , found )
assert . Equal ( t , test . expected , got )
} )
}
}