refactor: linting.

This commit is contained in:
Fernandez Ludovic 2018-05-28 18:31:28 +02:00 committed by Ludovic Fernandez
parent 1f0c69adc5
commit 9a1f8d748a
50 changed files with 570 additions and 601 deletions

View file

@ -72,7 +72,10 @@ func NewAccount(email string, conf *Configuration) *Account {
} }
acc.Registration = reg acc.Registration = reg
acc.Save() err = acc.Save()
if err != nil {
log.Fatalf("Could not save account for %s. Registration is nil -> %#v", email, err)
}
} }
if acc.conf == nil { if acc.conf == nil {

View file

@ -72,9 +72,6 @@ func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
if dir.NewOrderURL == "" { if dir.NewOrderURL == "" {
return nil, errors.New("directory missing new order URL") return nil, errors.New("directory missing new order URL")
} }
/*if dir.RevokeCertURL == "" {
return nil, errors.New("directory missing revoke certificate URL")
}*/
jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL} jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL}
if reg := user.GetRegistration(); reg != nil { if reg := user.GetRegistration(); reg != nil {
@ -179,7 +176,7 @@ func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string,
if c == nil || c.user == nil { if c == nil || c.user == nil {
return nil, errors.New("acme: cannot register a nil client or user") return nil, errors.New("acme: cannot register a nil client or user")
} }
logf("[INFO] acme: Registering account (EAB) for %s", c.user.GetEmail()) log.Printf("[INFO] acme: Registering account (EAB) for %s", c.user.GetEmail())
accMsg := accountMessage{} accMsg := accountMessage{}
if c.user.GetEmail() != "" { if c.user.GetEmail() != "" {
@ -240,7 +237,7 @@ func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
var retAccount accountMessage var retAccount accountMessage
c.jws.kid = accountLink c.jws.kid = accountLink
hdr, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount) _, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -261,11 +258,7 @@ func (c *Client) DeleteRegistration() error {
} }
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil) _, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil)
if err != nil { return err
return err
}
return nil
} }
// QueryRegistration runs a POST request on the client's registration and // QueryRegistration runs a POST request on the client's registration and
@ -829,11 +822,10 @@ func validate(j *jws, domain, uri string, c challenge) error {
log.Printf("[INFO][%s] The server validated our request", domain) log.Printf("[INFO][%s] The server validated our request", domain)
return nil return nil
case "pending": case "pending":
break
case "invalid": case "invalid":
return handleChallengeError(chlng) return handleChallengeError(chlng)
default: default:
return errors.New("The server returned an unexpected state") return errors.New("the server returned an unexpected state")
} }
ra, err := strconv.Atoi(hdr.Get("Retry-After")) ra, err := strconv.Atoi(hdr.Get("Retry-After"))

View file

@ -117,6 +117,10 @@ func GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
defer req.Body.Close() defer req.Body.Close()
ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024)) ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024))
if err != nil {
return nil, nil, err
}
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert) ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -137,7 +141,7 @@ func getKeyAuthorization(token string, key interface{}) (string, error) {
// Generate the Key Authorization for the challenge // Generate the Key Authorization for the challenge
jwk := &jose.JSONWebKey{Key: publicKey} jwk := &jose.JSONWebKey{Key: publicKey}
if jwk == nil { if jwk == nil {
return "", errors.New("Could not generate JWK from key") return "", errors.New("could not generate JWK from key")
} }
thumbBytes, err := jwk.Thumbprint(crypto.SHA256) thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
if err != nil { if err != nil {
@ -172,7 +176,7 @@ func parsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
} }
if len(certificates) == 0 { if len(certificates) == 0 {
return nil, errors.New("No certificates were found while parsing the bundle") return nil, errors.New("no certificates were found while parsing the bundle")
} }
return certificates, nil return certificates, nil
@ -187,7 +191,7 @@ func parsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
case "EC PRIVATE KEY": case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(keyBlock.Bytes) return x509.ParseECPrivateKey(keyBlock.Bytes)
default: default:
return nil, errors.New("Unknown PEM header value") return nil, errors.New("unknown PEM header value")
} }
} }
@ -206,7 +210,7 @@ func generatePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 8192) return rsa.GenerateKey(rand.Reader, 8192)
} }
return nil, fmt.Errorf("Invalid KeyType: %s", keyType) return nil, fmt.Errorf("invalid KeyType: %s", keyType)
} }
func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) { func generateCsr(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
@ -238,10 +242,8 @@ func pemEncode(data interface{}) []byte {
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes} pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
case *rsa.PrivateKey: case *rsa.PrivateKey:
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)} pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
break
case *x509.CertificateRequest: case *x509.CertificateRequest:
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw} pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
break
case derCertificateBytes: case derCertificateBytes:
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))} pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(derCertificateBytes))}
} }

View file

@ -28,7 +28,7 @@ func TestGenerateCSR(t *testing.T) {
if err != nil { if err != nil {
t.Error("Error generating CSR:", err) t.Error("Error generating CSR:", err)
} }
if csr == nil || len(csr) == 0 { if len(csr) == 0 {
t.Error("Expected CSR with data, but it was nil or length 0") t.Error("Expected CSR with data, but it was nil or length 0")
} }
} }

View file

@ -26,13 +26,13 @@ type jws struct {
func (j *jws) post(url string, content []byte) (*http.Response, error) { func (j *jws) post(url string, content []byte) (*http.Response, error) {
signedContent, err := j.signContent(url, content) signedContent, err := j.signContent(url, content)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error()) return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
} }
data := bytes.NewBuffer([]byte(signedContent.FullSerialize())) data := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
resp, err := httpPost(url, "application/jose+json", data) resp, err := httpPost(url, "application/jose+json", data)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to HTTP POST to %s -> %s", url, err.Error()) return nil, fmt.Errorf("failed to HTTP POST to %s -> %s", url, err.Error())
} }
nonce, nonceErr := getNonceFromResponse(resp) nonce, nonceErr := getNonceFromResponse(resp)
@ -77,12 +77,12 @@ func (j *jws) signContent(url string, content []byte) (*jose.JSONWebSignature, e
signer, err := jose.NewSigner(signKey, &options) signer, err := jose.NewSigner(signKey, &options)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to create jose signer -> %s", err.Error()) return nil, fmt.Errorf("failed to create jose signer -> %s", err.Error())
} }
signed, err := signer.Sign(content) signed, err := signer.Sign(content)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to sign content -> %s", err.Error()) return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
} }
return signed, nil return signed, nil
} }
@ -105,12 +105,12 @@ func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
}, },
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to create External Account Binding jose signer -> %s", err.Error()) return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %s", err.Error())
} }
signed, err := signer.Sign(jwkJSON) signed, err := signer.Sign(jwkJSON)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to External Account Binding sign content -> %s", err.Error()) return nil, fmt.Errorf("failed to External Account Binding sign content -> %s", err.Error())
} }
return signed, nil return signed, nil
@ -151,7 +151,7 @@ func (n *nonceManager) Push(nonce string) {
func getNonce(url string) (string, error) { func getNonce(url string) (string, error) {
resp, err := httpHead(url) resp, err := httpHead(url)
if err != nil { if err != nil {
return "", fmt.Errorf("Failed to get nonce from HTTP HEAD -> %s", err.Error()) return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %s", err.Error())
} }
return getNonceFromResponse(resp) return getNonceFromResponse(resp)
@ -160,7 +160,7 @@ func getNonce(url string) (string, error) {
func getNonceFromResponse(resp *http.Response) (string, error) { func getNonceFromResponse(resp *http.Response) (string, error) {
nonce := resp.Header.Get("Replay-Nonce") nonce := resp.Header.Get("Replay-Nonce")
if nonce == "" { if nonce == "" {
return "", fmt.Errorf("Server did not respond with a proper nonce header") return "", fmt.Errorf("server did not respond with a proper nonce header")
} }
return nonce, nil return nonce, nil

View file

@ -78,9 +78,6 @@ type csrMessage struct {
Csr string `json:"csr"` Csr string `json:"csr"`
} }
type emptyObjectMessage struct {
}
type revokeCertMessage struct { type revokeCertMessage struct {
Certificate string `json:"certificate"` Certificate string `json:"certificate"`
} }

View file

@ -1 +0,0 @@
package acme

View file

@ -52,7 +52,7 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
err := checkFolder(c.GlobalString("path")) err := checkFolder(c.GlobalString("path"))
if err != nil { if err != nil {
log.Fatalf("Could not check/create path: %s", err.Error()) log.Fatalf("Could not check/create path: %v", err)
} }
conf := NewConfiguration(c) conf := NewConfiguration(c)
@ -72,7 +72,7 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
client, err := acme.NewClient(c.GlobalString("server"), acc, keyType) client, err := acme.NewClient(c.GlobalString("server"), acc, keyType)
if err != nil { if err != nil {
log.Fatalf("Could not create client: %s", err.Error()) log.Fatalf("Could not create client: %v", err)
} }
if len(c.GlobalStringSlice("exclude")) > 0 { if len(c.GlobalStringSlice("exclude")) > 0 {
@ -85,7 +85,10 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
log.Fatal(err) log.Fatal(err)
} }
client.SetChallengeProvider(acme.HTTP01, provider) err = client.SetChallengeProvider(acme.HTTP01, provider)
if err != nil {
log.Fatal(err)
}
// --webroot=foo indicates that the user specifically want to do a HTTP challenge // --webroot=foo indicates that the user specifically want to do a HTTP challenge
// infer that the user also wants to exclude all other challenges // infer that the user also wants to exclude all other challenges
@ -97,17 +100,24 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
log.Fatal(err) log.Fatal(err)
} }
client.SetChallengeProvider(acme.HTTP01, provider) err = client.SetChallengeProvider(acme.HTTP01, provider)
if err != nil {
log.Fatal(err)
}
// --memcached-host=foo:11211 indicates that the user specifically want to do a HTTP challenge // --memcached-host=foo:11211 indicates that the user specifically want to do a HTTP challenge
// infer that the user also wants to exclude all other challenges // infer that the user also wants to exclude all other challenges
client.ExcludeChallenges([]acme.Challenge{acme.DNS01}) client.ExcludeChallenges([]acme.Challenge{acme.DNS01})
} }
if c.GlobalIsSet("http") { if c.GlobalIsSet("http") {
if strings.Index(c.GlobalString("http"), ":") == -1 { if !strings.Contains(c.GlobalString("http"), ":") {
log.Fatalf("The --http switch only accepts interface:port or :port for its argument.") log.Fatalf("The --http switch only accepts interface:port or :port for its argument.")
} }
client.SetHTTPAddress(c.GlobalString("http"))
err = client.SetHTTPAddress(c.GlobalString("http"))
if err != nil {
log.Fatal(err)
}
} }
if c.GlobalIsSet("dns") { if c.GlobalIsSet("dns") {
@ -116,7 +126,10 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
log.Fatal(err) log.Fatal(err)
} }
client.SetChallengeProvider(acme.DNS01, provider) err = client.SetChallengeProvider(acme.DNS01, provider)
if err != nil {
log.Fatal(err)
}
// --dns=foo indicates that the user specifically want to do a DNS challenge // --dns=foo indicates that the user specifically want to do a DNS challenge
// infer that the user also wants to exclude all other challenges // infer that the user also wants to exclude all other challenges
@ -124,7 +137,7 @@ func setup(c *cli.Context) (*Configuration, *Account, *acme.Client) {
} }
if client.GetExternalAccountRequired() && !c.GlobalIsSet("eab") { if client.GetExternalAccountRequired() && !c.GlobalIsSet("eab") {
logger().Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.") log.Fatal("Server requires External Account Binding. Use --eab with --kid and --hmac.")
} }
return conf, acc, client return conf, acc, client
@ -144,13 +157,13 @@ func saveCertRes(certRes *acme.CertificateResource, conf *Configuration) {
err := ioutil.WriteFile(certOut, certRes.Certificate, 0600) err := ioutil.WriteFile(certOut, certRes.Certificate, 0600)
if err != nil { if err != nil {
log.Fatalf("Unable to save Certificate for domain %s\n\t%s", certRes.Domain, err.Error()) log.Fatalf("Unable to save Certificate for domain %s\n\t%v", certRes.Domain, err)
} }
if certRes.IssuerCertificate != nil { if certRes.IssuerCertificate != nil {
err = ioutil.WriteFile(issuerOut, certRes.IssuerCertificate, 0600) err = ioutil.WriteFile(issuerOut, certRes.IssuerCertificate, 0600)
if err != nil { if err != nil {
log.Fatalf("Unable to save IssuerCertificate for domain %s\n\t%s", certRes.Domain, err.Error()) log.Fatalf("Unable to save IssuerCertificate for domain %s\n\t%v", certRes.Domain, err)
} }
} }
@ -158,29 +171,29 @@ func saveCertRes(certRes *acme.CertificateResource, conf *Configuration) {
// if we were given a CSR, we don't know the private key // if we were given a CSR, we don't know the private key
err = ioutil.WriteFile(privOut, certRes.PrivateKey, 0600) err = ioutil.WriteFile(privOut, certRes.PrivateKey, 0600)
if err != nil { if err != nil {
log.Fatalf("Unable to save PrivateKey for domain %s\n\t%s", certRes.Domain, err.Error()) log.Fatalf("Unable to save PrivateKey for domain %s\n\t%v", certRes.Domain, err)
} }
if conf.context.GlobalBool("pem") { if conf.context.GlobalBool("pem") {
err = ioutil.WriteFile(pemOut, bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil), 0600) err = ioutil.WriteFile(pemOut, bytes.Join([][]byte{certRes.Certificate, certRes.PrivateKey}, nil), 0600)
if err != nil { if err != nil {
log.Fatalf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%s", certRes.Domain, err.Error()) log.Fatalf("Unable to save Certificate and PrivateKey in .pem for domain %s\n\t%v", certRes.Domain, err)
} }
} }
} else if conf.context.GlobalBool("pem") { } else if conf.context.GlobalBool("pem") {
// we don't have the private key; can't write the .pem file // we don't have the private key; can't write the .pem file
log.Fatalf("Unable to save pem without private key for domain %s\n\t%s; are you using a CSR?", certRes.Domain, err.Error()) log.Fatalf("Unable to save pem without private key for domain %s\n\t%v; are you using a CSR?", certRes.Domain, err)
} }
jsonBytes, err := json.MarshalIndent(certRes, "", "\t") jsonBytes, err := json.MarshalIndent(certRes, "", "\t")
if err != nil { if err != nil {
log.Fatalf("Unable to marshal CertResource for domain %s\n\t%s", certRes.Domain, err.Error()) log.Fatalf("Unable to marshal CertResource for domain %s\n\t%v", certRes.Domain, err)
} }
err = ioutil.WriteFile(metaOut, jsonBytes, 0600) err = ioutil.WriteFile(metaOut, jsonBytes, 0600)
if err != nil { if err != nil {
log.Fatalf("Unable to save CertResource for domain %s\n\t%s", certRes.Domain, err.Error()) log.Fatalf("Unable to save CertResource for domain %s\n\t%v", certRes.Domain, err)
} }
} }
@ -197,7 +210,7 @@ func handleTOS(c *cli.Context, client *acme.Client) bool {
log.Println("Do you accept the TOS? Y/n") log.Println("Do you accept the TOS? Y/n")
text, err := reader.ReadString('\n') text, err := reader.ReadString('\n')
if err != nil { if err != nil {
log.Fatalf("Could not read from console: %s", err.Error()) log.Fatalf("Could not read from console: %v", err)
} }
text = strings.Trim(text, "\r\n") text = strings.Trim(text, "\r\n")
@ -262,7 +275,7 @@ func run(c *cli.Context) error {
hmacEncoded := c.GlobalString("hmac") hmacEncoded := c.GlobalString("hmac")
if kid == "" || hmacEncoded == "" { if kid == "" || hmacEncoded == "" {
logger().Fatalf("Requires arguments --kid and --hmac.") log.Fatalf("Requires arguments --kid and --hmac.")
} }
reg, err = client.RegisterWithExternalAccountBinding( reg, err = client.RegisterWithExternalAccountBinding(
@ -275,7 +288,7 @@ func run(c *cli.Context) error {
} }
if err != nil { if err != nil {
log.Fatalf("Could not complete registration\n\t%s", err.Error()) log.Fatalf("Could not complete registration\n\t%v", err)
} }
acc.Registration = reg acc.Registration = reg
@ -317,7 +330,7 @@ func run(c *cli.Context) error {
} }
if err != nil { if err != nil {
log.Printf("Could not obtain certificates\n\t%s", err.Error()) log.Printf("Could not obtain certificates\n\t%v", err)
// Make sure to return a non-zero exit code if ObtainSANCertificate // Make sure to return a non-zero exit code if ObtainSANCertificate
// returned at least one error. Due to us not returning partial // returned at least one error. Due to us not returning partial
@ -326,7 +339,7 @@ func run(c *cli.Context) error {
} }
if err = checkFolder(conf.CertPath()); err != nil { if err = checkFolder(conf.CertPath()); err != nil {
log.Fatalf("Could not check/create path: %s", err.Error()) log.Fatalf("Could not check/create path: %v", err)
} }
saveCertRes(cert, conf) saveCertRes(cert, conf)
@ -341,7 +354,7 @@ func revoke(c *cli.Context) error {
} }
if err := checkFolder(conf.CertPath()); err != nil { if err := checkFolder(conf.CertPath()); err != nil {
log.Fatalf("Could not check/create path: %s", err.Error()) log.Fatalf("Could not check/create path: %v", err)
} }
for _, domain := range c.GlobalStringSlice("domains") { for _, domain := range c.GlobalStringSlice("domains") {
@ -349,12 +362,15 @@ func revoke(c *cli.Context) error {
certPath := path.Join(conf.CertPath(), domain+".crt") certPath := path.Join(conf.CertPath(), domain+".crt")
certBytes, err := ioutil.ReadFile(certPath) certBytes, err := ioutil.ReadFile(certPath)
if err != nil {
log.Println(err)
}
err = client.RevokeCertificate(certBytes) err = client.RevokeCertificate(certBytes)
if err != nil { if err != nil {
log.Fatalf("Error while revoking the certificate for domain %s\n\t%s", domain, err.Error()) log.Fatalf("Error while revoking the certificate for domain %s\n\t%v", domain, err)
} else { } else {
log.Print("Certificate was revoked.") log.Println("Certificate was revoked.")
} }
} }
@ -383,7 +399,7 @@ func renew(c *cli.Context) error {
certBytes, err := ioutil.ReadFile(certPath) certBytes, err := ioutil.ReadFile(certPath)
if err != nil { if err != nil {
log.Fatalf("Error while loading the certificate for domain %s\n\t%s", domain, err.Error()) log.Fatalf("Error while loading the certificate for domain %s\n\t%v", domain, err)
} }
if c.IsSet("days") { if c.IsSet("days") {
@ -399,18 +415,18 @@ func renew(c *cli.Context) error {
metaBytes, err := ioutil.ReadFile(metaPath) metaBytes, err := ioutil.ReadFile(metaPath)
if err != nil { if err != nil {
log.Fatalf("Error while loading the meta data for domain %s\n\t%s", domain, err.Error()) log.Fatalf("Error while loading the meta data for domain %s\n\t%v", domain, err)
} }
var certRes acme.CertificateResource var certRes acme.CertificateResource
if err := json.Unmarshal(metaBytes, &certRes); err != nil { if err := json.Unmarshal(metaBytes, &certRes); err != nil {
log.Fatalf("Error while marshalling the meta data for domain %s\n\t%s", domain, err.Error()) log.Fatalf("Error while marshalling the meta data for domain %s\n\t%v", domain, err)
} }
if c.Bool("reuse-key") { if c.Bool("reuse-key") {
keyBytes, err := ioutil.ReadFile(privPath) keyBytes, err := ioutil.ReadFile(privPath)
if err != nil { if err != nil {
log.Fatalf("Error while loading the private key for domain %s\n\t%s", domain, err.Error()) log.Fatalf("Error while loading the private key for domain %s\n\t%v", domain, err)
} }
certRes.PrivateKey = keyBytes certRes.PrivateKey = keyBytes
} }

View file

@ -52,5 +52,5 @@ func loadPrivateKey(file string) (crypto.PrivateKey, error) {
return x509.ParseECPrivateKey(keyBlock.Bytes) return x509.ParseECPrivateKey(keyBlock.Bytes)
} }
return nil, errors.New("Unknown private key type.") return nil, errors.New("unknown private key type")
} }

View file

@ -2,12 +2,13 @@ package auroradns
import ( import (
"fmt" "fmt"
"os"
"sync"
"github.com/edeckers/auroradnsclient" "github.com/edeckers/auroradnsclient"
"github.com/edeckers/auroradnsclient/records" "github.com/edeckers/auroradnsclient/records"
"github.com/edeckers/auroradnsclient/zones" "github.com/edeckers/auroradnsclient/zones"
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"os"
"sync"
) )
// DNSProvider describes a provider for AuroraDNS // DNSProvider describes a provider for AuroraDNS
@ -59,7 +60,7 @@ func (provider *DNSProvider) getZoneInformationByName(name string) (zones.ZoneRe
} }
} }
return zones.ZoneRecord{}, fmt.Errorf("Could not find Zone record") return zones.ZoneRecord{}, fmt.Errorf("could not find Zone record")
} }
// Present creates a record with a secret // Present creates a record with a secret
@ -83,6 +84,9 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
authZone = acme.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
zoneRecord, err := provider.getZoneInformationByName(authZone) zoneRecord, err := provider.getZoneInformationByName(authZone)
if err != nil {
return fmt.Errorf("could not create record: %v", err)
}
reqData := reqData :=
records.CreateRecordRequest{ records.CreateRecordRequest{
@ -94,7 +98,7 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData) respData, err := provider.client.CreateRecord(zoneRecord.ID, reqData)
if err != nil { if err != nil {
return fmt.Errorf("Could not create record: '%s'.", err) return fmt.Errorf("could not create record: %v", err)
} }
provider.recordIDsMu.Lock() provider.recordIDsMu.Lock()
@ -113,12 +117,12 @@ func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error {
provider.recordIDsMu.Unlock() provider.recordIDsMu.Unlock()
if !ok { if !ok {
return fmt.Errorf("Unknown recordID for '%s'", fqdn) return fmt.Errorf("unknown recordID for %q", fqdn)
} }
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) return fmt.Errorf("could not determine zone for domain: %q. %v", domain, err)
} }
authZone = acme.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)

