package gandi
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"regexp"
"strings"
"testing"
"github.com/xenolf/lego/acme"
)
// stagingServer is the Let's Encrypt staging server used by the live test
const stagingServer = "https://acme-staging.api.letsencrypt.org/directory"
// user implements acme.User and is used by the live test
type user struct {
Email string
Registration *acme.RegistrationResource
key crypto.PrivateKey
}
func (u *user) GetEmail() string {
return u.Email
}
func (u *user) GetRegistration() *acme.RegistrationResource {
return u.Registration
}
func (u *user) GetPrivateKey() crypto.PrivateKey {
return u.key
}
// TestDNSProvider runs Present and CleanUp against a fake Gandi RPC
// Server, whose responses are predetermined for particular requests.
func TestDNSProvider(t *testing.T) {
fakeAPIKey := "123412341234123412341234"
fakeKeyAuth := "XXXX"
provider, err := NewDNSProviderCredentials(fakeAPIKey)
if err != nil {
t.Fatal(err)
}
regexpDate, err := regexp.Compile(`\[ACME Challenge [^\]:]*:[^\]]*\]`)
if err != nil {
t.Fatal(err)
}
// start fake RPC server
fakeServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "text/xml" {
t.Fatalf("Content-Type: text/xml header not found")
}
req, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
req = regexpDate.ReplaceAllLiteral(
req, []byte(`[ACME Challenge 01 Jan 16 00:00 +0000]`))
resp, ok := serverResponses[string(req)]
if !ok {
t.Fatalf("Server response for request not found")
}
_, err = io.Copy(w, strings.NewReader(resp))
if err != nil {
t.Fatal(err)
}
}))
defer fakeServer.Close()
// define function to override findZoneByFqdn with
fakeFindZoneByFqdn := func(fqdn string, nameserver []string) (string, error) {
return "example.com.", nil
}
// override gandi endpoint and findZoneByFqdn function
savedEndpoint, savedFindZoneByFqdn := endpoint, findZoneByFqdn
defer func() {
endpoint, findZoneByFqdn = savedEndpoint, savedFindZoneByFqdn
}()
endpoint, findZoneByFqdn = fakeServer.URL+"/", fakeFindZoneByFqdn
// run Present
err = provider.Present("abc.def.example.com", "", fakeKeyAuth)
if err != nil {
t.Fatal(err)
}
// run CleanUp
err = provider.CleanUp("abc.def.example.com", "", fakeKeyAuth)
if err != nil {
t.Fatal(err)
}
}
// TestDNSProviderLive performs a live test to obtain a certificate
// using the Let's Encrypt staging server. It runs provided that both
// the environment variables GANDI_API_KEY and GANDI_TEST_DOMAIN are
// set. Otherwise the test is skipped.
//
// To complete this test, go test must be run with the -timeout=40m
// flag, since the default timeout of 10m is insufficient.
func TestDNSProviderLive(t *testing.T) {
apiKey := os.Getenv("GANDI_API_KEY")
domain := os.Getenv("GANDI_TEST_DOMAIN")
if apiKey == "" || domain == "" {
t.Skip("skipping live test")
}
// create a user.
const rsaKeySize = 2048
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
if err != nil {
t.Fatal(err)
}
myUser := user{
Email: "test@example.com",
key: privateKey,
}
// create a client using staging server
client, err := acme.NewClient(stagingServer, &myUser, acme.RSA2048)
if err != nil {
t.Fatal(err)
}
provider, err := NewDNSProviderCredentials(apiKey)
if err != nil {
t.Fatal(err)
}
err = client.SetChallengeProvider(acme.DNS01, provider)
if err != nil {
t.Fatal(err)
}
client.ExcludeChallenges([]acme.Challenge{acme.HTTP01, acme.TLSSNI01})
// register and agree tos
reg, err := client.Register()
if err != nil {
t.Fatal(err)
}
myUser.Registration = reg
err = client.AgreeToTOS()
if err != nil {
t.Fatal(err)
}
// complete the challenge
bundle := false
_, failures := client.ObtainCertificate([]string{domain}, bundle, nil, false)
if len(failures) > 0 {
t.Fatal(failures)
}
}
// serverResponses is the XML-RPC Request->Response map used by the
// fake RPC server. It was generated by recording a real RPC session
// which resulted in the successful issue of a cert, and then
// anonymizing the RPC data.
var serverResponses = map[string]string{
// Present Request->Response 1 (getZoneID)
`
domain.info
123412341234123412341234
example.com.
`: `
date_updated
20160216T16:14:23
date_delete
20170331T16:04:06
is_premium
0
date_hold_begin
20170215T02:04:06
date_registry_end
20170215T02:04:06
authinfo_expiration_date
20161211T21:31:20
contacts
owner
handle
LEGO-GANDI
id
111111
admin
handle
LEGO-GANDI
id
111111
bill
handle
LEGO-GANDI
id
111111
tech
handle
LEGO-GANDI
id
111111
reseller
nameservers
a.dns.gandi.net
b.dns.gandi.net
c.dns.gandi.net
date_restore_end
20170501T02:04:06
id
2222222
authinfo
ABCDABCDAB
status
clientTransferProhibited
serverTransferProhibited
tags
date_hold_end
20170401T02:04:06
services
gandidns
gandimail
date_pending_delete_end
20170506T02:04:06
zone_id
1234567
date_renew_begin
20120101T00:00:00
fqdn
example.com
autorenew
date_registry_creation
20150215T02:04:06
tld
org
date_created
20150215T03:04:06
`,
// Present Request->Response 2 (cloneZone)
`
domain.zone.clone
123412341234123412341234
1234567
0
name
example.com [ACME Challenge 01 Jan 16 00:00 +0000]
`: `
name
example.com [ACME Challenge 01 Jan 16 00:00 +0000]
versions
1
date_updated
20160216T16:24:29
id
7654321
owner
LEGO-GANDI
version
1
domains
0
public
0
`,
// Present Request->Response 3 (newZoneVersion)
`
domain.zone.version.new
123412341234123412341234
7654321
`: `
2
`,
// Present Request->Response 4 (addTXTRecord)
`
domain.zone.record.add
123412341234123412341234
7654321
2
type
TXT
name
_acme-challenge.abc.def
value
ezRpBPY8wH8djMLYjX2uCKPwiKDkFZ1SFMJ6ZXGlHrQ
ttl
300
`: `
name
_acme-challenge.abc.def
type
TXT
id
333333333
value
"ezRpBPY8wH8djMLYjX2uCKPwiKDkFZ1SFMJ6ZXGlHrQ"
ttl
300
`,
// Present Request->Response 5 (setZoneVersion)
`
domain.zone.version.set
123412341234123412341234
7654321
2
`: `
1
`,
// Present Request->Response 6 (setZone)
`
domain.zone.set
123412341234123412341234
example.com.
7654321
`: `
date_updated
20160216T16:14:23
date_delete
20170331T16:04:06
is_premium
0
date_hold_begin
20170215T02:04:06
date_registry_end
20170215T02:04:06
authinfo_expiration_date
20161211T21:31:20
contacts
owner
handle
LEGO-GANDI
id
111111
admin
handle
LEGO-GANDI
id
111111
bill
handle
LEGO-GANDI
id
111111
tech
handle
LEGO-GANDI
id
111111
reseller
nameservers
a.dns.gandi.net
b.dns.gandi.net
c.dns.gandi.net
date_restore_end
20170501T02:04:06
id
2222222
authinfo
ABCDABCDAB
status
clientTransferProhibited
serverTransferProhibited
tags
date_hold_end
20170401T02:04:06
services
gandidns
gandimail
date_pending_delete_end
20170506T02:04:06
zone_id
7654321
date_renew_begin
20120101T00:00:00
fqdn
example.com
autorenew
date_registry_creation
20150215T02:04:06
tld
org
date_created
20150215T03:04:06
`,
// CleanUp Request->Response 1 (setZone)
`
domain.zone.set
123412341234123412341234
example.com.
1234567
`: `
date_updated
20160216T16:24:38
date_delete
20170331T16:04:06
is_premium
0
date_hold_begin
20170215T02:04:06
date_registry_end
20170215T02:04:06
authinfo_expiration_date
20161211T21:31:20
contacts
owner
handle
LEGO-GANDI
id
111111
admin
handle
LEGO-GANDI
id
111111
bill
handle
LEGO-GANDI
id
111111
tech
handle
LEGO-GANDI
id
111111
reseller
nameservers
a.dns.gandi.net
b.dns.gandi.net
c.dns.gandi.net
date_restore_end
20170501T02:04:06
id
2222222
authinfo
ABCDABCDAB
status
clientTransferProhibited
serverTransferProhibited
tags
date_hold_end
20170401T02:04:06
services
gandidns
gandimail
date_pending_delete_end
20170506T02:04:06
zone_id
1234567
date_renew_begin
20120101T00:00:00
fqdn
example.com
autorenew
date_registry_creation
20150215T02:04:06
tld
org
date_created
20150215T03:04:06
`,
// CleanUp Request->Response 2 (deleteZone)
`
domain.zone.delete
123412341234123412341234
7654321
`: `
1
`,
}