View file

@ -8,7 +8,7 @@ import (
"testing" "testing"
) )
var fakeAuroraDNSUserId = "asdf1234" var fakeAuroraDNSUserID = "asdf1234"
var fakeAuroraDNSKey = "key" var fakeAuroraDNSKey = "key"
func TestAuroraDNSPresent(t *testing.T) { func TestAuroraDNSPresent(t *testing.T) {
@ -60,7 +60,7 @@ func TestAuroraDNSPresent(t *testing.T) {
defer mock.Close() defer mock.Close()
auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserId, fakeAuroraDNSKey) auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserID, fakeAuroraDNSKey)
if auroraProvider == nil { if auroraProvider == nil {
t.Fatal("Expected non-nil AuroraDNS provider, but was nil") t.Fatal("Expected non-nil AuroraDNS provider, but was nil")
} }
@ -123,7 +123,7 @@ func TestAuroraDNSCleanUp(t *testing.T) {
})) }))
defer mock.Close() defer mock.Close()
auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserId, fakeAuroraDNSKey) auroraProvider, err := NewDNSProviderCredentials(mock.URL, fakeAuroraDNSUserID, fakeAuroraDNSKey)
if auroraProvider == nil { if auroraProvider == nil {
t.Fatal("Expected non-nil AuroraDNS provider, but was nil") t.Fatal("Expected non-nil AuroraDNS provider, but was nil")
} }

View file

@ -20,10 +20,10 @@ import (
// DNSProvider is an implementation of the acme.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
clientId string clientID string
clientSecret string clientSecret string
subscriptionId string subscriptionID string
tenantId string tenantID string
resourceGroup string resourceGroup string
context context.Context context context.Context
} }
@ -32,19 +32,19 @@ type DNSProvider struct {
// Credentials must be passed in the environment variables: AZURE_CLIENT_ID, // Credentials must be passed in the environment variables: AZURE_CLIENT_ID,
// AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP // AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
clientId := os.Getenv("AZURE_CLIENT_ID") clientID := os.Getenv("AZURE_CLIENT_ID")
clientSecret := os.Getenv("AZURE_CLIENT_SECRET") clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID") subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID")
tenantId := os.Getenv("AZURE_TENANT_ID") tenantID := os.Getenv("AZURE_TENANT_ID")
resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP") resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP")
return NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup) return NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup)
} }
// NewDNSProviderCredentials uses the supplied credentials to return a // NewDNSProviderCredentials uses the supplied credentials to return a
// DNSProvider instance configured for azure. // DNSProvider instance configured for azure.
func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup string) (*DNSProvider, error) { func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup string) (*DNSProvider, error) {
if clientId == "" || clientSecret == "" || subscriptionId == "" || tenantId == "" || resourceGroup == "" { if clientID == "" || clientSecret == "" || subscriptionID == "" || tenantID == "" || resourceGroup == "" {
missingEnvVars := []string{} var missingEnvVars []string
for _, envVar := range []string{"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP"} { for _, envVar := range []string{"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP"} {
if os.Getenv(envVar) == "" { if os.Getenv(envVar) == "" {
missingEnvVars = append(missingEnvVars, envVar) missingEnvVars = append(missingEnvVars, envVar)
@ -54,10 +54,10 @@ func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId,
} }
return &DNSProvider{ return &DNSProvider{
clientId: clientId, clientID: clientID,
clientSecret: clientSecret, clientSecret: clientSecret,
subscriptionId: subscriptionId, subscriptionID: subscriptionID,
tenantId: tenantId, tenantID: tenantID,
resourceGroup: resourceGroup, resourceGroup: resourceGroup,
// TODO: A timeout can be added here for cancellation purposes. // TODO: A timeout can be added here for cancellation purposes.
context: context.Background(), context: context.Background(),
@ -78,8 +78,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
return err return err
} }
rsc := dns.NewRecordSetsClient(c.subscriptionId) rsc := dns.NewRecordSetsClient(c.subscriptionID)
spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
if err != nil {
return err
}
rsc.Authorizer = autorest.NewBearerAuthorizer(spt) rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone)) relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
@ -87,16 +91,12 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
Name: &relative, Name: &relative,
RecordSetProperties: &dns.RecordSetProperties{ RecordSetProperties: &dns.RecordSetProperties{
TTL: to.Int64Ptr(60), TTL: to.Int64Ptr(60),
TxtRecords: &[]dns.TxtRecord{dns.TxtRecord{Value: &[]string{value}}}, TxtRecords: &[]dns.TxtRecord{{Value: &[]string{value}}},
}, },
} }
_, err = rsc.CreateOrUpdate(c.context, c.resourceGroup, zone, relative, dns.TXT, rec, "", "") _, err = rsc.CreateOrUpdate(c.context, c.resourceGroup, zone, relative, dns.TXT, rec, "", "")
return err
if err != nil {
return err
}
return nil
} }
// Returns the relative record to the domain // Returns the relative record to the domain
@ -114,15 +114,16 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone)) relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
rsc := dns.NewRecordSetsClient(c.subscriptionId) rsc := dns.NewRecordSetsClient(c.subscriptionID)
spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
_, err = rsc.Delete(c.context, c.resourceGroup, zone, relative, dns.TXT, "")
if err != nil { if err != nil {
return err return err
} }
return nil rsc.Authorizer = autorest.NewBearerAuthorizer(spt)
_, err = rsc.Delete(c.context, c.resourceGroup, zone, relative, dns.TXT, "")
return err
} }
// Checks that azure has a zone for this domain name. // Checks that azure has a zone for this domain name.
@ -134,12 +135,14 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
// Now we want to to Azure and get the zone. // Now we want to to Azure and get the zone.
spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint) spt, err := c.newServicePrincipalTokenFromCredentials(azure.PublicCloud.ResourceManagerEndpoint)
if err != nil {
return "", err
}
dc := dns.NewZonesClient(c.subscriptionId) dc := dns.NewZonesClient(c.subscriptionID)
dc.Authorizer = autorest.NewBearerAuthorizer(spt) dc.Authorizer = autorest.NewBearerAuthorizer(spt)
zone, err := dc.Get(c.context, c.resourceGroup, acme.UnFqdn(authZone)) zone, err := dc.Get(c.context, c.resourceGroup, acme.UnFqdn(authZone))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -151,9 +154,9 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
// NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the // NewServicePrincipalTokenFromCredentials creates a new ServicePrincipalToken using values of the
// passed credentials map. // passed credentials map.
func (c *DNSProvider) newServicePrincipalTokenFromCredentials(scope string) (*adal.ServicePrincipalToken, error) { func (c *DNSProvider) newServicePrincipalTokenFromCredentials(scope string) (*adal.ServicePrincipalToken, error) {
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, c.tenantId) oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, c.tenantID)
if err != nil { if err != nil {
panic(err) return nil, err
} }
return adal.NewServicePrincipalToken(*oauthConfig, c.clientId, c.clientSecret, scope) return adal.NewServicePrincipalToken(*oauthConfig, c.clientID, c.clientSecret, scope)
} }

View file

@ -13,18 +13,19 @@ import (
"strings" "strings"
"time" "time"
"github.com/xenolf/lego/acme"
"io/ioutil" "io/ioutil"
"github.com/xenolf/lego/acme"
) )
const bluecatUrlTemplate = "%s/Services/REST/v1" const bluecatURLTemplate = "%s/Services/REST/v1"
const configType = "Configuration" const configType = "Configuration"
const viewType = "View" const viewType = "View"
const txtType = "TXTRecord" const txtType = "TXTRecord"
const zoneType = "Zone" const zoneType = "Zone"
type entityResponse struct { type entityResponse struct {
Id uint `json:"id"` ID uint `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Properties string `json:"properties"` Properties string `json:"properties"`
@ -33,7 +34,7 @@ type entityResponse struct {
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
// Bluecat's Address Manager REST API to manage TXT records for a domain. // Bluecat's Address Manager REST API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
baseUrl string baseURL string
userName string userName string
password string password string
configName string configName string
@ -55,7 +56,7 @@ func NewDNSProvider() (*DNSProvider, error) {
password := os.Getenv("BLUECAT_PASSWORD") password := os.Getenv("BLUECAT_PASSWORD")
configName := os.Getenv("BLUECAT_CONFIG_NAME") configName := os.Getenv("BLUECAT_CONFIG_NAME")
dnsView := os.Getenv("BLUECAT_DNS_VIEW") dnsView := os.Getenv("BLUECAT_DNS_VIEW")
httpClient := http.Client{Timeout: time.Duration(30 * time.Second)} httpClient := http.Client{Timeout: 30 * time.Second}
return NewDNSProviderCredentials(server, userName, password, configName, dnsView, httpClient) return NewDNSProviderCredentials(server, userName, password, configName, dnsView, httpClient)
} }
@ -67,7 +68,7 @@ func NewDNSProviderCredentials(server, userName, password, configName, dnsView s
} }
return &DNSProvider{ return &DNSProvider{
baseUrl: fmt.Sprintf(bluecatUrlTemplate, server), baseURL: fmt.Sprintf(bluecatURLTemplate, server),
userName: userName, userName: userName,
password: password, password: password,
configName: configName, configName: configName,
@ -79,7 +80,7 @@ func NewDNSProviderCredentials(server, userName, password, configName, dnsView s
// Send a REST request, using query parameters specified. The Authorization // Send a REST request, using query parameters specified. The Authorization
// header will be set if we have an active auth token // header will be set if we have an active auth token
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}, queryArgs map[string]string) (*http.Response, error) { func (d *DNSProvider) sendRequest(method, resource string, payload interface{}, queryArgs map[string]string) (*http.Response, error) {
url := fmt.Sprintf("%s/%s", d.baseUrl, resource) url := fmt.Sprintf("%s/%s", d.baseURL, resource)
body, err := json.Marshal(payload) body, err := json.Marshal(payload)
if err != nil { if err != nil {
@ -159,14 +160,14 @@ func (d *DNSProvider) logout() error {
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return fmt.Errorf("Bluecat API request failed to delete session with HTTP status code %d", resp.StatusCode) return fmt.Errorf("Bluecat API request failed to delete session with HTTP status code %d", resp.StatusCode)
} else { }
authBytes, _ := ioutil.ReadAll(resp.Body)
authResp := string(authBytes)
if !strings.Contains(authResp, "successfully") { authBytes, _ := ioutil.ReadAll(resp.Body)
msg := strings.Trim(authResp, "\"") authResp := string(authBytes)
return fmt.Errorf("Bluecat API request failed to delete session: %s", msg)
} if !strings.Contains(authResp, "successfully") {
msg := strings.Trim(authResp, "\"")
return fmt.Errorf("Bluecat API request failed to delete session: %s", msg)
} }
d.token = "" d.token = ""
@ -175,7 +176,7 @@ func (d *DNSProvider) logout() error {
} }
// Lookup the entity ID of the configuration named in our properties // Lookup the entity ID of the configuration named in our properties
func (d *DNSProvider) lookupConfId() (uint, error) { func (d *DNSProvider) lookupConfID() (uint, error) {
queryArgs := map[string]string{ queryArgs := map[string]string{
"parentId": strconv.Itoa(0), "parentId": strconv.Itoa(0),
"name": d.configName, "name": d.configName,
@ -193,18 +194,18 @@ func (d *DNSProvider) lookupConfId() (uint, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
return conf.Id, nil return conf.ID, nil
} }
// Find the DNS view with the given name within // Find the DNS view with the given name within
func (d *DNSProvider) lookupViewId(viewName string) (uint, error) { func (d *DNSProvider) lookupViewID(viewName string) (uint, error) {
confId, err := d.lookupConfId() confID, err := d.lookupConfID()
if err != nil { if err != nil {
return 0, err return 0, err
} }
queryArgs := map[string]string{ queryArgs := map[string]string{
"parentId": strconv.FormatUint(uint64(confId), 10), "parentId": strconv.FormatUint(uint64(confID), 10),
"name": d.dnsView, "name": d.dnsView,
"type": viewType, "type": viewType,
} }
@ -221,13 +222,13 @@ func (d *DNSProvider) lookupViewId(viewName string) (uint, error) {
return 0, err return 0, err
} }
return view.Id, nil return view.ID, nil
} }
// Return the entityId of the parent zone by recursing from the root view // Return the entityId of the parent zone by recursing from the root view
// Also return the simple name of the host // Also return the simple name of the host
func (d *DNSProvider) lookupParentZoneId(viewId uint, fqdn string) (uint, string, error) { func (d *DNSProvider) lookupParentZoneID(viewID uint, fqdn string) (uint, string, error) {
parentViewId := viewId parentViewID := viewID
name := "" name := ""
if fqdn != "" { if fqdn != "" {
@ -236,25 +237,24 @@ func (d *DNSProvider) lookupParentZoneId(viewId uint, fqdn string) (uint, string
name = zones[0] name = zones[0]
for i := last; i > -1; i-- { for i := last; i > -1; i-- {
zoneId, err := d.getZone(parentViewId, zones[i]) zoneID, err := d.getZone(parentViewID, zones[i])
if err != nil || zoneId == 0 { if err != nil || zoneID == 0 {
return parentViewId, name, err return parentViewID, name, err
} }
if (i > 0) { if i > 0 {
name = strings.Join(zones[0:i],".") name = strings.Join(zones[0:i], ".")
} }
parentViewId = zoneId parentViewID = zoneID
} }
} }
return parentViewId, name, nil return parentViewID, name, nil
} }
// Get the DNS zone with the specified name under the parentId // Get the DNS zone with the specified name under the parentId
func (d *DNSProvider) getZone(parentId uint, name string) (uint, error) { func (d *DNSProvider) getZone(parentID uint, name string) (uint, error) {
queryArgs := map[string]string{ queryArgs := map[string]string{
"parentId": strconv.FormatUint(uint64(parentId), 10), "parentId": strconv.FormatUint(uint64(parentID), 10),
"name": name, "name": name,
"type": zoneType, "type": zoneType,
} }
@ -275,7 +275,7 @@ func (d *DNSProvider) getZone(parentId uint, name string) (uint, error) {
return 0, err return 0, err
} }
return zone.Id, nil return zone.ID, nil
} }
// Present creates a TXT record using the specified parameters // Present creates a TXT record using the specified parameters
@ -289,21 +289,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return err return err
} }
viewId, err := d.lookupViewId(d.dnsView) viewID, err := d.lookupViewID(d.dnsView)
if err != nil { if err != nil {
return err return err
} }
parentZoneId, name, err := d.lookupParentZoneId(viewId, fqdn) parentZoneID, name, err := d.lookupParentZoneID(viewID, fqdn)
if err != nil {
return err
}
queryArgs := map[string]string{ queryArgs := map[string]string{
"parentId": strconv.FormatUint(uint64(parentZoneId), 10), "parentId": strconv.FormatUint(uint64(parentZoneID), 10),
} }
body := bluecatEntity{ body := bluecatEntity{
Name: name, Name: name,
Type: "TXTRecord", Type: "TXTRecord",
Properties: fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", ttl, fqdn, value), Properties: fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", ttl, fqdn, value),
} }
resp, err := d.sendRequest("POST", "addEntity", body, queryArgs) resp, err := d.sendRequest("POST", "addEntity", body, queryArgs)
@ -321,23 +324,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return fmt.Errorf("Bluecat API addEntity request failed: %s", addTxtResp) return fmt.Errorf("Bluecat API addEntity request failed: %s", addTxtResp)
} }
err = d.deploy(uint(parentZoneId)) err = d.deploy(parentZoneID)
if err != nil { if err != nil {
return err return err
} }
err = d.logout() return d.logout()
if err != nil {
return err
}
return nil
} }
// Deploy the DNS config for the specified entity to the authoritative servers // Deploy the DNS config for the specified entity to the authoritative servers
func (d *DNSProvider) deploy(entityId uint) error { func (d *DNSProvider) deploy(entityID uint) error {
queryArgs := map[string]string{ queryArgs := map[string]string{
"entityId": strconv.FormatUint(uint64(entityId), 10), "entityId": strconv.FormatUint(uint64(entityID), 10),
} }
resp, err := d.sendRequest("POST", "quickDeploy", nil, queryArgs) resp, err := d.sendRequest("POST", "quickDeploy", nil, queryArgs)
@ -359,18 +357,18 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return err return err
} }
viewId, err := d.lookupViewId(d.dnsView) viewID, err := d.lookupViewID(d.dnsView)
if err != nil { if err != nil {
return err return err
} }
parentId, name, err := d.lookupParentZoneId(viewId, fqdn) parentID, name, err := d.lookupParentZoneID(viewID, fqdn)
if err != nil { if err != nil {
return err return err
} }
queryArgs := map[string]string{ queryArgs := map[string]string{
"parentId": strconv.FormatUint(uint64(parentId), 10), "parentId": strconv.FormatUint(uint64(parentID), 10),
"name": name, "name": name,
"type": txtType, "type": txtType,
} }
@ -387,7 +385,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return err return err
} }
queryArgs = map[string]string{ queryArgs = map[string]string{
"objectId": strconv.FormatUint(uint64(txtRec.Id), 10), "objectId": strconv.FormatUint(uint64(txtRec.ID), 10),
} }
resp, err = d.sendRequest("DELETE", "delete", nil, queryArgs) resp, err = d.sendRequest("DELETE", "delete", nil, queryArgs)
@ -396,23 +394,18 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
defer resp.Body.Close() defer resp.Body.Close()
err = d.deploy(parentId) err = d.deploy(parentID)
if err != nil { if err != nil {
return err return err
} }
err = d.logout() return d.logout()
if err != nil {
return err
}
return nil
} }
//JSON body for Bluecat entity requests and responses // JSON body for Bluecat entity requests and responses
type bluecatEntity struct { type bluecatEntity struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Properties string `json:"properties"` Properties string `json:"properties"`
} }

View file

@ -4,8 +4,9 @@ import (
"os" "os"
"testing" "testing"
"github.com/stretchr/testify/assert"
"time" "time"
"github.com/stretchr/testify/assert"
) )
var ( var (
@ -14,7 +15,7 @@ var (
bluecatUserName string bluecatUserName string
bluecatPassword string bluecatPassword string
bluecatConfigName string bluecatConfigName string
bluecatDnsView string bluecatDNSView string
bluecatDomain string bluecatDomain string
) )
@ -24,8 +25,13 @@ func init() {
bluecatPassword = os.Getenv("BLUECAT_PASSWORD") bluecatPassword = os.Getenv("BLUECAT_PASSWORD")
bluecatDomain = os.Getenv("BLUECAT_DOMAIN") bluecatDomain = os.Getenv("BLUECAT_DOMAIN")
bluecatConfigName = os.Getenv("BLUECAT_CONFIG_NAME") bluecatConfigName = os.Getenv("BLUECAT_CONFIG_NAME")
bluecatDnsView = os.Getenv("BLUECAT_DNS_VIEW") bluecatDNSView = os.Getenv("BLUECAT_DNS_VIEW")
if len(bluecatServer) > 0 && len(bluecatDomain) > 0 && len(bluecatUserName) > 0 && len(bluecatPassword) > 0 && len(bluecatConfigName) > 0 && len(bluecatDnsView) > 0 { if len(bluecatServer) > 0 &&
len(bluecatDomain) > 0 &&
len(bluecatUserName) > 0 &&
len(bluecatPassword) > 0 &&
len(bluecatConfigName) > 0 &&
len(bluecatDNSView) > 0 {
bluecatLiveTest = true bluecatLiveTest = true
} }
} }

View file

@ -81,11 +81,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
} }
_, err = c.makeRequest("POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body)) _, err = c.makeRequest("POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body))
if err != nil { return err
return err
}
return nil
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
@ -98,11 +94,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
_, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil) _, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil)
if err != nil { return err
return err
}
return nil
} }
func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
@ -162,7 +154,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
} }
} }
return nil, fmt.Errorf("No existing record found for %s", fqdn) return nil, fmt.Errorf("no existing record found for %s", fqdn)
} }
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) { func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) {
@ -187,7 +179,6 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
req.Header.Set("X-Auth-Email", c.authEmail) req.Header.Set("X-Auth-Email", c.authEmail)
req.Header.Set("X-Auth-Key", c.authKey) req.Header.Set("X-Auth-Key", c.authKey)
//req.Header.Set("User-Agent", userAgent())
client := http.Client{Timeout: 30 * time.Second} client := http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)

View file

@ -101,7 +101,7 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
} }
} }
return "", fmt.Errorf("Zone %s not found in cloudxns for domain %s", authZone, fqdn) return "", fmt.Errorf("zone %s not found in cloudxns for domain %s", authZone, fqdn)
} }
func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) { func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) {
@ -122,7 +122,7 @@ func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) {
} }
} }
return "", fmt.Errorf("No existing record found for %s", fqdn) return "", fmt.Errorf("no existing record found for %s", fqdn)
} }
func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error { func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error {
@ -146,11 +146,7 @@ func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error {
} }
_, err = c.makeRequest("POST", "record", body) _, err = c.makeRequest("POST", "record", body)
if err != nil { return err
return err
}
return nil
} }
func (c *DNSProvider) delTxtRecord(recordID, zoneID string) error { func (c *DNSProvider) delTxtRecord(recordID, zoneID string) error {

View file

@ -22,6 +22,12 @@ type DNSProvider struct {
recordIDsMu sync.Mutex recordIDsMu sync.Mutex
} }
// Timeout returns the timeout and interval to use when checking for DNS
// propagation. Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return 60 * time.Second, 5 * time.Second
}
// NewDNSProvider returns a DNSProvider instance configured for Digital // NewDNSProvider returns a DNSProvider instance configured for Digital
// Ocean. Credentials must be passed in the environment variable: // Ocean. Credentials must be passed in the environment variable:
// DO_AUTH_TOKEN. // DO_AUTH_TOKEN.
@ -44,34 +50,17 @@ func NewDNSProviderCredentials(apiAuthToken string) (*DNSProvider, error) {
// Present creates a TXT record using the specified parameters // Present creates a TXT record using the specified parameters
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
// txtRecordRequest represents the request body to DO's API to make a TXT record
type txtRecordRequest struct {
RecordType string `json:"type"`
Name string `json:"name"`
Data string `json:"data"`
}
// txtRecordResponse represents a response from DO's API after making a TXT record
type txtRecordResponse struct {
DomainRecord struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Data string `json:"data"`
} `json:"domain_record"`
}
fqdn, value, _ := acme.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
} }
authZone = acme.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, authZone) reqURL := fmt.Sprintf("%s/v2/domains/%s/records", digitalOceanBaseURL, authZone)
reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value} reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value, TTL: 30}
body, err := json.Marshal(reqData) body, err := json.Marshal(reqData)
if err != nil { if err != nil {
return err return err
@ -124,7 +113,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return fmt.Errorf("Could not determine zone for domain: '%s'. %s", domain, err) return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
} }
authZone = acme.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
@ -164,3 +153,21 @@ type digitalOceanAPIError struct {
} }
var digitalOceanBaseURL = "https://api.digitalocean.com" var digitalOceanBaseURL = "https://api.digitalocean.com"
// txtRecordRequest represents the request body to DO's API to make a TXT record
type txtRecordRequest struct {
RecordType string `json:"type"`
Name string `json:"name"`
Data string `json:"data"`
TTL int `json:"ttl"`
}
// txtRecordResponse represents a response from DO's API after making a TXT record
type txtRecordResponse struct {
DomainRecord struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Data string `json:"data"`
} `json:"domain_record"`
}

View file

@ -33,7 +33,7 @@ func TestDigitalOceanPresent(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Error reading request body: %v", err) t.Fatalf("Error reading request body: %v", err)
} }
if got, want := string(reqBody), `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI"}`; got != want { if got, want := string(reqBody), `{"type":"TXT","name":"_acme-challenge.example.com.","data":"w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI","ttl":30}`; got != want {
t.Errorf("Expected body data to be: `%s` but got `%s`", want, got) t.Errorf("Expected body data to be: `%s` but got `%s`", want, got)
} }

View file

@ -1,4 +1,3 @@
// Factory for DNS providers
package dns package dns
import ( import (
@ -7,6 +6,7 @@ import (
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"github.com/xenolf/lego/providers/dns/auroradns" "github.com/xenolf/lego/providers/dns/auroradns"
"github.com/xenolf/lego/providers/dns/azure" "github.com/xenolf/lego/providers/dns/azure"
"github.com/xenolf/lego/providers/dns/bluecat"
"github.com/xenolf/lego/providers/dns/cloudflare" "github.com/xenolf/lego/providers/dns/cloudflare"
"github.com/xenolf/lego/providers/dns/cloudxns" "github.com/xenolf/lego/providers/dns/cloudxns"
"github.com/xenolf/lego/providers/dns/digitalocean" "github.com/xenolf/lego/providers/dns/digitalocean"
@ -35,9 +35,9 @@ import (
"github.com/xenolf/lego/providers/dns/rfc2136" "github.com/xenolf/lego/providers/dns/rfc2136"
"github.com/xenolf/lego/providers/dns/route53" "github.com/xenolf/lego/providers/dns/route53"
"github.com/xenolf/lego/providers/dns/vultr" "github.com/xenolf/lego/providers/dns/vultr"
"github.com/xenolf/lego/providers/dns/bluecat"
) )
// NewDNSChallengeProviderByName Factory for DNS providers
func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) { func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) {
var err error var err error
var provider acme.ChallengeProvider var provider acme.ChallengeProvider
@ -107,7 +107,7 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
case "exec": case "exec":
provider, err = exec.NewDNSProvider() provider, err = exec.NewDNSProvider()
default: default:
err = fmt.Errorf("Unrecognised DNS provider: %s", name) err = fmt.Errorf("unrecognised DNS provider: %s", name)
} }
return provider, err return provider, err
} }

View file

@ -23,14 +23,14 @@ type DNSProvider struct {
// See: https://developer.dnsimple.com/v2/#authentication // See: https://developer.dnsimple.com/v2/#authentication
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
accessToken := os.Getenv("DNSIMPLE_OAUTH_TOKEN") accessToken := os.Getenv("DNSIMPLE_OAUTH_TOKEN")
baseUrl := os.Getenv("DNSIMPLE_BASE_URL") baseURL := os.Getenv("DNSIMPLE_BASE_URL")
return NewDNSProviderCredentials(accessToken, baseUrl) return NewDNSProviderCredentials(accessToken, baseURL)
} }
// NewDNSProviderCredentials uses the supplied credentials to return a // NewDNSProviderCredentials uses the supplied credentials to return a
// DNSProvider instance configured for dnsimple. // DNSProvider instance configured for dnsimple.
func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error) { func NewDNSProviderCredentials(accessToken, baseURL string) (*DNSProvider, error) {
if accessToken == "" { if accessToken == "" {
return nil, fmt.Errorf("DNSimple OAuth token is missing") return nil, fmt.Errorf("DNSimple OAuth token is missing")
} }
@ -38,8 +38,8 @@ func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error
client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(accessToken)) client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(accessToken))
client.UserAgent = "lego" client.UserAgent = "lego"
if baseUrl != "" { if baseURL != "" {
client.BaseURL = baseUrl client.BaseURL = baseURL
} }
return &DNSProvider{client: client}, nil return &DNSProvider{client: client}, nil
@ -119,8 +119,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) {
} }
if hostedZone.ID == 0 { if hostedZone.ID == 0 {
return "", fmt.Errorf("Zone %s not found in DNSimple for domain %s", authZone, domain) return "", fmt.Errorf("zone %s not found in DNSimple for domain %s", authZone, domain)
} }
return hostedZone.Name, nil return hostedZone.Name, nil
@ -173,7 +172,7 @@ func (c *DNSProvider) getAccountID() (string, error) {
} }
if whoamiResponse.Data.Account == nil { if whoamiResponse.Data.Account == nil {
return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token.") return "", fmt.Errorf("DNSimple user tokens are not supported, please use an account token")
} }
return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil

View file

@ -12,19 +12,19 @@ var (
dnsimpleLiveTest bool dnsimpleLiveTest bool
dnsimpleOauthToken string dnsimpleOauthToken string
dnsimpleDomain string dnsimpleDomain string
dnsimpleBaseUrl string dnsimpleBaseURL string
) )
func init() { func init() {
dnsimpleOauthToken = os.Getenv("DNSIMPLE_OAUTH_TOKEN") dnsimpleOauthToken = os.Getenv("DNSIMPLE_OAUTH_TOKEN")
dnsimpleDomain = os.Getenv("DNSIMPLE_DOMAIN") dnsimpleDomain = os.Getenv("DNSIMPLE_DOMAIN")
dnsimpleBaseUrl = "https://api.sandbox.dnsimple.com" dnsimpleBaseURL = "https://api.sandbox.dnsimple.com"
if len(dnsimpleOauthToken) > 0 && len(dnsimpleDomain) > 0 { if len(dnsimpleOauthToken) > 0 && len(dnsimpleDomain) > 0 {
baseUrl := os.Getenv("DNSIMPLE_BASE_URL") baseURL := os.Getenv("DNSIMPLE_BASE_URL")
if baseUrl != "" { if baseURL != "" {
dnsimpleBaseUrl = baseUrl dnsimpleBaseURL = baseURL
} }
dnsimpleLiveTest = true dnsimpleLiveTest = true
@ -33,7 +33,7 @@ func init() {
func restoreDNSimpleEnv() { func restoreDNSimpleEnv() {
os.Setenv("DNSIMPLE_OAUTH_TOKEN", dnsimpleOauthToken) os.Setenv("DNSIMPLE_OAUTH_TOKEN", dnsimpleOauthToken)
os.Setenv("DNSIMPLE_BASE_URL", dnsimpleBaseUrl) os.Setenv("DNSIMPLE_BASE_URL", dnsimpleBaseURL)
} }
// //
@ -114,7 +114,7 @@ func TestLiveDNSimplePresent(t *testing.T) {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseUrl) provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseURL)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.Present(dnsimpleDomain, "", "123d==") err = provider.Present(dnsimpleDomain, "", "123d==")
@ -132,7 +132,7 @@ func TestLiveDNSimpleCleanUp(t *testing.T) {
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseUrl) provider, err := NewDNSProviderCredentials(dnsimpleOauthToken, dnsimpleBaseURL)
assert.NoError(t, err) assert.NoError(t, err)
err = provider.CleanUp(dnsimpleDomain, "", "123d==") err = provider.CleanUp(dnsimpleDomain, "", "123d==")

View file

@ -95,11 +95,7 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
record := &Record{Type: "TXT", Name: name, Value: value, TTL: ttl} record := &Record{Type: "TXT", Name: name, Value: value, TTL: ttl}
err = d.createRecord(domain, record) err = d.createRecord(domain, record)
if err != nil { return err
return err
}
return nil
} }
// CleanUp removes the TXT records matching the specified parameters // CleanUp removes the TXT records matching the specified parameters
@ -226,7 +222,7 @@ func (d *DNSProvider) sendRequest(method, resource string, payload interface{})
} }
client := &http.Client{ client := &http.Client{
Transport: transport, Transport: transport,
Timeout: time.Duration(10 * time.Second), Timeout: 10 * time.Second,
} }
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {

View file

@ -28,6 +28,7 @@ func TestPresentAndCleanup(t *testing.T) {
} }
provider, err := NewDNSProvider() provider, err := NewDNSProvider()
assert.NoError(t, err)
err = provider.Present(testDomain, "", "123d==") err = provider.Present(testDomain, "", "123d==")
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -1,82 +1,81 @@
// Adds lego support for http://duckdns.org . // Package duckdns Adds lego support for http://duckdns.org .
// // See http://www.duckdns.org/spec.jsp for more info on updating TXT records.
// See http://www.duckdns.org/spec.jsp for more info on updating TXT records. package duckdns
package duckdns
import (
import ( "errors"
"errors" "fmt"
"fmt" "io/ioutil"
"io/ioutil" "os"
"os"
"github.com/xenolf/lego/acme"
"github.com/xenolf/lego/acme" )
)
// DNSProvider adds and removes the record for the DNS challenge
// DNSProvider adds and removes the record for the DNS challenge type DNSProvider struct {
type DNSProvider struct { // The duckdns api token
// The duckdns api token token string
token string }
}
// NewDNSProvider returns a new DNS provider using
// NewDNSProvider returns a new DNS provider using // environment variable DUCKDNS_TOKEN for adding and removing the DNS record.
// environment variable DUCKDNS_TOKEN for adding and removing the DNS record. func NewDNSProvider() (*DNSProvider, error) {
func NewDNSProvider() (*DNSProvider, error) { duckdnsToken := os.Getenv("DUCKDNS_TOKEN")
duckdnsToken := os.Getenv("DUCKDNS_TOKEN")
return NewDNSProviderCredentials(duckdnsToken)
return NewDNSProviderCredentials(duckdnsToken) }
}
// NewDNSProviderCredentials uses the supplied credentials to return a
// NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for http://duckdns.org .
// DNSProvider instance configured for http://duckdns.org . func NewDNSProviderCredentials(duckdnsToken string) (*DNSProvider, error) {
func NewDNSProviderCredentials(duckdnsToken string) (*DNSProvider, error) { if duckdnsToken == "" {
if duckdnsToken == "" { return nil, errors.New("environment variable DUCKDNS_TOKEN not set")
return nil, errors.New("environment variable DUCKDNS_TOKEN not set") }
}
return &DNSProvider{token: duckdnsToken}, nil
return &DNSProvider{token: duckdnsToken}, nil }
}
// makeDuckdnsURL creates a url to clear the set or unset the TXT record.
// makeDuckdnsURL creates a url to clear the set or unset the TXT record. // txt == "" will clear the TXT record.
// txt == "" will clear the TXT record. func makeDuckdnsURL(domain, token, txt string) string {
func makeDuckdnsURL(domain, token, txt string) string { requestBase := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s", domain, token)
requestBase := fmt.Sprintf("https://www.duckdns.org/update?domains=%s&token=%s", domain, token) if txt == "" {
if txt == "" { return requestBase + "&clear=true"
return requestBase + "&clear=true" }
} return requestBase + "&txt=" + txt
return requestBase + "&txt=" + txt }
}
func issueDuckdnsRequest(url string) error {
func issueDuckdnsRequest(url string) error { response, err := acme.HTTPClient.Get(url)
response, err := acme.HTTPClient.Get(url) if err != nil {
if err != nil { return err
return err }
} defer response.Body.Close()
defer response.Body.Close()
bodyBytes, err := ioutil.ReadAll(response.Body)
bodyBytes, err := ioutil.ReadAll(response.Body) if err != nil {
if err != nil { return err
return err }
} body := string(bodyBytes)
body := string(bodyBytes) if body != "OK" {
if body != "OK" { return fmt.Errorf("Request to change TXT record for duckdns returned the following result (%s) this does not match expectation (OK) used url [%s]", body, url)
return fmt.Errorf("Request to change TXT record for duckdns returned the following result (%s) this does not match expectation (OK) used url [%s]", body, url) }
} return nil
return nil }
}
// Present creates a TXT record to fulfil the dns-01 challenge.
// Present creates a TXT record to fulfil the dns-01 challenge. // In duckdns you only have one TXT record shared with
// In duckdns you only have one TXT record shared with // the domain and all sub domains.
// the domain and all sub domains. //
// // To update the TXT record we just need to make one simple get request.
// To update the TXT record we just need to make one simple get request. func (d *DNSProvider) Present(domain, token, keyAuth string) error {
func (d *DNSProvider) Present(domain, token, keyAuth string) error { _, txtRecord, _ := acme.DNS01Record(domain, keyAuth)
_, txtRecord, _ := acme.DNS01Record(domain, keyAuth) url := makeDuckdnsURL(domain, d.token, txtRecord)
url := makeDuckdnsURL(domain, d.token, txtRecord) return issueDuckdnsRequest(url)
return issueDuckdnsRequest(url) }
}
// CleanUp clears duckdns TXT record
// CleanUp clears duckdns TXT record func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { url := makeDuckdnsURL(domain, d.token, "")
url := makeDuckdnsURL(domain, d.token, "") return issueDuckdnsRequest(url)
return issueDuckdnsRequest(url) }
}

View file

@ -1,65 +1,65 @@
package duckdns package duckdns
import ( import (
"os" "os"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var ( var (
duckdnsLiveTest bool duckdnsLiveTest bool
duckdnsToken string duckdnsToken string
duckdnsDomain string duckdnsDomain string
) )
func init() { func init() {
duckdnsToken = os.Getenv("DUCKDNS_TOKEN") duckdnsToken = os.Getenv("DUCKDNS_TOKEN")
duckdnsDomain = os.Getenv("DUCKDNS_DOMAIN") duckdnsDomain = os.Getenv("DUCKDNS_DOMAIN")
if len(duckdnsDomain) > 0 && len(duckdnsDomain) > 0 { if len(duckdnsToken) > 0 && len(duckdnsDomain) > 0 {
duckdnsLiveTest = true duckdnsLiveTest = true
} }
} }
func restoreDuckdnsEnv() { func restoreDuckdnsEnv() {
os.Setenv("DUCKDNS_TOKEN", duckdnsToken) os.Setenv("DUCKDNS_TOKEN", duckdnsToken)
} }
func TestNewDNSProviderValidEnv(t *testing.T) { func TestNewDNSProviderValidEnv(t *testing.T) {
os.Setenv("DUCKDNS_TOKEN", "123") os.Setenv("DUCKDNS_TOKEN", "123")
_, err := NewDNSProvider() _, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
restoreDuckdnsEnv() restoreDuckdnsEnv()
} }
func TestNewDNSProviderMissingCredErr(t *testing.T) { func TestNewDNSProviderMissingCredErr(t *testing.T) {
os.Setenv("DUCKDNS_TOKEN", "") os.Setenv("DUCKDNS_TOKEN", "")
_, err := NewDNSProvider() _, err := NewDNSProvider()
assert.EqualError(t, err, "environment variable DUCKDNS_TOKEN not set") assert.EqualError(t, err, "environment variable DUCKDNS_TOKEN not set")
restoreDuckdnsEnv() restoreDuckdnsEnv()
} }
func TestLiveDuckdnsPresent(t *testing.T) { func TestLiveDuckdnsPresent(t *testing.T) {
if !duckdnsLiveTest { if !duckdnsLiveTest {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
provider, err := NewDNSProvider() provider, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
err = provider.Present(duckdnsDomain, "", "123d==") err = provider.Present(duckdnsDomain, "", "123d==")
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestLiveDuckdnsCleanUp(t *testing.T) { func TestLiveDuckdnsCleanUp(t *testing.T) {
if !duckdnsLiveTest { if !duckdnsLiveTest {
t.Skip("skipping live test") t.Skip("skipping live test")
} }
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)
provider, err := NewDNSProvider() provider, err := NewDNSProvider()
assert.NoError(t, err) assert.NoError(t, err)
err = provider.CleanUp(duckdnsDomain, "", "123d==") err = provider.CleanUp(duckdnsDomain, "", "123d==")
assert.NoError(t, err) assert.NoError(t, err)
} }

View file

@ -80,7 +80,7 @@ func (d *DNSProvider) sendRequest(method, resource string, payload interface{})
req.Header.Set("Auth-Token", d.token) req.Header.Set("Auth-Token", d.token)
} }
client := &http.Client{Timeout: time.Duration(10 * time.Second)} client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -158,7 +158,7 @@ func (d *DNSProvider) logout() error {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("Auth-Token", d.token) req.Header.Set("Auth-Token", d.token)
client := &http.Client{Timeout: time.Duration(10 * time.Second)} client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
@ -206,12 +206,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
return err return err
} }
err = d.logout() return d.logout()
if err != nil {
return err
}
return nil
} }
func (d *DNSProvider) publish(zone, notes string) error { func (d *DNSProvider) publish(zone, notes string) error {
@ -222,12 +217,9 @@ func (d *DNSProvider) publish(zone, notes string) error {
pub := &publish{Publish: true, Notes: notes} pub := &publish{Publish: true, Notes: notes}
resource := fmt.Sprintf("Zone/%s/", zone) resource := fmt.Sprintf("Zone/%s/", zone)
_, err := d.sendRequest("PUT", resource, pub)
if err != nil {
return err
}
return nil _, err := d.sendRequest("PUT", resource, pub)
return err
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
@ -253,7 +245,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("Auth-Token", d.token) req.Header.Set("Auth-Token", d.token)
client := &http.Client{Timeout: time.Duration(10 * time.Second)} client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
@ -269,10 +261,5 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return err return err
} }
err = d.logout() return d.logout()
if err != nil {
return err
}
return nil
} }

View file

@ -16,7 +16,7 @@ type DNSProvider struct {
client *egoscale.Client client *egoscale.Client
} }
// Credentials must be passed in the environment variables: // NewDNSProvider Credentials must be passed in the environment variables:
// EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT. // EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT.
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
key := os.Getenv("EXOSCALE_API_KEY") key := os.Getenv("EXOSCALE_API_KEY")
@ -25,7 +25,7 @@ func NewDNSProvider() (*DNSProvider, error) {
return NewDNSProviderClient(key, secret, endpoint) return NewDNSProviderClient(key, secret, endpoint)
} }
// Uses the supplied parameters to return a DNSProvider instance // NewDNSProviderClient Uses the supplied parameters to return a DNSProvider instance
// configured for Exoscale. // configured for Exoscale.
func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) { func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) {
if key == "" || secret == "" { if key == "" || secret == "" {
@ -48,7 +48,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
return err return err
} }
recordID, err := c.FindExistingRecordId(zone, recordName) recordID, err := c.FindExistingRecordID(zone, recordName)
if err != nil { if err != nil {
return err return err
} }
@ -84,7 +84,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return err return err
} }
recordID, err := c.FindExistingRecordId(zone, recordName) recordID, err := c.FindExistingRecordID(zone, recordName)
if err != nil { if err != nil {
return err return err
} }
@ -99,9 +99,9 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return nil return nil
} }
// Query Exoscale to find an existing record for this name. // FindExistingRecordID Query Exoscale to find an existing record for this name.
// Returns nil if no record could be found // Returns nil if no record could be found
func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, error) { func (c *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, error) {
records, err := c.client.GetRecords(zone) records, err := c.client.GetRecords(zone)
if err != nil { if err != nil {
return -1, errors.New("Error while retrievening DNS records: " + err.Error()) return -1, errors.New("Error while retrievening DNS records: " + err.Error())
@ -114,7 +114,7 @@ func (c *DNSProvider) FindExistingRecordId(zone, recordName string) (int64, erro
return 0, nil return 0, nil
} }
// Extract DNS zone and DNS entry name // FindZoneAndRecordName Extract DNS zone and DNS entry name
func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) { func (c *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers) zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {

View file

@ -74,15 +74,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
if ttl < 300 { if ttl < 300 {
ttl = 300 // 300 is gandi minimum value for ttl ttl = 300 // 300 is gandi minimum value for ttl
} }
// find authZone and Gandi zone_id for fqdn // find authZone and Gandi zone_id for fqdn
authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers) authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err) return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err)
} }
zoneID, err := d.getZoneID(authZone) zoneID, err := d.getZoneID(authZone)
if err != nil { if err != nil {
return err return err
} }
// determine name of TXT record // determine name of TXT record
if !strings.HasSuffix( if !strings.HasSuffix(
strings.ToLower(fqdn), strings.ToLower("."+authZone)) { strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
@ -90,40 +93,49 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
"Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn) "Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
} }
name := fqdn[:len(fqdn)-len("."+authZone)] name := fqdn[:len(fqdn)-len("."+authZone)]
// acquire lock and check there is not a challenge already in // acquire lock and check there is not a challenge already in
// progress for this value of authZone // progress for this value of authZone
d.inProgressMu.Lock() d.inProgressMu.Lock()
defer d.inProgressMu.Unlock() defer d.inProgressMu.Unlock()
if _, ok := d.inProgressAuthZones[authZone]; ok { if _, ok := d.inProgressAuthZones[authZone]; ok {
return fmt.Errorf( return fmt.Errorf(
"Gandi DNS: challenge already in progress for authZone %s", "Gandi DNS: challenge already in progress for authZone %s",
authZone) authZone)
} }
// perform API actions to create and activate new gandi zone // perform API actions to create and activate new gandi zone
// containing the required TXT record // containing the required TXT record
newZoneName := fmt.Sprintf( newZoneName := fmt.Sprintf(
"%s [ACME Challenge %s]", "%s [ACME Challenge %s]",
acme.UnFqdn(authZone), time.Now().Format(time.RFC822Z)) acme.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
newZoneID, err := d.cloneZone(zoneID, newZoneName) newZoneID, err := d.cloneZone(zoneID, newZoneName)
if err != nil { if err != nil {
return err return err
} }
newZoneVersion, err := d.newZoneVersion(newZoneID) newZoneVersion, err := d.newZoneVersion(newZoneID)
if err != nil { if err != nil {
return err return err
} }
err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, ttl) err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, ttl)
if err != nil { if err != nil {
return err return err
} }
err = d.setZoneVersion(newZoneID, newZoneVersion) err = d.setZoneVersion(newZoneID, newZoneVersion)
if err != nil { if err != nil {
return err return err
} }
err = d.setZone(authZone, newZoneID) err = d.setZone(authZone, newZoneID)
if err != nil { if err != nil {
return err return err
} }
// save data necessary for CleanUp // save data necessary for CleanUp
d.inProgressFQDNs[fqdn] = inProgressInfo{ d.inProgressFQDNs[fqdn] = inProgressInfo{
zoneID: zoneID, zoneID: zoneID,
@ -142,25 +154,25 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
// acquire lock and retrieve zoneID, newZoneID and authZone // acquire lock and retrieve zoneID, newZoneID and authZone
d.inProgressMu.Lock() d.inProgressMu.Lock()
defer d.inProgressMu.Unlock() defer d.inProgressMu.Unlock()
if _, ok := d.inProgressFQDNs[fqdn]; !ok { if _, ok := d.inProgressFQDNs[fqdn]; !ok {
// if there is no cleanup information then just return // if there is no cleanup information then just return
return nil return nil
} }
zoneID := d.inProgressFQDNs[fqdn].zoneID zoneID := d.inProgressFQDNs[fqdn].zoneID
newZoneID := d.inProgressFQDNs[fqdn].newZoneID newZoneID := d.inProgressFQDNs[fqdn].newZoneID
authZone := d.inProgressFQDNs[fqdn].authZone authZone := d.inProgressFQDNs[fqdn].authZone
delete(d.inProgressFQDNs, fqdn) delete(d.inProgressFQDNs, fqdn)
delete(d.inProgressAuthZones, authZone) delete(d.inProgressAuthZones, authZone)
// perform API actions to restore old gandi zone for authZone // perform API actions to restore old gandi zone for authZone
err := d.setZone(authZone, zoneID) err := d.setZone(authZone, zoneID)
if err != nil { if err != nil {
return err return err
} }
err = d.deleteZone(newZoneID)
if err != nil { return d.deleteZone(newZoneID)
return err
}
return nil
} }
// Timeout returns the values (40*time.Minute, 60*time.Second) which // Timeout returns the values (40*time.Minute, 60*time.Second) which
@ -259,15 +271,18 @@ func (e rpcError) Error() string {
func httpPost(url string, bodyType string, body io.Reader) ([]byte, error) { func httpPost(url string, bodyType string, body io.Reader) ([]byte, error) {
client := http.Client{Timeout: 60 * time.Second} client := http.Client{Timeout: 60 * time.Second}
resp, err := client.Post(url, bodyType, body) resp, err := client.Post(url, bodyType, body)
if err != nil { if err != nil {
return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err) return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body) b, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err) return nil, fmt.Errorf("Gandi DNS: HTTP Post Error: %v", err)
} }
return b, nil return b, nil
} }
@ -281,12 +296,14 @@ func rpcCall(call *methodCall, resp response) error {
if err != nil { if err != nil {
return fmt.Errorf("Gandi DNS: Marshal Error: %v", err) return fmt.Errorf("Gandi DNS: Marshal Error: %v", err)
} }
// post // post
b = append([]byte(`<?xml version="1.0"?>`+"\n"), b...) b = append([]byte(`<?xml version="1.0"?>`+"\n"), b...)
respBody, err := httpPost(endpoint, "text/xml", bytes.NewReader(b)) respBody, err := httpPost(endpoint, "text/xml", bytes.NewReader(b))
if err != nil { if err != nil {
return err return err
} }
// unmarshal // unmarshal
err = xml.Unmarshal(respBody, resp) err = xml.Unmarshal(respBody, resp)
if err != nil { if err != nil {
@ -313,12 +330,14 @@ func (d *DNSProvider) getZoneID(domain string) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
var zoneID int var zoneID int
for _, member := range resp.StructMembers { for _, member := range resp.StructMembers {
if member.Name == "zone_id" { if member.Name == "zone_id" {
zoneID = member.ValueInt zoneID = member.ValueInt
} }
} }
if zoneID == 0 { if zoneID == 0 {
return 0, fmt.Errorf( return 0, fmt.Errorf(
"Gandi DNS: Could not determine zone_id for %s", domain) "Gandi DNS: Could not determine zone_id for %s", domain)
@ -346,12 +365,14 @@ func (d *DNSProvider) cloneZone(zoneID int, name string) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
var newZoneID int var newZoneID int
for _, member := range resp.StructMembers { for _, member := range resp.StructMembers {
if member.Name == "id" { if member.Name == "id" {
newZoneID = member.ValueInt newZoneID = member.ValueInt
} }
} }
if newZoneID == 0 { if newZoneID == 0 {
return 0, fmt.Errorf("Gandi DNS: Could not determine cloned zone_id") return 0, fmt.Errorf("Gandi DNS: Could not determine cloned zone_id")
} }
@ -370,6 +391,7 @@ func (d *DNSProvider) newZoneVersion(zoneID int) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
if resp.Value == 0 { if resp.Value == 0 {
return 0, fmt.Errorf("Gandi DNS: Could not create new zone version") return 0, fmt.Errorf("Gandi DNS: Could not create new zone version")
} }
@ -402,10 +424,7 @@ func (d *DNSProvider) addTXTRecord(zoneID int, version int, name string, value s
}, },
}, },
}, resp) }, resp)
if err != nil { return err
return err
}
return nil
} }
func (d *DNSProvider) setZoneVersion(zoneID int, version int) error { func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
@ -421,6 +440,7 @@ func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
if err != nil { if err != nil {
return err return err
} }
if !resp.Value { if !resp.Value {
return fmt.Errorf("Gandi DNS: could not set zone version") return fmt.Errorf("Gandi DNS: could not set zone version")
} }
@ -440,12 +460,14 @@ func (d *DNSProvider) setZone(domain string, zoneID int) error {
if err != nil { if err != nil {
return err return err
} }
var respZoneID int var respZoneID int
for _, member := range resp.StructMembers { for _, member := range resp.StructMembers {
if member.Name == "zone_id" { if member.Name == "zone_id" {
respZoneID = member.ValueInt respZoneID = member.ValueInt
} }
} }
if respZoneID != zoneID { if respZoneID != zoneID {
return fmt.Errorf( return fmt.Errorf(
"Gandi DNS: Could not set new zone_id for %s", domain) "Gandi DNS: Could not set new zone_id for %s", domain)
@ -465,6 +487,7 @@ func (d *DNSProvider) deleteZone(zoneID int) error {
if err != nil { if err != nil {
return err return err
} }
if !resp.Value { if !resp.Value {
return fmt.Errorf("Gandi DNS: could not delete zone_id") return fmt.Errorf("Gandi DNS: could not delete zone_id")
} }

View file

@ -1,7 +1,6 @@
package gandi package gandi
import ( import (
"crypto"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -9,30 +8,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"testing" "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 // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC
// Server, whose responses are predetermined for particular requests. // Server, whose responses are predetermined for particular requests.
func TestDNSProvider(t *testing.T) { func TestDNSProvider(t *testing.T) {

View file

@ -21,6 +21,7 @@ var (
// endpoint is the Gandi API endpoint used by Present and // endpoint is the Gandi API endpoint used by Present and
// CleanUp. It is overridden during tests. // CleanUp. It is overridden during tests.
endpoint = "https://dns.api.gandi.net/api/v5" endpoint = "https://dns.api.gandi.net/api/v5"
// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden // findZoneByFqdn determines the DNS zone of an fqdn. It is overridden
// during tests. // during tests.
findZoneByFqdn = acme.FindZoneByFqdn findZoneByFqdn = acme.FindZoneByFqdn
@ -66,11 +67,13 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
if ttl < 300 { if ttl < 300 {
ttl = 300 // 300 is gandi minimum value for ttl ttl = 300 // 300 is gandi minimum value for ttl
} }
// find authZone // find authZone
authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers) authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err) return fmt.Errorf("Gandi DNS: findZoneByFqdn failure: %v", err)
} }
// determine name of TXT record // determine name of TXT record
if !strings.HasSuffix( if !strings.HasSuffix(
strings.ToLower(fqdn), strings.ToLower("."+authZone)) { strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
@ -78,15 +81,18 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
"Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn) "Gandi DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
} }
name := fqdn[:len(fqdn)-len("."+authZone)] name := fqdn[:len(fqdn)-len("."+authZone)]
// acquire lock and check there is not a challenge already in // acquire lock and check there is not a challenge already in
// progress for this value of authZone // progress for this value of authZone
d.inProgressMu.Lock() d.inProgressMu.Lock()
defer d.inProgressMu.Unlock() defer d.inProgressMu.Unlock()
// add TXT record into authZone // add TXT record into authZone
err = d.addTXTRecord(acme.UnFqdn(authZone), name, value, ttl) err = d.addTXTRecord(acme.UnFqdn(authZone), name, value, ttl)
if err != nil { if err != nil {
return err return err
} }
// save data necessary for CleanUp // save data necessary for CleanUp
d.inProgressFQDNs[fqdn] = inProgressInfo{ d.inProgressFQDNs[fqdn] = inProgressInfo{
authZone: authZone, authZone: authZone,
@ -98,6 +104,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
// CleanUp removes the TXT record matching the specified parameters. // CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acme.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
// acquire lock and retrieve authZone // acquire lock and retrieve authZone
d.inProgressMu.Lock() d.inProgressMu.Lock()
defer d.inProgressMu.Unlock() defer d.inProgressMu.Unlock()
@ -105,15 +112,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
// if there is no cleanup information then just return // if there is no cleanup information then just return
return nil return nil
} }
fieldName := d.inProgressFQDNs[fqdn].fieldName fieldName := d.inProgressFQDNs[fqdn].fieldName
authZone := d.inProgressFQDNs[fqdn].authZone authZone := d.inProgressFQDNs[fqdn].authZone
delete(d.inProgressFQDNs, fqdn) delete(d.inProgressFQDNs, fqdn)
// delete TXT record from authZone // delete TXT record from authZone
err := d.deleteTXTRecord(acme.UnFqdn(authZone), fieldName) return d.deleteTXTRecord(acme.UnFqdn(authZone), fieldName)
if err != nil {
return err
}
return nil
} }
// Timeout returns the values (20*time.Minute, 20*time.Second) which // Timeout returns the values (20*time.Minute, 20*time.Second) which
@ -149,16 +154,18 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest(method, url, bytes.NewReader(body)) req, err := http.NewRequest(method, url, bytes.NewReader(body))
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
if len(d.apiKey) > 0 { if len(d.apiKey) > 0 {
req.Header.Set("X-Api-Key", d.apiKey) req.Header.Set("X-Api-Key", d.apiKey)
} }
client := &http.Client{Timeout: time.Duration(10 * time.Second)} client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -1,7 +1,6 @@
package gandiv5 package gandiv5
import ( import (
"crypto"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -9,30 +8,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"testing" "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 // TestDNSProvider runs Present and CleanUp against a fake Gandi RPC
// Server, whose responses are predetermined for particular requests. // Server, whose responses are predetermined for particular requests.
func TestDNSProvider(t *testing.T) { func TestDNSProvider(t *testing.T) {

View file

@ -64,6 +64,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
if err != nil { if err != nil {
return fmt.Errorf("GleSYS DNS: findZoneByFqdn failure: %v", err) return fmt.Errorf("GleSYS DNS: findZoneByFqdn failure: %v", err)
} }
// determine name of TXT record // determine name of TXT record
if !strings.HasSuffix( if !strings.HasSuffix(
strings.ToLower(fqdn), strings.ToLower("."+authZone)) { strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
@ -71,23 +72,27 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
"GleSYS DNS: unexpected authZone %s for fqdn %s", authZone, fqdn) "GleSYS DNS: unexpected authZone %s for fqdn %s", authZone, fqdn)
} }
name := fqdn[:len(fqdn)-len("."+authZone)] name := fqdn[:len(fqdn)-len("."+authZone)]
// acquire lock and check there is not a challenge already in // acquire lock and check there is not a challenge already in
// progress for this value of authZone // progress for this value of authZone
d.inProgressMu.Lock() d.inProgressMu.Lock()
defer d.inProgressMu.Unlock() defer d.inProgressMu.Unlock()
// add TXT record into authZone // add TXT record into authZone
recordId, err := d.addTXTRecord(domain, acme.UnFqdn(authZone), name, value, ttl) recordID, err := d.addTXTRecord(domain, acme.UnFqdn(authZone), name, value, ttl)
if err != nil { if err != nil {
return err return err
} }
// save data necessary for CleanUp // save data necessary for CleanUp
d.activeRecords[fqdn] = recordId d.activeRecords[fqdn] = recordID
return nil return nil
} }
// CleanUp removes the TXT record matching the specified parameters. // CleanUp removes the TXT record matching the specified parameters.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acme.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
// acquire lock and retrieve authZone // acquire lock and retrieve authZone
d.inProgressMu.Lock() d.inProgressMu.Lock()
defer d.inProgressMu.Unlock() defer d.inProgressMu.Unlock()
@ -95,14 +100,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
// if there is no cleanup information then just return // if there is no cleanup information then just return
return nil return nil
} }
recordId := d.activeRecords[fqdn]
recordID := d.activeRecords[fqdn]
delete(d.activeRecords, fqdn) delete(d.activeRecords, fqdn)
// delete TXT record from authZone // delete TXT record from authZone
err := d.deleteTXTRecord(domain, recordId) return d.deleteTXTRecord(domain, recordID)
if err != nil {
return err
}
return nil
} }
// Timeout returns the values (20*time.Minute, 20*time.Second) which // Timeout returns the values (20*time.Minute, 20*time.Second) which
@ -119,7 +122,7 @@ type addRecordRequest struct {
Host string `json:"host"` Host string `json:"host"`
Type string `json:"type"` Type string `json:"type"`
Data string `json:"data"` Data string `json:"data"`
Ttl int `json:"ttl,omitempty"` TTL int `json:"ttl,omitempty"`
} }
type deleteRecordRequest struct { type deleteRecordRequest struct {
@ -144,14 +147,16 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
if err != nil { if err != nil {
return nil, err return nil, err
} }
req, err := http.NewRequest(method, url, bytes.NewReader(body)) req, err := http.NewRequest(method, url, bytes.NewReader(body))
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(d.apiUser, d.apiKey) req.SetBasicAuth(d.apiUser, d.apiKey)
client := &http.Client{Timeout: time.Duration(10 * time.Second)} client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
@ -161,6 +166,7 @@ func (d *DNSProvider) sendRequest(method string, resource string, payload interf
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
return nil, fmt.Errorf("GleSYS DNS: request failed with HTTP status code %d", resp.StatusCode) return nil, fmt.Errorf("GleSYS DNS: request failed with HTTP status code %d", resp.StatusCode)
} }
var response responseStruct var response responseStruct
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
@ -175,7 +181,7 @@ func (d *DNSProvider) addTXTRecord(fqdn string, domain string, name string, valu
Host: name, Host: name,
Type: "TXT", Type: "TXT",
Data: value, Data: value,
Ttl: ttl, TTL: ttl,
}) })
if response != nil && response.Response.Status.Code == 200 { if response != nil && response.Response.Status.Code == 200 {
log.Printf("[INFO][%s] GleSYS DNS: Successfully created recordid %d", fqdn, response.Response.Record.Recordid) log.Printf("[INFO][%s] GleSYS DNS: Successfully created recordid %d", fqdn, response.Response.Record.Recordid)

View file

@ -10,9 +10,10 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/xenolf/lego/acme"
"io/ioutil" "io/ioutil"
"strings" "strings"
"github.com/xenolf/lego/acme"
) )
// GoDaddyAPIURL represents the API endpoint to call. // GoDaddyAPIURL represents the API endpoint to call.
@ -75,7 +76,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
Type: "TXT", Type: "TXT",
Name: recordName, Name: recordName,
Data: value, Data: value,
Ttl: ttl, TTL: ttl,
}, },
} }
@ -98,7 +99,7 @@ func (c *DNSProvider) updateRecords(records []DNSRecord, domainZone string, reco
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
bodyBytes, _ := ioutil.ReadAll(resp.Body) bodyBytes, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Could not create record %v; Status: %v; Body: %s\n", string(body), resp.StatusCode, string(bodyBytes)) return fmt.Errorf("could not create record %v; Status: %v; Body: %s", string(body), resp.StatusCode, string(bodyBytes))
} }
return nil return nil
} }
@ -146,10 +147,11 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Res
return client.Do(req) return client.Do(req)
} }
// DNSRecord a DNS record
type DNSRecord struct { type DNSRecord struct {
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Data string `json:"data"` Data string `json:"data"`
Priority int `json:"priority,omitempty"` Priority int `json:"priority,omitempty"`
Ttl int `json:"ttl,omitempty"` TTL int `json:"ttl,omitempty"`
} }

View file

@ -11,7 +11,6 @@ import (
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
"google.golang.org/api/dns/v1" "google.golang.org/api/dns/v1"
@ -74,7 +73,7 @@ func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider,
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to acquire config: %v", err) return nil, fmt.Errorf("Unable to acquire config: %v", err)
} }
client := conf.Client(oauth2.NoContext) client := conf.Client(context.Background())
svc, err := dns.New(client) svc, err := dns.New(client)
if err != nil { if err != nil {

View file

@ -80,6 +80,7 @@ func TestLiveGoogleCloudPresentMultiple(t *testing.T) {
// Check that we're able to create multiple entries // Check that we're able to create multiple entries
err = provider.Present(gcloudDomain, "1", "123d==") err = provider.Present(gcloudDomain, "1", "123d==")
assert.NoError(t, err)
err = provider.Present(gcloudDomain, "2", "123d==") err = provider.Present(gcloudDomain, "2", "123d==")
assert.NoError(t, err) assert.NoError(t, err)
} }

View file

@ -19,7 +19,7 @@ const (
) )
type hostedZoneInfo struct { type hostedZoneInfo struct {
domainId int domainID int
resourceName string resourceName string
} }
@ -72,7 +72,7 @@ func (p *DNSProvider) Present(domain, token, keyAuth string) error {
return err return err
} }
if _, err = p.linode.CreateDomainResourceTXT(zone.domainId, acme.UnFqdn(fqdn), value, 60); err != nil { if _, err = p.linode.CreateDomainResourceTXT(zone.domainID, acme.UnFqdn(fqdn), value, 60); err != nil {
return err return err
} }
@ -88,7 +88,7 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
// Get all TXT records for the specified domain. // Get all TXT records for the specified domain.
resources, err := p.linode.GetResourcesByType(zone.domainId, "TXT") resources, err := p.linode.GetResourcesByType(zone.domainID, "TXT")
if err != nil { if err != nil {
return err return err
} }
@ -101,7 +101,7 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return err return err
} }
if resp.ResourceID != resource.ResourceID { if resp.ResourceID != resource.ResourceID {
return errors.New("Error deleting resource: resource IDs do not match!") return errors.New("error deleting resource: resource IDs do not match")
} }
break break
} }
@ -125,7 +125,7 @@ func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
} }
return &hostedZoneInfo{ return &hostedZoneInfo{
domainId: domain.DomainID, domainID: domain.DomainID,
resourceName: resourceName, resourceName: resourceName,
}, nil }, nil
} }

View file

@ -144,7 +144,7 @@ func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, e
} }
} }
if longest < 1 { if longest < 1 {
return nil, fmt.Errorf("Invalid domain name '%s'", domain) return nil, fmt.Errorf("invalid domain name %q", domain)
} }
tld := strings.Join(parts[longest:], ".") tld := strings.Join(parts[longest:], ".")
@ -318,7 +318,7 @@ func (d *DNSProvider) setHosts(ch *challenge, hosts []host) error {
shr.Errors[0].Description, shr.Errors[0].Number) shr.Errors[0].Description, shr.Errors[0].Number)
} }
if shr.Result.IsSuccess != "true" { if shr.Result.IsSuccess != "true" {
return fmt.Errorf("Namecheap setHosts failed.") return fmt.Errorf("Namecheap setHosts failed")
} }
return nil return nil

View file

@ -2,11 +2,12 @@ package otc
import ( import (
"fmt" "fmt"
"github.com/stretchr/testify/assert"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
var fakeOTCUserName = "test" var fakeOTCUserName = "test"
@ -15,12 +16,14 @@ var fakeOTCDomainName = "test"
var fakeOTCProjectName = "test" var fakeOTCProjectName = "test"
var fakeOTCToken = "62244bc21da68d03ebac94e6636ff01f" var fakeOTCToken = "62244bc21da68d03ebac94e6636ff01f"
// DNSMock mock
type DNSMock struct { type DNSMock struct {
t *testing.T t *testing.T
Server *httptest.Server Server *httptest.Server
Mux *http.ServeMux Mux *http.ServeMux
} }
// NewDNSMock create a new DNSMock
func NewDNSMock(t *testing.T) *DNSMock { func NewDNSMock(t *testing.T) *DNSMock {
return &DNSMock{ return &DNSMock{
t: t, t: t,
@ -38,6 +41,7 @@ func (m *DNSMock) ShutdownServer() {
m.Server.Close() m.Server.Close()
} }
// HandleAuthSuccessfully Handle auth successfully
func (m *DNSMock) HandleAuthSuccessfully() { func (m *DNSMock) HandleAuthSuccessfully() {
m.Mux.HandleFunc("/v3/auth/token", func(w http.ResponseWriter, r *http.Request) { m.Mux.HandleFunc("/v3/auth/token", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Subject-Token", fakeOTCToken) w.Header().Set("X-Subject-Token", fakeOTCToken)
@ -64,6 +68,7 @@ func (m *DNSMock) HandleAuthSuccessfully() {
}) })
} }
// HandleListZonesSuccessfully Handle list zones successfully
func (m *DNSMock) HandleListZonesSuccessfully() { func (m *DNSMock) HandleListZonesSuccessfully() {
m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{ fmt.Fprintf(w, `{
@ -79,6 +84,7 @@ func (m *DNSMock) HandleListZonesSuccessfully() {
}) })
} }
// HandleListZonesEmpty Handle list zones empty
func (m *DNSMock) HandleListZonesEmpty() { func (m *DNSMock) HandleListZonesEmpty() {
m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) { m.Mux.HandleFunc("/v2/zones", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{ fmt.Fprintf(w, `{
@ -93,6 +99,7 @@ func (m *DNSMock) HandleListZonesEmpty() {
}) })
} }
// HandleDeleteRecordsetsSuccessfully Handle delete recordsets successfully
func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() { func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() {
m.Mux.HandleFunc("/v2/zones/123123/recordsets/321321", func(w http.ResponseWriter, r *http.Request) { m.Mux.HandleFunc("/v2/zones/123123/recordsets/321321", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{ fmt.Fprintf(w, `{
@ -107,6 +114,7 @@ func (m *DNSMock) HandleDeleteRecordsetsSuccessfully() {
}) })
} }
// HandleListRecordsetsEmpty Handle list recordsets empty
func (m *DNSMock) HandleListRecordsetsEmpty() { func (m *DNSMock) HandleListRecordsetsEmpty() {
m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{ fmt.Fprintf(w, `{
@ -118,6 +126,8 @@ func (m *DNSMock) HandleListRecordsetsEmpty() {
assert.Equal(m.t, r.URL.RawQuery, "type=TXT&name=_acme-challenge.example.com.") assert.Equal(m.t, r.URL.RawQuery, "type=TXT&name=_acme-challenge.example.com.")
}) })
} }
// HandleListRecordsetsSuccessfully Handle list recordsets successfully
func (m *DNSMock) HandleListRecordsetsSuccessfully() { func (m *DNSMock) HandleListRecordsetsSuccessfully() {
m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) { m.Mux.HandleFunc("/v2/zones/123123/recordsets", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" { if r.Method == "GET" {

View file

@ -59,6 +59,7 @@ func NewDNSProviderCredentials(domainName, userName, password, projectName, iden
}, nil }, nil
} }
// SendRequest send request
func (d *DNSProvider) SendRequest(method, resource string, payload interface{}) (io.Reader, error) { func (d *DNSProvider) SendRequest(method, resource string, payload interface{}) (io.Reader, error) {
url := fmt.Sprintf("%s/%s", d.otcBaseURL, resource) url := fmt.Sprintf("%s/%s", d.otcBaseURL, resource)
@ -81,7 +82,7 @@ func (d *DNSProvider) SendRequest(method, resource string, payload interface{})
tr.DisableKeepAlives = true tr.DisableKeepAlives = true
client := &http.Client{ client := &http.Client{
Timeout: time.Duration(10 * time.Second), Timeout: 10 * time.Second,
Transport: tr, Transport: tr,
} }
resp, err := client.Do(req) resp, err := client.Do(req)
@ -168,7 +169,7 @@ func (d *DNSProvider) loginRequest() error {
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: time.Duration(10 * time.Second)} client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return err return err
@ -221,12 +222,7 @@ func (d *DNSProvider) loginRequest() error {
// Starts a new OTC API Session. Authenticates using userName, password // Starts a new OTC API Session. Authenticates using userName, password
// and receives a token to be used in for subsequent requests. // and receives a token to be used in for subsequent requests.
func (d *DNSProvider) login() error { func (d *DNSProvider) login() error {
err := d.loginRequest() return d.loginRequest()
if err != nil {
return err
}
return nil
} }
func (d *DNSProvider) getZoneID(zone string) (string, error) { func (d *DNSProvider) getZoneID(zone string) (string, error) {
@ -305,10 +301,7 @@ func (d *DNSProvider) deleteRecordSet(zoneID, recordID string) error {
resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID) resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID)
_, err := d.SendRequest("DELETE", resource, nil) _, err := d.SendRequest("DELETE", resource, nil)
if err != nil { return err
return err
}
return nil
} }
// Present creates a TXT record using the specified parameters // Present creates a TXT record using the specified parameters
@ -340,7 +333,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Type string `json:"type"` Type string `json:"type"`
Ttl int `json:"ttl"` TTL int `json:"ttl"`
Records []string `json:"records"` Records []string `json:"records"`
} }
@ -348,16 +341,11 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
Name: fqdn, Name: fqdn,
Description: "Added TXT record for ACME dns-01 challenge using lego client", Description: "Added TXT record for ACME dns-01 challenge using lego client",
Type: "TXT", Type: "TXT",
Ttl: 300, TTL: ttl,
Records: []string{fmt.Sprintf("\"%s\"", value)}, Records: []string{fmt.Sprintf("\"%s\"", value)},
} }
_, err = d.SendRequest("POST", resource, r1) _, err = d.SendRequest("POST", resource, r1)
return err
if err != nil {
return err
}
return nil
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
@ -375,7 +363,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
zoneID, err := d.getZoneID(authZone) zoneID, err := d.getZoneID(authZone)
if err != nil { if err != nil {
return err return err
} }
@ -384,5 +371,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
if err != nil { if err != nil {
return fmt.Errorf("unable go get record %s for zone %s: %s", fqdn, domain, err) return fmt.Errorf("unable go get record %s for zone %s: %s", fqdn, domain, err)
} }
return d.deleteRecordSet(zoneID, recordID) return d.deleteRecordSet(zoneID, recordID)
} }

View file

@ -1,4 +1,4 @@
// Package OVH implements a DNS provider for solving the DNS-01 // Package ovh implements a DNS provider for solving the DNS-01
// challenge using OVH DNS. // challenge using OVH DNS.
package ovh package ovh

View file

@ -29,12 +29,12 @@ type DNSProvider struct {
// PDNS_API_URL and PDNS_API_KEY. // PDNS_API_URL and PDNS_API_KEY.
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
key := os.Getenv("PDNS_API_KEY") key := os.Getenv("PDNS_API_KEY")
hostUrl, err := url.Parse(os.Getenv("PDNS_API_URL")) hostURL, err := url.Parse(os.Getenv("PDNS_API_URL"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewDNSProviderCredentials(hostUrl, key) return NewDNSProviderCredentials(hostURL, key)
} }
// NewDNSProviderCredentials uses the supplied credentials to return a // NewDNSProviderCredentials uses the supplied credentials to return a
@ -107,12 +107,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
} }
_, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body)) _, err = c.makeRequest("PATCH", zone.URL, bytes.NewReader(body))
if err != nil { return err
fmt.Println("here")
return err
}
return nil
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
@ -131,7 +126,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
rrsets := rrSets{ rrsets := rrSets{
RRSets: []rrSet{ RRSets: []rrSet{
rrSet{ {
Name: set.Name, Name: set.Name,
Type: set.Type, Type: set.Type,
ChangeType: "DELETE", ChangeType: "DELETE",
@ -220,7 +215,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) {
} }
} }
return nil, fmt.Errorf("No existing record found for %s", fqdn) return nil, fmt.Errorf("no existing record found for %s", fqdn)
} }
func (c *DNSProvider) getAPIVersion() { func (c *DNSProvider) getAPIVersion() {

View file

@ -92,7 +92,7 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
client := http.Client{Timeout: 30 * time.Second} client := http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error querying Rackspace Identity API: %v", err) return nil, fmt.Errorf("error querying Rackspace Identity API: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -115,7 +115,7 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
} }
} }
if dnsEndpoint == "" { if dnsEndpoint == "" {
return nil, fmt.Errorf("Failed to populate DNS endpoint, check Rackspace API for changes.") return nil, fmt.Errorf("failed to populate DNS endpoint, check Rackspace API for changes")
} }
return &DNSProvider{ return &DNSProvider{
@ -132,8 +132,8 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
return err return err
} }
rec := RackspaceRecords{ rec := Records{
RackspaceRecord: []RackspaceRecord{{ Record: []Record{{
Name: acme.UnFqdn(fqdn), Name: acme.UnFqdn(fqdn),
Type: "TXT", Type: "TXT",
Data: value, Data: value,
@ -147,11 +147,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
} }
_, err = c.makeRequest("POST", fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body)) _, err = c.makeRequest("POST", fmt.Sprintf("/domains/%d/records", zoneID), bytes.NewReader(body))
if err != nil { return err
return err
}
return nil
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
@ -168,11 +164,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
_, err = c.makeRequest("DELETE", fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil) _, err = c.makeRequest("DELETE", fmt.Sprintf("/domains/%d/records?id=%s", zoneID, record.ID), nil)
if err != nil { return err
return err
}
return nil
} }
// getHostedZoneID performs a lookup to get the DNS zone which needs // getHostedZoneID performs a lookup to get the DNS zone which needs
@ -205,36 +197,35 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) {
// If nothing was returned, or for whatever reason more than 1 was returned (the search uses exact match, so should not occur) // If nothing was returned, or for whatever reason more than 1 was returned (the search uses exact match, so should not occur)
if zoneSearchResponse.TotalEntries != 1 { if zoneSearchResponse.TotalEntries != 1 {
return 0, fmt.Errorf("Found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn) return 0, fmt.Errorf("found %d zones for %s in Rackspace for domain %s", zoneSearchResponse.TotalEntries, authZone, fqdn)
} }
return zoneSearchResponse.HostedZones[0].ID, nil return zoneSearchResponse.HostedZones[0].ID, nil
} }
// findTxtRecord searches a DNS zone for a TXT record with a specific name // findTxtRecord searches a DNS zone for a TXT record with a specific name
func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*RackspaceRecord, error) { func (c *DNSProvider) findTxtRecord(fqdn string, zoneID int) (*Record, error) {
result, err := c.makeRequest("GET", fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)), nil) result, err := c.makeRequest("GET", fmt.Sprintf("/domains/%d/records?type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var records RackspaceRecords var records Records
err = json.Unmarshal(result, &records) err = json.Unmarshal(result, &records)
if err != nil { if err != nil {
return nil, err return nil, err
} }
recordsLength := len(records.RackspaceRecord) recordsLength := len(records.Record)
switch recordsLength { switch recordsLength {
case 1: case 1:
break
case 0: case 0:
return nil, fmt.Errorf("No TXT record found for %s", fqdn) return nil, fmt.Errorf("no TXT record found for %s", fqdn)
default: default:
return nil, fmt.Errorf("More than 1 TXT record found for %s", fqdn) return nil, fmt.Errorf("more than 1 TXT record found for %s", fqdn)
} }
return &records.RackspaceRecord[0], nil return &records.Record[0], nil
} }
// makeRequest is a wrapper function used for making DNS API requests // makeRequest is a wrapper function used for making DNS API requests
@ -251,13 +242,13 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
client := http.Client{Timeout: 30 * time.Second} client := http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error querying DNS API: %v", err) return nil, fmt.Errorf("error querying DNS API: %v", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
return nil, fmt.Errorf("Request failed for %s %s. Response code: %d", method, url, resp.StatusCode) return nil, fmt.Errorf("request failed for %s %s. Response code: %d", method, url, resp.StatusCode)
} }
var r json.RawMessage var r json.RawMessage
@ -269,13 +260,13 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
return r, nil return r, nil
} }
// RackspaceRecords is the list of records sent/received from the DNS API // Records is the list of records sent/received from the DNS API
type RackspaceRecords struct { type Records struct {
RackspaceRecord []RackspaceRecord `json:"records"` Record []Record `json:"records"`
} }
// RackspaceRecord represents a Rackspace DNS record // Record represents a Rackspace DNS record
type RackspaceRecord struct { type Record struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Data string `json:"data"` Data string `json:"data"`

View file

@ -42,13 +42,14 @@ func liveRackspaceEnv() {
os.Setenv("RACKSPACE_API_KEY", rackspaceAPIKey) os.Setenv("RACKSPACE_API_KEY", rackspaceAPIKey)
} }
func startTestServers() (identityAPI, dnsAPI *httptest.Server) { func startTestServers() (*httptest.Server, *httptest.Server) {
dnsAPI = httptest.NewServer(dnsMux()) dnsAPI := httptest.NewServer(dnsMux())
dnsEndpoint := dnsAPI.URL + "/123456" dnsEndpoint := dnsAPI.URL + "/123456"
identityAPI = httptest.NewServer(identityHandler(dnsEndpoint)) identityAPI := httptest.NewServer(identityHandler(dnsEndpoint))
testAPIURL = identityAPI.URL + "/" testAPIURL = identityAPI.URL + "/"
return
return identityAPI, dnsAPI
} }
func closeTestServers(identityAPI, dnsAPI *httptest.Server) { func closeTestServers(identityAPI, dnsAPI *httptest.Server) {

View file

@ -27,7 +27,7 @@ type DNSProvider struct {
// dynamic update. Configured with environment variables: // dynamic update. Configured with environment variables:
// RFC2136_NAMESERVER: Network address in the form "host" or "host:port". // RFC2136_NAMESERVER: Network address in the form "host" or "host:port".
// RFC2136_TSIG_ALGORITHM: Defaults to hmac-md5.sig-alg.reg.int. (HMAC-MD5). // RFC2136_TSIG_ALGORITHM: Defaults to hmac-md5.sig-alg.reg.int. (HMAC-MD5).
// See https://github.com/miekg/dns/blob/master/tsig.go for supported values. // See https://github.com/miekg/dns/blob/master/tsig.go for supported values.
// RFC2136_TSIG_KEY: Name of the secret key as defined in DNS server configuration. // RFC2136_TSIG_KEY: Name of the secret key as defined in DNS server configuration.
// RFC2136_TSIG_SECRET: Secret key payload. // RFC2136_TSIG_SECRET: Secret key payload.
// RFC2136_TIMEOUT: DNS propagation timeout in time.ParseDuration format. (60s) // RFC2136_TIMEOUT: DNS propagation timeout in time.ParseDuration format. (60s)
@ -77,7 +77,7 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t
if err != nil { if err != nil {
return nil, err return nil, err
} else if t < 0 { } else if t < 0 {
return nil, fmt.Errorf("Invalid/negative RFC2136_TIMEOUT: %v", timeout) return nil, fmt.Errorf("invalid/negative RFC2136_TIMEOUT: %v", timeout)
} else { } else {
d.timeout = t d.timeout = t
} }
@ -86,26 +86,26 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKey, tsigSecret, t
return d, nil return d, nil
} }
// Returns the timeout configured with RFC2136_TIMEOUT, or 60s. // Timeout Returns the timeout configured with RFC2136_TIMEOUT, or 60s.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
return d.timeout, 2 * time.Second return d.timeout, 2 * time.Second
} }
// Present creates a TXT record using the specified parameters // Present creates a TXT record using the specified parameters
func (r *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
return r.changeRecord("INSERT", fqdn, value, ttl) return d.changeRecord("INSERT", fqdn, value, ttl)
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
return r.changeRecord("REMOVE", fqdn, value, ttl) return d.changeRecord("REMOVE", fqdn, value, ttl)
} }
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { func (d *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
// Find the zone for the given fqdn // Find the zone for the given fqdn
zone, err := acme.FindZoneByFqdn(fqdn, []string{r.nameserver}) zone, err := acme.FindZoneByFqdn(fqdn, []string{d.nameserver})
if err != nil { if err != nil {
return err return err
} }
@ -127,20 +127,20 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
case "REMOVE": case "REMOVE":
m.Remove(rrs) m.Remove(rrs)
default: default:
return fmt.Errorf("Unexpected action: %s", action) return fmt.Errorf("unexpected action: %s", action)
} }
// Setup client // Setup client
c := new(dns.Client) c := new(dns.Client)
c.SingleInflight = true c.SingleInflight = true
// TSIG authentication / msg signing // TSIG authentication / msg signing
if len(r.tsigKey) > 0 && len(r.tsigSecret) > 0 { if len(d.tsigKey) > 0 && len(d.tsigSecret) > 0 {
m.SetTsig(dns.Fqdn(r.tsigKey), r.tsigAlgorithm, 300, time.Now().Unix()) m.SetTsig(dns.Fqdn(d.tsigKey), d.tsigAlgorithm, 300, time.Now().Unix())
c.TsigSecret = map[string]string{dns.Fqdn(r.tsigKey): r.tsigSecret} c.TsigSecret = map[string]string{dns.Fqdn(d.tsigKey): d.tsigSecret}
} }
// Send the query // Send the query
reply, _, err := c.Exchange(m, r.nameserver) reply, _, err := c.Exchange(m, d.nameserver)
if err != nil { if err != nil {
return fmt.Errorf("DNS update failed: %v", err) return fmt.Errorf("DNS update failed: %v", err)
} }

View file

@ -99,7 +99,7 @@ func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
hostedZoneID, err := r.getHostedZoneID(fqdn) hostedZoneID, err := r.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return fmt.Errorf("Failed to determine Route 53 hosted zone ID: %v", err) return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err)
} }
recordSet := newTXTRecordSet(fqdn, value, ttl) recordSet := newTXTRecordSet(fqdn, value, ttl)
@ -118,7 +118,7 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
resp, err := r.client.ChangeResourceRecordSets(reqParams) resp, err := r.client.ChangeResourceRecordSets(reqParams)
if err != nil { if err != nil {
return fmt.Errorf("Failed to change Route 53 record set: %v", err) return fmt.Errorf("failed to change Route 53 record set: %v", err)
} }
statusID := resp.ChangeInfo.Id statusID := resp.ChangeInfo.Id
@ -129,9 +129,9 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error {
} }
resp, err := r.client.GetChange(reqParams) resp, err := r.client.GetChange(reqParams)
if err != nil { if err != nil {
return false, fmt.Errorf("Failed to query Route 53 change status: %v", err) return false, fmt.Errorf("failed to query Route 53 change status: %v", err)
} }
if *resp.ChangeInfo.Status == route53.ChangeStatusInsync { if aws.StringValue(resp.ChangeInfo.Status) == route53.ChangeStatusInsync {
return true, nil return true, nil
} }
return false, nil return false, nil
@ -160,14 +160,14 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
var hostedZoneID string var hostedZoneID string
for _, hostedZone := range resp.HostedZones { for _, hostedZone := range resp.HostedZones {
// .Name has a trailing dot // .Name has a trailing dot
if !*hostedZone.Config.PrivateZone && *hostedZone.Name == authZone { if !aws.BoolValue(hostedZone.Config.PrivateZone) && aws.StringValue(hostedZone.Name) == authZone {
hostedZoneID = *hostedZone.Id hostedZoneID = aws.StringValue(hostedZone.Id)
break break
} }
} }
if len(hostedZoneID) == 0 { if len(hostedZoneID) == 0 {
return "", fmt.Errorf("Zone %s not found in Route 53 for domain %s", authZone, fqdn) return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn)
} }
if strings.HasPrefix(hostedZoneID, "/hostedzone/") { if strings.HasPrefix(hostedZoneID, "/hostedzone/") {

View file

@ -11,7 +11,6 @@ import (
) )
func TestRoute53TTL(t *testing.T) { func TestRoute53TTL(t *testing.T) {
m, err := testGetAndPreCheck() m, err := testGetAndPreCheck()
if err != nil { if err != nil {
t.Skip(err.Error()) t.Skip(err.Error())
@ -19,13 +18,14 @@ func TestRoute53TTL(t *testing.T) {
provider, err := NewDNSProvider() provider, err := NewDNSProvider()
if err != nil { if err != nil {
t.Fatalf("Fatal: %s", err.Error()) t.Fatal(err)
} }
err = provider.Present(m["route53Domain"], "foo", "bar") err = provider.Present(m["route53Domain"], "foo", "bar")
if err != nil { if err != nil {
t.Fatalf("Fatal: %s", err.Error()) t.Fatal(err)
} }
// we need a separate R53 client here as the one in the DNS provider is // we need a separate R53 client here as the one in the DNS provider is
// unexported. // unexported.
fqdn := "_acme-challenge." + m["route53Domain"] + "." fqdn := "_acme-challenge." + m["route53Domain"] + "."
@ -33,23 +33,25 @@ func TestRoute53TTL(t *testing.T) {
zoneID, err := provider.getHostedZoneID(fqdn) zoneID, err := provider.getHostedZoneID(fqdn)
if err != nil { if err != nil {
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(m["route53Domain"], "foo", "bar")
t.Fatalf("Fatal: %s", err.Error()) t.Fatal(err)
} }
params := &route53.ListResourceRecordSetsInput{ params := &route53.ListResourceRecordSetsInput{
HostedZoneId: aws.String(zoneID), HostedZoneId: aws.String(zoneID),
} }
resp, err := svc.ListResourceRecordSets(params) resp, err := svc.ListResourceRecordSets(params)
if err != nil { if err != nil {
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(m["route53Domain"], "foo", "bar")
t.Fatalf("Fatal: %s", err.Error()) t.Fatal(err)
} }
for _, v := range resp.ResourceRecordSets { for _, v := range resp.ResourceRecordSets {
if *v.Name == fqdn && *v.Type == "TXT" && *v.TTL == 10 { if aws.StringValue(v.Name) == fqdn && aws.StringValue(v.Type) == "TXT" && aws.Int64Value(v.TTL) == 10 {
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(m["route53Domain"], "foo", "bar")
return return
} }
} }
provider.CleanUp(m["route53Domain"], "foo", "bar") provider.CleanUp(m["route53Domain"], "foo", "bar")
t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", m["route53Domain"]) t.Fatalf("Could not find a TXT record for _acme-challenge.%s with a TTL of 10", m["route53Domain"])
} }

View file

@ -65,7 +65,7 @@ func TestRegionFromEnv(t *testing.T) {
os.Setenv("AWS_REGION", "us-east-1") os.Setenv("AWS_REGION", "us-east-1")
sess := session.New(aws.NewConfig()) sess := session.New(aws.NewConfig())
assert.Equal(t, "us-east-1", *sess.Config.Region, "Expected Region to be set from environment") assert.Equal(t, "us-east-1", aws.StringValue(sess.Config.Region), "Expected Region to be set from environment")
restoreRoute53Env() restoreRoute53Env()
} }

View file

@ -10,18 +10,18 @@ import (
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
) )
// HTTPProvider implements ChallengeProvider for `http-01` challenge // HTTPProvider implements HTTPProvider for `http-01` challenge
type MemcachedProvider struct { type HTTPProvider struct {
hosts []string hosts []string
} }
// NewHTTPProvider returns a HTTPProvider instance with a configured webroot path // NewMemcachedProvider returns a HTTPProvider instance with a configured webroot path
func NewMemcachedProvider(hosts []string) (*MemcachedProvider, error) { func NewMemcachedProvider(hosts []string) (*HTTPProvider, error) {
if len(hosts) == 0 { if len(hosts) == 0 {
return nil, fmt.Errorf("No memcached hosts provided") return nil, fmt.Errorf("no memcached hosts provided")
} }
c := &MemcachedProvider{ c := &HTTPProvider{
hosts: hosts, hosts: hosts,
} }
@ -29,7 +29,7 @@ func NewMemcachedProvider(hosts []string) (*MemcachedProvider, error) {
} }
// Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given webroot path // Present makes the token available at `HTTP01ChallengePath(token)` by creating a file in the given webroot path
func (w *MemcachedProvider) Present(domain, token, keyAuth string) error { func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
var errs []error var errs []error
challengePath := path.Join("/", acme.HTTP01ChallengePath(token)) challengePath := path.Join("/", acme.HTTP01ChallengePath(token))
@ -39,7 +39,7 @@ func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
errs = append(errs, err) errs = append(errs, err)
continue continue
} }
mc.Add(&memcache.Item{ _ = mc.Add(&memcache.Item{
Key: challengePath, Key: challengePath,
Value: []byte(keyAuth), Value: []byte(keyAuth),
Expiration: 60, Expiration: 60,
@ -47,14 +47,14 @@ func (w *MemcachedProvider) Present(domain, token, keyAuth string) error {
} }
if len(errs) == len(w.hosts) { if len(errs) == len(w.hosts) {
return fmt.Errorf("Unable to store key in any of the memcache hosts -> %v", errs) return fmt.Errorf("unable to store key in any of the memcache hosts -> %v", errs)
} }
return nil return nil
} }
// CleanUp removes the file created for the challenge // CleanUp removes the file created for the challenge
func (w *MemcachedProvider) CleanUp(domain, token, keyAuth string) error { func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error {
// Memcached will clean up itself, that's what expiration is for. // Memcached will clean up itself, that's what expiration is for.
return nil return nil
} }

View file

@ -31,7 +31,7 @@ func init() {
func TestNewMemcachedProviderEmpty(t *testing.T) { func TestNewMemcachedProviderEmpty(t *testing.T) {
emptyHosts := make([]string, 0) emptyHosts := make([]string, 0)
_, err := NewMemcachedProvider(emptyHosts) _, err := NewMemcachedProvider(emptyHosts)
assert.EqualError(t, err, "No memcached hosts provided") assert.EqualError(t, err, "no memcached hosts provided")
} }
func TestNewMemcachedProviderValid(t *testing.T) { func TestNewMemcachedProviderValid(t *testing.T) {

View file

@ -35,12 +35,12 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
challengeFilePath := path.Join(w.path, acme.HTTP01ChallengePath(token)) challengeFilePath := path.Join(w.path, acme.HTTP01ChallengePath(token))
err = os.MkdirAll(path.Dir(challengeFilePath), 0755) err = os.MkdirAll(path.Dir(challengeFilePath), 0755)
if err != nil { if err != nil {
return fmt.Errorf("Could not create required directories in webroot for HTTP challenge -> %v", err) return fmt.Errorf("could not create required directories in webroot for HTTP challenge -> %v", err)
} }
err = ioutil.WriteFile(challengeFilePath, []byte(keyAuth), 0644) err = ioutil.WriteFile(challengeFilePath, []byte(keyAuth), 0644)
if err != nil { if err != nil {
return fmt.Errorf("Could not write file in webroot for HTTP challenge -> %v", err) return fmt.Errorf("could not write file in webroot for HTTP challenge -> %v", err)
} }
return nil return nil
@ -48,10 +48,9 @@ func (w *HTTPProvider) Present(domain, token, keyAuth string) error {
// CleanUp removes the file created for the challenge // CleanUp removes the file created for the challenge
func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error { func (w *HTTPProvider) CleanUp(domain, token, keyAuth string) error {
var err error err := os.Remove(path.Join(w.path, acme.HTTP01ChallengePath(token)))
err = os.Remove(path.Join(w.path, acme.HTTP01ChallengePath(token)))
if err != nil { if err != nil {
return fmt.Errorf("Could not remove file in webroot after HTTP challenge -> %v", err) return fmt.Errorf("could not remove file in webroot after HTTP challenge -> %v", err)
} }
return nil return nil