Use to the stable version of Lego

This commit is contained in:
Ludovic Fernandez 2018-05-31 09:30:04 +02:00 committed by Traefiker Bot
parent 36e273714d
commit b2cf03fa5c
108 changed files with 3847 additions and 1152 deletions

27
Gopkg.lock generated
View file

@ -320,9 +320,10 @@
version = "v3.2.0" version = "v3.2.0"
[[projects]] [[projects]]
branch = "master"
name = "github.com/dnsimple/dnsimple-go" name = "github.com/dnsimple/dnsimple-go"
packages = ["dnsimple"] packages = ["dnsimple"]
revision = "f2d9b723cc9547d182e24ac2e527ae25d25fc93f" revision = "bbe1a2c87affea187478e24d3aea3cac25f870b3"
[[projects]] [[projects]]
name = "github.com/docker/cli" name = "github.com/docker/cli"
@ -991,9 +992,10 @@
revision = "1f5c07e90700ae93ddcba0c7af7d9c7201646ccc" revision = "1f5c07e90700ae93ddcba0c7af7d9c7201646ccc"
[[projects]] [[projects]]
branch = "master"
name = "github.com/ovh/go-ovh" name = "github.com/ovh/go-ovh"
packages = ["ovh"] packages = ["ovh"]
revision = "4b1fea467323b74c5f462f0947f402b428ca0626" revision = "91b7eb631d2eced3e706932a0b36ee8b5ee22e92"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -1241,10 +1243,10 @@
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70" revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
[[projects]] [[projects]]
branch = "containous-fork"
name = "github.com/xenolf/lego" name = "github.com/xenolf/lego"
packages = [ packages = [
"acmev2", "acme",
"log",
"providers/dns", "providers/dns",
"providers/dns/auroradns", "providers/dns/auroradns",
"providers/dns/azure", "providers/dns/azure",
@ -1262,9 +1264,9 @@
"providers/dns/fastdns", "providers/dns/fastdns",
"providers/dns/gandi", "providers/dns/gandi",
"providers/dns/gandiv5", "providers/dns/gandiv5",
"providers/dns/gcloud",
"providers/dns/glesys", "providers/dns/glesys",
"providers/dns/godaddy", "providers/dns/godaddy",
"providers/dns/googlecloud",
"providers/dns/lightsail", "providers/dns/lightsail",
"providers/dns/linode", "providers/dns/linode",
"providers/dns/namecheap", "providers/dns/namecheap",
@ -1278,8 +1280,8 @@
"providers/dns/route53", "providers/dns/route53",
"providers/dns/vultr" "providers/dns/vultr"
] ]
revision = "3d653ee2ee38f1d71beb5f09b37b23344eff0ab3" revision = "fefeb24353e513a985358d4a7746b2467ac05e31"
source = "github.com/containous/lego" version = "v1.0.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -1319,6 +1321,7 @@
revision = "22ae77b79946ea320088417e4d50825671d82d57" revision = "22ae77b79946ea320088417e4d50825671d82d57"
[[projects]] [[projects]]
branch = "master"
name = "golang.org/x/oauth2" name = "golang.org/x/oauth2"
packages = [ packages = [
".", ".",
@ -1327,7 +1330,7 @@
"jws", "jws",
"jwt" "jwt"
] ]
revision = "7fdf09982454086d5570c7db3e11f360194830ca" revision = "ec22f46f877b4505e0117eeaab541714644fdd28"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -1366,6 +1369,7 @@
revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959" revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959"
[[projects]] [[projects]]
branch = "master"
name = "google.golang.org/api" name = "google.golang.org/api"
packages = [ packages = [
"dns/v1", "dns/v1",
@ -1373,7 +1377,7 @@
"googleapi", "googleapi",
"googleapi/internal/uritemplates" "googleapi/internal/uritemplates"
] ]
revision = "1575df15c1bb8b18ad4d9bc5ca495cc85b0764fe" revision = "de943baf05a022a8f921b544b7827bacaba1aed5"
[[projects]] [[projects]]
name = "google.golang.org/appengine" name = "google.golang.org/appengine"
@ -1444,6 +1448,7 @@
revision = "cb884138e64c9a8bf5c7d6106d74b0fca082df0c" revision = "cb884138e64c9a8bf5c7d6106d74b0fca082df0c"
[[projects]] [[projects]]
branch = "v2"
name = "gopkg.in/ns1/ns1-go.v2" name = "gopkg.in/ns1/ns1-go.v2"
packages = [ packages = [
"rest", "rest",
@ -1453,7 +1458,7 @@
"rest/model/filter", "rest/model/filter",
"rest/model/monitor" "rest/model/monitor"
] ]
revision = "c563826f4cbef9c11bebeb9f20a3f7afe9c1e2f4" revision = "a5bcac82d3f637d3928d30476610891935b2d691"
[[projects]] [[projects]]
name = "gopkg.in/square/go-jose.v2" name = "gopkg.in/square/go-jose.v2"
@ -1674,6 +1679,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "c7d91203842be1915ca08a31917a079489bff7ffc6f2e494330e9556b4730a06" inputs-digest = "a6acf97e7459604ff987fa8af4520a1660ad8871df0db131ae3e9442574460b0"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View file

@ -181,9 +181,8 @@
name = "github.com/vulcand/oxy" name = "github.com/vulcand/oxy"
[[constraint]] [[constraint]]
branch = "containous-fork"
name = "github.com/xenolf/lego" name = "github.com/xenolf/lego"
source = "github.com/containous/lego" version = "1.0.0"
[[constraint]] [[constraint]]
name = "google.golang.org/grpc" name = "google.golang.org/grpc"

View file

@ -17,7 +17,7 @@ import (
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
acmeprovider "github.com/containous/traefik/provider/acme" acmeprovider "github.com/containous/traefik/provider/acme"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// Account is used to store lets encrypt registration info // Account is used to store lets encrypt registration info

View file

@ -26,7 +26,8 @@ import (
"github.com/containous/traefik/tls/generate" "github.com/containous/traefik/tls/generate"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/eapache/channels" "github.com/eapache/channels"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
legolog "github.com/xenolf/lego/log"
"github.com/xenolf/lego/providers/dns" "github.com/xenolf/lego/providers/dns"
) )
@ -42,7 +43,7 @@ type ACME struct {
Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"` Domains []types.Domain `description:"SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='main.net,san1.net,san2.net'"`
Storage string `description:"File or key used for certificates storage."` Storage string `description:"File or key used for certificates storage."`
StorageFile string // Deprecated StorageFile string // Deprecated
OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` //deprecated OnDemand bool `description:"(Deprecated) Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate."` // Deprecated
OnHostRule bool `description:"Enable certificate generation on frontends Host rules."` OnHostRule bool `description:"Enable certificate generation on frontends Host rules."`
CAServer string `description:"CA server to use."` CAServer string `description:"CA server to use."`
EntryPoint string `description:"Entrypoint to proxy acme challenge to."` EntryPoint string `description:"Entrypoint to proxy acme challenge to."`
@ -63,9 +64,9 @@ type ACME struct {
func (a *ACME) init() error { func (a *ACME) init() error {
if a.ACMELogging { if a.ACMELogging {
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags) legolog.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
} else { } else {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0) legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
} }
// no certificates in TLS config, so we add a default one // no certificates in TLS config, so we add a default one
cert, err := generate.DefaultCertificate() cert, err := generate.DefaultCertificate()

View file

@ -15,7 +15,7 @@ import (
"github.com/containous/traefik/tls/generate" "github.com/containous/traefik/tls/generate"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
func TestDomainsSet(t *testing.T) { func TestDomainsSet(t *testing.T) {

View file

@ -9,7 +9,7 @@ import (
"github.com/containous/traefik/cluster" "github.com/containous/traefik/cluster"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
var _ acme.ChallengeProviderTimeout = (*challengeHTTPProvider)(nil) var _ acme.ChallengeProviderTimeout = (*challengeHTTPProvider)(nil)

View file

@ -7,7 +7,7 @@ import (
"crypto/x509" "crypto/x509"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// Account is used to store lets encrypt registration info // Account is used to store lets encrypt registration info

View file

@ -8,7 +8,7 @@ import (
"github.com/containous/flaeg" "github.com/containous/flaeg"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/safe" "github.com/containous/traefik/safe"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
func dnsOverrideDelay(delay flaeg.Duration) error { func dnsOverrideDelay(delay flaeg.Duration) error {

View file

@ -23,7 +23,8 @@ import (
traefikTLS "github.com/containous/traefik/tls" traefikTLS "github.com/containous/traefik/tls"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/pkg/errors" "github.com/pkg/errors"
acme "github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
legolog "github.com/xenolf/lego/log"
"github.com/xenolf/lego/providers/dns" "github.com/xenolf/lego/providers/dns"
) )
@ -98,9 +99,9 @@ func (p *Provider) SetConfigListenerChan(configFromListenerChan chan types.Confi
func (p *Provider) init() error { func (p *Provider) init() error {
if p.ACMELogging { if p.ACMELogging {
acme.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags) legolog.Logger = fmtlog.New(os.Stderr, "legolog: ", fmtlog.LstdFlags)
} else { } else {
acme.Logger = fmtlog.New(ioutil.Discard, "", 0) legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
} }
var err error var err error
@ -248,7 +249,7 @@ func (p *Provider) resolveCertificate(domain types.Domain, domainFromConfigurati
} }
p.addCertificateForDomain(domain, certificate.Certificate, certificate.PrivateKey) p.addCertificateForDomain(domain, certificate.Certificate, certificate.PrivateKey)
return &certificate, nil return certificate, nil
} }
func (p *Provider) getClient() (*acme.Client, error) { func (p *Provider) getClient() (*acme.Client, error) {

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2017 Aetrion LLC dba DNSimple Copyright (c) 2014-2018 Aetrion LLC dba DNSimple
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,15 +1,12 @@
package dnsimple package dnsimple
import (
)
type AccountsService struct { type AccountsService struct {
client *Client client *Client
} }
// Account represents a DNSimple account. // Account represents a DNSimple account.
type Account struct { type Account struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
PlanIdentifier string `json:"plan_identifier,omitempty"` PlanIdentifier string `json:"plan_identifier,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`

View file

@ -2,29 +2,31 @@ package dnsimple
import ( import (
"fmt" "fmt"
"strconv"
) )
// CertificatesService handles communication with the certificate related // CertificatesService handles communication with the certificate related
// methods of the DNSimple API. // methods of the DNSimple API.
// //
// See https://developer.dnsimple.com/v2/domains/certificates // See https://developer.dnsimple.com/v2/certificates
type CertificatesService struct { type CertificatesService struct {
client *Client client *Client
} }
// Certificate represents a Certificate in DNSimple. // Certificate represents a Certificate in DNSimple.
type Certificate struct { type Certificate struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
DomainID int `json:"domain_id,omitempty"` DomainID int64 `json:"domain_id,omitempty"`
CommonName string `json:"common_name,omitempty"` ContactID int64 `json:"contact_id,omitempty"`
Years int `json:"years,omitempty"` CommonName string `json:"common_name,omitempty"`
State string `json:"state,omitempty"` AlternateNames []string `json:"alternate_names,omitempty"`
AuthorityIdentifier string `json:"authority_identifier,omitempty"` Years int `json:"years,omitempty"`
CreatedAt string `json:"created_at,omitempty"` State string `json:"state,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"` AuthorityIdentifier string `json:"authority_identifier,omitempty"`
ExpiresOn string `json:"expires_on,omitempty"` AutoRenew bool `json:"auto_renew"`
CertificateRequest string `json:"csr,omitempty"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
ExpiresOn string `json:"expires_on,omitempty"`
CertificateRequest string `json:"csr,omitempty"`
} }
// CertificateBundle represents a container for all the PEM-encoded X509 certificate entities, // CertificateBundle represents a container for all the PEM-encoded X509 certificate entities,
@ -37,9 +39,46 @@ type CertificateBundle struct {
IntermediateCertificates []string `json:"chain,omitempty"` IntermediateCertificates []string `json:"chain,omitempty"`
} }
func certificatePath(accountID, domainIdentifier, certificateID string) (path string) { // CertificatePurchase represents a Certificate Purchase in DNSimple.
type CertificatePurchase struct {
ID int64 `json:"id,omitempty"`
CertificateID int64 `json:"new_certificate_id,omitempty"`
State string `json:"state,omitempty"`
AutoRenew bool `json:"auto_renew,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
// CertificateRenewal represents a Certificate Renewal in DNSimple.
type CertificateRenewal struct {
ID int64 `json:"id,omitempty"`
OldCertificateID int64 `json:"old_certificate_id,omitempty"`
NewCertificateID int64 `json:"new_certificate_id,omitempty"`
State string `json:"state,omitempty"`
AutoRenew bool `json:"auto_renew,omitempty"`
CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}
// LetsencryptCertificateAttributes is a set of attributes to purchase a Let's Encrypt certificate.
type LetsencryptCertificateAttributes struct {
ContactID int64 `json:"contact_id,omitempty"`
Name string `json:"name,omitempty"`
AutoRenew bool `json:"auto_renew,omitempty"`
AlternateNames []string `json:"alternate_names,omitempty"`
}
func certificatePath(accountID, domainIdentifier string, certificateID int64) (path string) {
path = fmt.Sprintf("%v/certificates", domainPath(accountID, domainIdentifier)) path = fmt.Sprintf("%v/certificates", domainPath(accountID, domainIdentifier))
if certificateID != "" { if certificateID != 0 {
path += fmt.Sprintf("/%v", certificateID)
}
return
}
func letsencryptCertificatePath(accountID, domainIdentifier string, certificateID int64) (path string) {
path = fmt.Sprintf("%v/certificates/letsencrypt", domainPath(accountID, domainIdentifier))
if certificateID != 0 {
path += fmt.Sprintf("/%v", certificateID) path += fmt.Sprintf("/%v", certificateID)
} }
return return
@ -63,11 +102,23 @@ type certificatesResponse struct {
Data []Certificate `json:"data"` Data []Certificate `json:"data"`
} }
// ListCertificates list the certificates for a domain. // certificatePurchaseResponse represents a response from an API method that returns a CertificatePurchase struct.
type certificatePurchaseResponse struct {
Response
Data *CertificatePurchase `json:"data"`
}
// certificateRenewalResponse represents a response from an API method that returns a CertificateRenewal struct.
type certificateRenewalResponse struct {
Response
Data *CertificateRenewal `json:"data"`
}
// ListCertificates lists the certificates for a domain in the account.
// //
// See https://developer.dnsimple.com/v2/domains/certificates#list // See https://developer.dnsimple.com/v2/certificates#listCertificates
func (s *CertificatesService) ListCertificates(accountID, domainIdentifier string, options *ListOptions) (*certificatesResponse, error) { func (s *CertificatesService) ListCertificates(accountID, domainIdentifier string, options *ListOptions) (*certificatesResponse, error) {
path := versioned(certificatePath(accountID, domainIdentifier, "")) path := versioned(certificatePath(accountID, domainIdentifier, 0))
certificatesResponse := &certificatesResponse{} certificatesResponse := &certificatesResponse{}
path, err := addURLQueryOptions(path, options) path, err := addURLQueryOptions(path, options)
@ -84,11 +135,11 @@ func (s *CertificatesService) ListCertificates(accountID, domainIdentifier strin
return certificatesResponse, nil return certificatesResponse, nil
} }
// GetCertificate fetches the certificate. // GetCertificate gets the details of a certificate.
// //
// See https://developer.dnsimple.com/v2/domains/certificates#get // See https://developer.dnsimple.com/v2/certificates#getCertificate
func (s *CertificatesService) GetCertificate(accountID, domainIdentifier string, certificateID int) (*certificateResponse, error) { func (s *CertificatesService) GetCertificate(accountID, domainIdentifier string, certificateID int64) (*certificateResponse, error) {
path := versioned(certificatePath(accountID, domainIdentifier, strconv.Itoa(certificateID))) path := versioned(certificatePath(accountID, domainIdentifier, certificateID))
certificateResponse := &certificateResponse{} certificateResponse := &certificateResponse{}
resp, err := s.client.get(path, certificateResponse) resp, err := s.client.get(path, certificateResponse)
@ -100,12 +151,12 @@ func (s *CertificatesService) GetCertificate(accountID, domainIdentifier string,
return certificateResponse, nil return certificateResponse, nil
} }
// DownloadCertificate download the issued server certificate, // DownloadCertificate gets the PEM-encoded certificate,
// as well the root certificate and the intermediate chain. // along with the root certificate and intermediate chain.
// //
// See https://developer.dnsimple.com/v2/domains/certificates#download // See https://developer.dnsimple.com/v2/certificates#downloadCertificate
func (s *CertificatesService) DownloadCertificate(accountID, domainIdentifier string, certificateID int) (*certificateBundleResponse, error) { func (s *CertificatesService) DownloadCertificate(accountID, domainIdentifier string, certificateID int64) (*certificateBundleResponse, error) {
path := versioned(certificatePath(accountID, domainIdentifier, strconv.Itoa(certificateID)) + "/download") path := versioned(certificatePath(accountID, domainIdentifier, certificateID) + "/download")
certificateBundleResponse := &certificateBundleResponse{} certificateBundleResponse := &certificateBundleResponse{}
resp, err := s.client.get(path, certificateBundleResponse) resp, err := s.client.get(path, certificateBundleResponse)
@ -117,11 +168,11 @@ func (s *CertificatesService) DownloadCertificate(accountID, domainIdentifier st
return certificateBundleResponse, nil return certificateBundleResponse, nil
} }
// GetCertificatePrivateKey fetches the certificate private key. // GetCertificatePrivateKey gets the PEM-encoded certificate private key.
// //
// See https://developer.dnsimple.com/v2/domains/certificates#get-private-key // See https://developer.dnsimple.com/v2/certificates#getCertificatePrivateKey
func (s *CertificatesService) GetCertificatePrivateKey(accountID, domainIdentifier string, certificateID int) (*certificateBundleResponse, error) { func (s *CertificatesService) GetCertificatePrivateKey(accountID, domainIdentifier string, certificateID int64) (*certificateBundleResponse, error) {
path := versioned(certificatePath(accountID, domainIdentifier, strconv.Itoa(certificateID)) + "/private_key") path := versioned(certificatePath(accountID, domainIdentifier, certificateID) + "/private_key")
certificateBundleResponse := &certificateBundleResponse{} certificateBundleResponse := &certificateBundleResponse{}
resp, err := s.client.get(path, certificateBundleResponse) resp, err := s.client.get(path, certificateBundleResponse)
@ -132,3 +183,67 @@ func (s *CertificatesService) GetCertificatePrivateKey(accountID, domainIdentifi
certificateBundleResponse.HttpResponse = resp certificateBundleResponse.HttpResponse = resp
return certificateBundleResponse, nil return certificateBundleResponse, nil
} }
// PurchaseLetsencryptCertificate purchases a Let's Encrypt certificate.
//
// See https://developer.dnsimple.com/v2/certificates/#purchaseLetsencryptCertificate
func (s *CertificatesService) PurchaseLetsencryptCertificate(accountID, domainIdentifier string, certificateAttributes LetsencryptCertificateAttributes) (*certificatePurchaseResponse, error) {
path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, 0))
certificatePurchaseResponse := &certificatePurchaseResponse{}
resp, err := s.client.post(path, certificateAttributes, certificatePurchaseResponse)
if err != nil {
return nil, err
}
certificatePurchaseResponse.HttpResponse = resp
return certificatePurchaseResponse, nil
}
// IssueLetsencryptCertificate issues a pending Let's Encrypt certificate purchase order.
//
// See https://developer.dnsimple.com/v2/certificates/#issueLetsencryptCertificate
func (s *CertificatesService) IssueLetsencryptCertificate(accountID, domainIdentifier string, certificateID int64) (*certificateResponse, error) {
path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, certificateID) + "/issue")
certificateResponse := &certificateResponse{}
resp, err := s.client.post(path, nil, certificateResponse)
if err != nil {
return nil, err
}
certificateResponse.HttpResponse = resp
return certificateResponse, nil
}
// PurchaseLetsencryptCertificateRenewal purchases a Let's Encrypt certificate renewal.
//
// See https://developer.dnsimple.com/v2/certificates/#purchaseRenewalLetsencryptCertificate
func (s *CertificatesService) PurchaseLetsencryptCertificateRenewal(accountID, domainIdentifier string, certificateID int64, certificateAttributes LetsencryptCertificateAttributes) (*certificateRenewalResponse, error) {
path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, certificateID) + "/renewals")
certificateRenewalResponse := &certificateRenewalResponse{}
resp, err := s.client.post(path, certificateAttributes, certificateRenewalResponse)
if err != nil {
return nil, err
}
certificateRenewalResponse.HttpResponse = resp
return certificateRenewalResponse, nil
}
// IssueLetsencryptCertificateRenewal issues a pending Let's Encrypt certificate renewal order.
//
// See https://developer.dnsimple.com/v2/certificates/#issueRenewalLetsencryptCertificate
func (s *CertificatesService) IssueLetsencryptCertificateRenewal(accountID, domainIdentifier string, certificateID, certificateRenewalID int64) (*certificateResponse, error) {
path := versioned(letsencryptCertificatePath(accountID, domainIdentifier, certificateID) + fmt.Sprintf("/renewals/%d/issue", certificateRenewalID))
certificateResponse := &certificateResponse{}
resp, err := s.client.post(path, nil, certificateResponse)
if err != nil {
return nil, err
}
certificateResponse.HttpResponse = resp
return certificateResponse, nil
}

View file

@ -14,8 +14,8 @@ type ContactsService struct {
// Contact represents a Contact in DNSimple. // Contact represents a Contact in DNSimple.
type Contact struct { type Contact struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
AccountID int `json:"account_id,omitempty"` AccountID int64 `json:"account_id,omitempty"`
Label string `json:"label,omitempty"` Label string `json:"label,omitempty"`
FirstName string `json:"first_name,omitempty"` FirstName string `json:"first_name,omitempty"`
LastName string `json:"last_name,omitempty"` LastName string `json:"last_name,omitempty"`
@ -34,7 +34,7 @@ type Contact struct {
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
func contactPath(accountID string, contactID int) (path string) { func contactPath(accountID string, contactID int64) (path string) {
path = fmt.Sprintf("/%v/contacts", accountID) path = fmt.Sprintf("/%v/contacts", accountID)
if contactID != 0 { if contactID != 0 {
path += fmt.Sprintf("/%v", contactID) path += fmt.Sprintf("/%v", contactID)
@ -94,7 +94,7 @@ func (s *ContactsService) CreateContact(accountID string, contactAttributes Cont
// GetContact fetches a contact. // GetContact fetches a contact.
// //
// See https://developer.dnsimple.com/v2/contacts/#get // See https://developer.dnsimple.com/v2/contacts/#get
func (s *ContactsService) GetContact(accountID string, contactID int) (*contactResponse, error) { func (s *ContactsService) GetContact(accountID string, contactID int64) (*contactResponse, error) {
path := versioned(contactPath(accountID, contactID)) path := versioned(contactPath(accountID, contactID))
contactResponse := &contactResponse{} contactResponse := &contactResponse{}
@ -110,7 +110,7 @@ func (s *ContactsService) GetContact(accountID string, contactID int) (*contactR
// UpdateContact updates a contact. // UpdateContact updates a contact.
// //
// See https://developer.dnsimple.com/v2/contacts/#update // See https://developer.dnsimple.com/v2/contacts/#update
func (s *ContactsService) UpdateContact(accountID string, contactID int, contactAttributes Contact) (*contactResponse, error) { func (s *ContactsService) UpdateContact(accountID string, contactID int64, contactAttributes Contact) (*contactResponse, error) {
path := versioned(contactPath(accountID, contactID)) path := versioned(contactPath(accountID, contactID))
contactResponse := &contactResponse{} contactResponse := &contactResponse{}
@ -126,7 +126,7 @@ func (s *ContactsService) UpdateContact(accountID string, contactID int, contact
// DeleteContact PERMANENTLY deletes a contact from the account. // DeleteContact PERMANENTLY deletes a contact from the account.
// //
// See https://developer.dnsimple.com/v2/contacts/#delete // See https://developer.dnsimple.com/v2/contacts/#delete
func (s *ContactsService) DeleteContact(accountID string, contactID int) (*contactResponse, error) { func (s *ContactsService) DeleteContact(accountID string, contactID int64) (*contactResponse, error) {
path := versioned(contactPath(accountID, contactID)) path := versioned(contactPath(accountID, contactID))
contactResponse := &contactResponse{} contactResponse := &contactResponse{}

View file

@ -23,7 +23,7 @@ const (
// This is a pro-forma convention given that Go dependencies // This is a pro-forma convention given that Go dependencies
// tends to be fetched directly from the repo. // tends to be fetched directly from the repo.
// It is also used in the user-agent identify the client. // It is also used in the user-agent identify the client.
Version = "0.14.0" Version = "0.16.0"
// defaultBaseURL to the DNSimple production API. // defaultBaseURL to the DNSimple production API.
defaultBaseURL = "https://api.dnsimple.com" defaultBaseURL = "https://api.dnsimple.com"

View file

@ -14,9 +14,9 @@ type DomainsService struct {
// Domain represents a domain in DNSimple. // Domain represents a domain in DNSimple.
type Domain struct { type Domain struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
AccountID int `json:"account_id,omitempty"` AccountID int64 `json:"account_id,omitempty"`
RegistrantID int `json:"registrant_id,omitempty"` RegistrantID int64 `json:"registrant_id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
UnicodeName string `json:"unicode_name,omitempty"` UnicodeName string `json:"unicode_name,omitempty"`
Token string `json:"token,omitempty"` Token string `json:"token,omitempty"`

View file

@ -6,10 +6,10 @@ import (
// Collaborator represents a Collaborator in DNSimple. // Collaborator represents a Collaborator in DNSimple.
type Collaborator struct { type Collaborator struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
DomainID int `json:"domain_id,omitempty"` DomainID int64 `json:"domain_id,omitempty"`
DomainName string `json:"domain_name,omitempty"` DomainName string `json:"domain_name,omitempty"`
UserID int `json:"user_id,omitempty"` UserID int64 `json:"user_id,omitempty"`
UserEmail string `json:"user_email,omitempty"` UserEmail string `json:"user_email,omitempty"`
Invitation bool `json:"invitation,omitempty"` Invitation bool `json:"invitation,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
@ -17,9 +17,9 @@ type Collaborator struct {
AcceptedAt string `json:"accepted_at,omitempty"` AcceptedAt string `json:"accepted_at,omitempty"`
} }
func collaboratorPath(accountID, domainIdentifier, collaboratorID string) (path string) { func collaboratorPath(accountID, domainIdentifier string, collaboratorID int64) (path string) {
path = fmt.Sprintf("%v/collaborators", domainPath(accountID, domainIdentifier)) path = fmt.Sprintf("%v/collaborators", domainPath(accountID, domainIdentifier))
if collaboratorID != "" { if collaboratorID != 0 {
path += fmt.Sprintf("/%v", collaboratorID) path += fmt.Sprintf("/%v", collaboratorID)
} }
return return
@ -46,7 +46,7 @@ type collaboratorsResponse struct {
// //
// See https://developer.dnsimple.com/v2/domains/collaborators#list // See https://developer.dnsimple.com/v2/domains/collaborators#list
func (s *DomainsService) ListCollaborators(accountID, domainIdentifier string, options *ListOptions) (*collaboratorsResponse, error) { func (s *DomainsService) ListCollaborators(accountID, domainIdentifier string, options *ListOptions) (*collaboratorsResponse, error) {
path := versioned(collaboratorPath(accountID, domainIdentifier, "")) path := versioned(collaboratorPath(accountID, domainIdentifier, 0))
collaboratorsResponse := &collaboratorsResponse{} collaboratorsResponse := &collaboratorsResponse{}
path, err := addURLQueryOptions(path, options) path, err := addURLQueryOptions(path, options)
@ -67,7 +67,7 @@ func (s *DomainsService) ListCollaborators(accountID, domainIdentifier string, o
// //
// See https://developer.dnsimple.com/v2/domains/collaborators#add // See https://developer.dnsimple.com/v2/domains/collaborators#add
func (s *DomainsService) AddCollaborator(accountID string, domainIdentifier string, attributes CollaboratorAttributes) (*collaboratorResponse, error) { func (s *DomainsService) AddCollaborator(accountID string, domainIdentifier string, attributes CollaboratorAttributes) (*collaboratorResponse, error) {
path := versioned(collaboratorPath(accountID, domainIdentifier, "")) path := versioned(collaboratorPath(accountID, domainIdentifier, 0))
collaboratorResponse := &collaboratorResponse{} collaboratorResponse := &collaboratorResponse{}
resp, err := s.client.post(path, attributes, collaboratorResponse) resp, err := s.client.post(path, attributes, collaboratorResponse)
@ -81,8 +81,8 @@ func (s *DomainsService) AddCollaborator(accountID string, domainIdentifier stri
// RemoveCollaborator PERMANENTLY deletes a domain from the account. // RemoveCollaborator PERMANENTLY deletes a domain from the account.
// //
// See https://developer.dnsimple.com/v2/domains/collaborators#add // See https://developer.dnsimple.com/v2/domains/collaborators#remove
func (s *DomainsService) RemoveCollaborator(accountID string, domainIdentifier string, collaboratorID string) (*collaboratorResponse, error) { func (s *DomainsService) RemoveCollaborator(accountID string, domainIdentifier string, collaboratorID int64) (*collaboratorResponse, error) {
path := versioned(collaboratorPath(accountID, domainIdentifier, collaboratorID)) path := versioned(collaboratorPath(accountID, domainIdentifier, collaboratorID))
collaboratorResponse := &collaboratorResponse{} collaboratorResponse := &collaboratorResponse{}

View file

@ -4,8 +4,8 @@ import "fmt"
// DelegationSignerRecord represents a delegation signer record for a domain in DNSimple. // DelegationSignerRecord represents a delegation signer record for a domain in DNSimple.
type DelegationSignerRecord struct { type DelegationSignerRecord struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
DomainID int `json:"domain_id,omitempty"` DomainID int64 `json:"domain_id,omitempty"`
Algorithm string `json:"algorithm"` Algorithm string `json:"algorithm"`
Digest string `json:"digest"` Digest string `json:"digest"`
DigestType string `json:"digest_type"` DigestType string `json:"digest_type"`
@ -14,10 +14,10 @@ type DelegationSignerRecord struct {
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
func delegationSignerRecordPath(accountID string, domainIdentifier string, dsRecordID int) (path string) { func delegationSignerRecordPath(accountID string, domainIdentifier string, dsRecordID int64) (path string) {
path = fmt.Sprintf("%v/ds_records", domainPath(accountID, domainIdentifier)) path = fmt.Sprintf("%v/ds_records", domainPath(accountID, domainIdentifier))
if dsRecordID != 0 { if dsRecordID != 0 {
path += fmt.Sprintf("/%d", dsRecordID) path += fmt.Sprintf("/%v", dsRecordID)
} }
return return
} }
@ -74,7 +74,7 @@ func (s *DomainsService) CreateDelegationSignerRecord(accountID string, domainId
// GetDelegationSignerRecord fetches a delegation signer record. // GetDelegationSignerRecord fetches a delegation signer record.
// //
// See https://developer.dnsimple.com/v2/domains/dnssec/#ds-record-get // See https://developer.dnsimple.com/v2/domains/dnssec/#ds-record-get
func (s *DomainsService) GetDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int) (*delegationSignerRecordResponse, error) { func (s *DomainsService) GetDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int64) (*delegationSignerRecordResponse, error) {
path := versioned(delegationSignerRecordPath(accountID, domainIdentifier, dsRecordID)) path := versioned(delegationSignerRecordPath(accountID, domainIdentifier, dsRecordID))
dsRecordResponse := &delegationSignerRecordResponse{} dsRecordResponse := &delegationSignerRecordResponse{}
@ -91,7 +91,7 @@ func (s *DomainsService) GetDelegationSignerRecord(accountID string, domainIdent
// from the domain. // from the domain.
// //
// See https://developer.dnsimple.com/v2/domains/dnssec/#ds-record-delete // See https://developer.dnsimple.com/v2/domains/dnssec/#ds-record-delete
func (s *DomainsService) DeleteDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int) (*delegationSignerRecordResponse, error) { func (s *DomainsService) DeleteDelegationSignerRecord(accountID string, domainIdentifier string, dsRecordID int64) (*delegationSignerRecordResponse, error) {
path := versioned(delegationSignerRecordPath(accountID, domainIdentifier, dsRecordID)) path := versioned(delegationSignerRecordPath(accountID, domainIdentifier, dsRecordID))
dsRecordResponse := &delegationSignerRecordResponse{} dsRecordResponse := &delegationSignerRecordResponse{}

View file

@ -1,6 +1,8 @@
package dnsimple package dnsimple
import "fmt" import (
"fmt"
)
// Dnssec represents the current DNSSEC settings for a domain in DNSimple. // Dnssec represents the current DNSSEC settings for a domain in DNSimple.
type Dnssec struct { type Dnssec struct {

View file

@ -6,18 +6,18 @@ import (
// EmailForward represents an email forward in DNSimple. // EmailForward represents an email forward in DNSimple.
type EmailForward struct { type EmailForward struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
DomainID int `json:"domain_id,omitempty"` DomainID int64 `json:"domain_id,omitempty"`
From string `json:"from,omitempty"` From string `json:"from,omitempty"`
To string `json:"to,omitempty"` To string `json:"to,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
func emailForwardPath(accountID string, domainIdentifier string, forwardID int) (path string) { func emailForwardPath(accountID string, domainIdentifier string, forwardID int64) (path string) {
path = fmt.Sprintf("%v/email_forwards", domainPath(accountID, domainIdentifier)) path = fmt.Sprintf("%v/email_forwards", domainPath(accountID, domainIdentifier))
if forwardID != 0 { if forwardID != 0 {
path += fmt.Sprintf("/%d", forwardID) path += fmt.Sprintf("/%v", forwardID)
} }
return return
} }
@ -38,7 +38,7 @@ type emailForwardsResponse struct {
// //
// See https://developer.dnsimple.com/v2/domains/email-forwards/#list // See https://developer.dnsimple.com/v2/domains/email-forwards/#list
func (s *DomainsService) ListEmailForwards(accountID string, domainIdentifier string, options *ListOptions) (*emailForwardsResponse, error) { func (s *DomainsService) ListEmailForwards(accountID string, domainIdentifier string, options *ListOptions) (*emailForwardsResponse, error) {
path := versioned(emailForwardPath(accountID, domainIdentifier , 0)) path := versioned(emailForwardPath(accountID, domainIdentifier, 0))
forwardsResponse := &emailForwardsResponse{} forwardsResponse := &emailForwardsResponse{}
path, err := addURLQueryOptions(path, options) path, err := addURLQueryOptions(path, options)
@ -74,7 +74,7 @@ func (s *DomainsService) CreateEmailForward(accountID string, domainIdentifier s
// GetEmailForward fetches an email forward. // GetEmailForward fetches an email forward.
// //
// See https://developer.dnsimple.com/v2/domains/email-forwards/#get // See https://developer.dnsimple.com/v2/domains/email-forwards/#get
func (s *DomainsService) GetEmailForward(accountID string, domainIdentifier string, forwardID int) (*emailForwardResponse, error) { func (s *DomainsService) GetEmailForward(accountID string, domainIdentifier string, forwardID int64) (*emailForwardResponse, error) {
path := versioned(emailForwardPath(accountID, domainIdentifier, forwardID)) path := versioned(emailForwardPath(accountID, domainIdentifier, forwardID))
forwardResponse := &emailForwardResponse{} forwardResponse := &emailForwardResponse{}
@ -90,7 +90,7 @@ func (s *DomainsService) GetEmailForward(accountID string, domainIdentifier stri
// DeleteEmailForward PERMANENTLY deletes an email forward from the domain. // DeleteEmailForward PERMANENTLY deletes an email forward from the domain.
// //
// See https://developer.dnsimple.com/v2/domains/email-forwards/#delete // See https://developer.dnsimple.com/v2/domains/email-forwards/#delete
func (s *DomainsService) DeleteEmailForward(accountID string, domainIdentifier string, forwardID int) (*emailForwardResponse, error) { func (s *DomainsService) DeleteEmailForward(accountID string, domainIdentifier string, forwardID int64) (*emailForwardResponse, error) {
path := versioned(emailForwardPath(accountID, domainIdentifier, forwardID)) path := versioned(emailForwardPath(accountID, domainIdentifier, forwardID))
forwardResponse := &emailForwardResponse{} forwardResponse := &emailForwardResponse{}

View file

@ -6,19 +6,19 @@ import (
// DomainPush represents a domain push in DNSimple. // DomainPush represents a domain push in DNSimple.
type DomainPush struct { type DomainPush struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
DomainID int `json:"domain_id,omitempty"` DomainID int64 `json:"domain_id,omitempty"`
ContactID int `json:"contact_id,omitempty"` ContactID int64 `json:"contact_id,omitempty"`
AccountID int `json:"account_id,omitempty"` AccountID int64 `json:"account_id,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
AcceptedAt string `json:"accepted_at,omitempty"` AcceptedAt string `json:"accepted_at,omitempty"`
} }
func domainPushPath(accountID string, pushID int) (path string) { func domainPushPath(accountID string, pushID int64) (path string) {
path = fmt.Sprintf("/%v/pushes", accountID) path = fmt.Sprintf("/%v/pushes", accountID)
if pushID != 0 { if pushID != 0 {
path += fmt.Sprintf("/%d", pushID) path += fmt.Sprintf("/%v", pushID)
} }
return return
} }
@ -38,13 +38,13 @@ type domainPushesResponse struct {
// DomainPushAttributes represent a domain push payload (see initiate). // DomainPushAttributes represent a domain push payload (see initiate).
type DomainPushAttributes struct { type DomainPushAttributes struct {
NewAccountEmail string `json:"new_account_email,omitempty"` NewAccountEmail string `json:"new_account_email,omitempty"`
ContactID string `json:"contact_id,omitempty"` ContactID int64 `json:"contact_id,omitempty"`
} }
// InitiatePush initiate a new domain push. // InitiatePush initiate a new domain push.
// //
// See https://developer.dnsimple.com/v2/domains/pushes/#initiate // See https://developer.dnsimple.com/v2/domains/pushes/#initiate
func (s *DomainsService) InitiatePush(accountID string, domainID string, pushAttributes DomainPushAttributes) (*domainPushResponse, error) { func (s *DomainsService) InitiatePush(accountID, domainID string, pushAttributes DomainPushAttributes) (*domainPushResponse, error) {
path := versioned(fmt.Sprintf("/%v/pushes", domainPath(accountID, domainID))) path := versioned(fmt.Sprintf("/%v/pushes", domainPath(accountID, domainID)))
pushResponse := &domainPushResponse{} pushResponse := &domainPushResponse{}
@ -81,7 +81,7 @@ func (s *DomainsService) ListPushes(accountID string, options *ListOptions) (*do
// AcceptPush accept a push for a domain. // AcceptPush accept a push for a domain.
// //
// See https://developer.dnsimple.com/v2/domains/pushes/#accept // See https://developer.dnsimple.com/v2/domains/pushes/#accept
func (s *DomainsService) AcceptPush(accountID string, pushID int, pushAttributes DomainPushAttributes) (*domainPushResponse, error) { func (s *DomainsService) AcceptPush(accountID string, pushID int64, pushAttributes DomainPushAttributes) (*domainPushResponse, error) {
path := versioned(domainPushPath(accountID, pushID)) path := versioned(domainPushPath(accountID, pushID))
pushResponse := &domainPushResponse{} pushResponse := &domainPushResponse{}
@ -97,7 +97,7 @@ func (s *DomainsService) AcceptPush(accountID string, pushID int, pushAttributes
// RejectPush reject a push for a domain. // RejectPush reject a push for a domain.
// //
// See https://developer.dnsimple.com/v2/domains/pushes/#reject // See https://developer.dnsimple.com/v2/domains/pushes/#reject
func (s *DomainsService) RejectPush(accountID string, pushID int) (*domainPushResponse, error) { func (s *DomainsService) RejectPush(accountID string, pushID int64) (*domainPushResponse, error) {
path := versioned(domainPushPath(accountID, pushID)) path := versioned(domainPushPath(accountID, pushID))
pushResponse := &domainPushResponse{} pushResponse := &domainPushResponse{}

View file

@ -28,7 +28,7 @@ type domainCheckResponse struct {
// CheckDomain checks a domain name. // CheckDomain checks a domain name.
// //
// See https://developer.dnsimple.com/v2/registrar/#check // See https://developer.dnsimple.com/v2/registrar/#check
func (s *RegistrarService) CheckDomain(accountID, domainName string) (*domainCheckResponse, error) { func (s *RegistrarService) CheckDomain(accountID string, domainName string) (*domainCheckResponse, error) {
path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/check", accountID, domainName)) path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/check", accountID, domainName))
checkResponse := &domainCheckResponse{} checkResponse := &domainCheckResponse{}
@ -70,7 +70,7 @@ type DomainPremiumPriceOptions struct {
// - renewal // - renewal
// //
// See https://developer.dnsimple.com/v2/registrar/#premium-price // See https://developer.dnsimple.com/v2/registrar/#premium-price
func (s *RegistrarService) GetDomainPremiumPrice(accountID, domainName string, options *DomainPremiumPriceOptions) (*domainPremiumPriceResponse, error) { func (s *RegistrarService) GetDomainPremiumPrice(accountID string, domainName string, options *DomainPremiumPriceOptions) (*domainPremiumPriceResponse, error) {
var err error var err error
path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/premium_price", accountID, domainName)) path := versioned(fmt.Sprintf("/%v/registrar/domains/%v/premium_price", accountID, domainName))
priceResponse := &domainPremiumPriceResponse{} priceResponse := &domainPremiumPriceResponse{}
@ -100,7 +100,6 @@ type DomainRegistration struct {
State string `json:"state"` State string `json:"state"`
AutoRenew bool `json:"auto_renew"` AutoRenew bool `json:"auto_renew"`
WhoisPrivacy bool `json:"whois_privacy"` WhoisPrivacy bool `json:"whois_privacy"`
PremiumPrice string `json:"premium_price"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
@ -122,6 +121,8 @@ type DomainRegisterRequest struct {
// Set to true to enable the auto-renewal of the domain. // Set to true to enable the auto-renewal of the domain.
// Default to true. // Default to true.
EnableAutoRenewal bool `json:"auto_renew,omitempty"` EnableAutoRenewal bool `json:"auto_renew,omitempty"`
// Required as confirmation of the price, only if the domain is premium.
PremiumPrice string `json:"premium_price,omitempty"`
} }
// RegisterDomain registers a domain name. // RegisterDomain registers a domain name.
@ -150,7 +151,6 @@ type DomainTransfer struct {
State string `json:"state"` State string `json:"state"`
AutoRenew bool `json:"auto_renew"` AutoRenew bool `json:"auto_renew"`
WhoisPrivacy bool `json:"whois_privacy"` WhoisPrivacy bool `json:"whois_privacy"`
PremiumPrice string `json:"premium_price"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
@ -175,6 +175,8 @@ type DomainTransferRequest struct {
// Set to true to enable the auto-renewal of the domain. // Set to true to enable the auto-renewal of the domain.
// Default to true. // Default to true.
EnableAutoRenewal bool `json:"auto_renew,omitempty"` EnableAutoRenewal bool `json:"auto_renew,omitempty"`
// Required as confirmation of the price, only if the domain is premium.
PremiumPrice string `json:"premium_price,omitempty"`
} }
// TransferDomain transfers a domain name. // TransferDomain transfers a domain name.
@ -219,13 +221,12 @@ func (s *RegistrarService) TransferDomainOut(accountID string, domainName string
// DomainRenewal represents the result of a domain renewal call. // DomainRenewal represents the result of a domain renewal call.
type DomainRenewal struct { type DomainRenewal struct {
ID int `json:"id"` ID int `json:"id"`
DomainID int `json:"domain_id"` DomainID int `json:"domain_id"`
Period int `json:"period"` Period int `json:"period"`
State string `json:"state"` State string `json:"state"`
PremiumPrice string `json:"premium_price"` CreatedAt string `json:"created_at,omitempty"`
CreatedAt string `json:"created_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
} }
// domainRenewalResponse represents a response from an API method that returns a domain renewal. // domainRenewalResponse represents a response from an API method that returns a domain renewal.
@ -239,6 +240,8 @@ type domainRenewalResponse struct {
type DomainRenewRequest struct { type DomainRenewRequest struct {
// The number of years // The number of years
Period int `json:"period"` Period int `json:"period"`
// Required as confirmation of the price, only if the domain is premium.
PremiumPrice string `json:"premium_price,omitempty"`
} }
// RenewDomain renews a domain name. // RenewDomain renews a domain name.

View file

@ -6,8 +6,8 @@ import (
// WhoisPrivacy represents a whois privacy in DNSimple. // WhoisPrivacy represents a whois privacy in DNSimple.
type WhoisPrivacy struct { type WhoisPrivacy struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
DomainID int `json:"domain_id,omitempty"` DomainID int64 `json:"domain_id,omitempty"`
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
ExpiresOn string `json:"expires_on,omitempty"` ExpiresOn string `json:"expires_on,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`

View file

@ -14,7 +14,7 @@ type ServicesService struct {
// Service represents a Service in DNSimple. // Service represents a Service in DNSimple.
type Service struct { type Service struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
SID string `json:"sid,omitempty"` SID string `json:"sid,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
@ -36,10 +36,10 @@ type ServiceSetting struct {
Password bool `json:"password,omitempty"` Password bool `json:"password,omitempty"`
} }
func servicePath(serviceID string) (path string) { func servicePath(serviceIdentifier string) (path string) {
path = "/services" path = "/services"
if serviceID != "" { if serviceIdentifier != "" {
path += fmt.Sprintf("/%v", serviceID) path += fmt.Sprintf("/%v", serviceIdentifier)
} }
return return
} }

View file

@ -4,11 +4,11 @@ import (
"fmt" "fmt"
) )
func domainServicesPath(accountID string, domainID string, serviceIdentifier string) string { func domainServicesPath(accountID string, domainIdentifier string, serviceIdentifier string) string {
if serviceIdentifier != "" { if serviceIdentifier != "" {
return fmt.Sprintf("/%v/domains/%v/services/%v", accountID, domainID, serviceIdentifier) return fmt.Sprintf("/%v/domains/%v/services/%v", accountID, domainIdentifier, serviceIdentifier)
} }
return fmt.Sprintf("/%v/domains/%v/services", accountID, domainID) return fmt.Sprintf("/%v/domains/%v/services", accountID, domainIdentifier)
} }
// DomainServiceSettings represents optional settings when applying a DNSimple one-click service to a domain. // DomainServiceSettings represents optional settings when applying a DNSimple one-click service to a domain.
@ -19,8 +19,8 @@ type DomainServiceSettings struct {
// AppliedServices lists the applied one-click services for a domain. // AppliedServices lists the applied one-click services for a domain.
// //
// See https://developer.dnsimple.com/v2/services/domains/#applied // See https://developer.dnsimple.com/v2/services/domains/#applied
func (s *ServicesService) AppliedServices(accountID string, domainID string, options *ListOptions) (*servicesResponse, error) { func (s *ServicesService) AppliedServices(accountID string, domainIdentifier string, options *ListOptions) (*servicesResponse, error) {
path := versioned(domainServicesPath(accountID, domainID, "")) path := versioned(domainServicesPath(accountID, domainIdentifier, ""))
servicesResponse := &servicesResponse{} servicesResponse := &servicesResponse{}
path, err := addURLQueryOptions(path, options) path, err := addURLQueryOptions(path, options)
@ -40,8 +40,8 @@ func (s *ServicesService) AppliedServices(accountID string, domainID string, opt
// ApplyService applies a one-click services to a domain. // ApplyService applies a one-click services to a domain.
// //
// See https://developer.dnsimple.com/v2/services/domains/#apply // See https://developer.dnsimple.com/v2/services/domains/#apply
func (s *ServicesService) ApplyService(accountID string, serviceIdentifier string, domainID string, settings DomainServiceSettings) (*serviceResponse, error) { func (s *ServicesService) ApplyService(accountID string, serviceIdentifier string, domainIdentifier string, settings DomainServiceSettings) (*serviceResponse, error) {
path := versioned(domainServicesPath(accountID, domainID, serviceIdentifier)) path := versioned(domainServicesPath(accountID, domainIdentifier, serviceIdentifier))
serviceResponse := &serviceResponse{} serviceResponse := &serviceResponse{}
resp, err := s.client.post(path, settings, nil) resp, err := s.client.post(path, settings, nil)
@ -56,8 +56,8 @@ func (s *ServicesService) ApplyService(accountID string, serviceIdentifier strin
// UnapplyService unapplies a one-click services from a domain. // UnapplyService unapplies a one-click services from a domain.
// //
// See https://developer.dnsimple.com/v2/services/domains/#unapply // See https://developer.dnsimple.com/v2/services/domains/#unapply
func (s *ServicesService) UnapplyService(accountID string, serviceIdentifier string, domainID string) (*serviceResponse, error) { func (s *ServicesService) UnapplyService(accountID string, serviceIdentifier string, domainIdentifier string) (*serviceResponse, error) {
path := versioned(domainServicesPath(accountID, domainID, serviceIdentifier)) path := versioned(domainServicesPath(accountID, domainIdentifier, serviceIdentifier))
serviceResponse := &serviceResponse{} serviceResponse := &serviceResponse{}
resp, err := s.client.delete(path, nil, nil) resp, err := s.client.delete(path, nil, nil)

View file

@ -14,9 +14,9 @@ type TemplatesService struct {
// Template represents a Template in DNSimple. // Template represents a Template in DNSimple.
type Template struct { type Template struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
SID string `json:"sid,omitempty"` SID string `json:"sid,omitempty"`
AccountID int `json:"account_id,omitempty"` AccountID int64 `json:"account_id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"` Description string `json:"description,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`

View file

@ -7,8 +7,8 @@ import (
// ApplyTemplate applies a template to the given domain. // ApplyTemplate applies a template to the given domain.
// //
// See https://developer.dnsimple.com/v2/templates/domains/#apply // See https://developer.dnsimple.com/v2/templates/domains/#apply
func (s *TemplatesService) ApplyTemplate(accountID string, templateIdentifier string, domainID string) (*templateResponse, error) { func (s *TemplatesService) ApplyTemplate(accountID string, templateIdentifier string, domainIdentifier string) (*templateResponse, error) {
path := versioned(fmt.Sprintf("%v/templates/%v", domainPath(accountID, domainID), templateIdentifier)) path := versioned(fmt.Sprintf("%v/templates/%v", domainPath(accountID, domainIdentifier), templateIdentifier))
templateResponse := &templateResponse{} templateResponse := &templateResponse{}
resp, err := s.client.post(path, nil, nil) resp, err := s.client.post(path, nil, nil)

View file

@ -6,8 +6,8 @@ import (
// TemplateRecord represents a DNS record for a template in DNSimple. // TemplateRecord represents a DNS record for a template in DNSimple.
type TemplateRecord struct { type TemplateRecord struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
TemplateID int `json:"template_id,omitempty"` TemplateID int64 `json:"template_id,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
TTL int `json:"ttl,omitempty"` TTL int `json:"ttl,omitempty"`
@ -17,8 +17,8 @@ type TemplateRecord struct {
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
func templateRecordPath(accountID string, templateIdentifier string, templateRecordID string) string { func templateRecordPath(accountID string, templateIdentifier string, templateRecordID int64) string {
if templateRecordID != "" { if templateRecordID != 0 {
return fmt.Sprintf("%v/records/%v", templatePath(accountID, templateIdentifier), templateRecordID) return fmt.Sprintf("%v/records/%v", templatePath(accountID, templateIdentifier), templateRecordID)
} }
@ -41,7 +41,7 @@ type templateRecordsResponse struct {
// //
// See https://developer.dnsimple.com/v2/templates/records/#list // See https://developer.dnsimple.com/v2/templates/records/#list
func (s *TemplatesService) ListTemplateRecords(accountID string, templateIdentifier string, options *ListOptions) (*templateRecordsResponse, error) { func (s *TemplatesService) ListTemplateRecords(accountID string, templateIdentifier string, options *ListOptions) (*templateRecordsResponse, error) {
path := versioned(templateRecordPath(accountID, templateIdentifier, "")) path := versioned(templateRecordPath(accountID, templateIdentifier, 0))
templateRecordsResponse := &templateRecordsResponse{} templateRecordsResponse := &templateRecordsResponse{}
path, err := addURLQueryOptions(path, options) path, err := addURLQueryOptions(path, options)
@ -62,7 +62,7 @@ func (s *TemplatesService) ListTemplateRecords(accountID string, templateIdentif
// //
// See https://developer.dnsimple.com/v2/templates/records/#create // See https://developer.dnsimple.com/v2/templates/records/#create
func (s *TemplatesService) CreateTemplateRecord(accountID string, templateIdentifier string, templateRecordAttributes TemplateRecord) (*templateRecordResponse, error) { func (s *TemplatesService) CreateTemplateRecord(accountID string, templateIdentifier string, templateRecordAttributes TemplateRecord) (*templateRecordResponse, error) {
path := versioned(templateRecordPath(accountID, templateIdentifier, "")) path := versioned(templateRecordPath(accountID, templateIdentifier, 0))
templateRecordResponse := &templateRecordResponse{} templateRecordResponse := &templateRecordResponse{}
resp, err := s.client.post(path, templateRecordAttributes, templateRecordResponse) resp, err := s.client.post(path, templateRecordAttributes, templateRecordResponse)
@ -77,7 +77,7 @@ func (s *TemplatesService) CreateTemplateRecord(accountID string, templateIdenti
// GetTemplateRecord fetches a template record. // GetTemplateRecord fetches a template record.
// //
// See https://developer.dnsimple.com/v2/templates/records/#get // See https://developer.dnsimple.com/v2/templates/records/#get
func (s *TemplatesService) GetTemplateRecord(accountID string, templateIdentifier string, templateRecordID string) (*templateRecordResponse, error) { func (s *TemplatesService) GetTemplateRecord(accountID string, templateIdentifier string, templateRecordID int64) (*templateRecordResponse, error) {
path := versioned(templateRecordPath(accountID, templateIdentifier, templateRecordID)) path := versioned(templateRecordPath(accountID, templateIdentifier, templateRecordID))
templateRecordResponse := &templateRecordResponse{} templateRecordResponse := &templateRecordResponse{}
@ -93,7 +93,7 @@ func (s *TemplatesService) GetTemplateRecord(accountID string, templateIdentifie
// DeleteTemplateRecord deletes a template record. // DeleteTemplateRecord deletes a template record.
// //
// See https://developer.dnsimple.com/v2/templates/records/#delete // See https://developer.dnsimple.com/v2/templates/records/#delete
func (s *TemplatesService) DeleteTemplateRecord(accountID string, templateIdentifier string, templateRecordID string) (*templateRecordResponse, error) { func (s *TemplatesService) DeleteTemplateRecord(accountID string, templateIdentifier string, templateRecordID int64) (*templateRecordResponse, error) {
path := versioned(templateRecordPath(accountID, templateIdentifier, templateRecordID)) path := versioned(templateRecordPath(accountID, templateIdentifier, templateRecordID))
templateRecordResponse := &templateRecordResponse{} templateRecordResponse := &templateRecordResponse{}

View file

@ -2,6 +2,6 @@ package dnsimple
// User represents a DNSimple user. // User represents a DNSimple user.
type User struct { type User struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
} }

View file

@ -14,7 +14,7 @@ type VanityNameServersService struct {
// VanityNameServer represents data for a single vanity name server // VanityNameServer represents data for a single vanity name server
type VanityNameServer struct { type VanityNameServer struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
IPv4 string `json:"ipv4,omitempty"` IPv4 string `json:"ipv4,omitempty"`
IPv6 string `json:"ipv6,omitempty"` IPv6 string `json:"ipv6,omitempty"`
@ -22,8 +22,8 @@ type VanityNameServer struct {
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
func vanityNameServerPath(accountID string, domainID string) string { func vanityNameServerPath(accountID string, domainIdentifier string) string {
return fmt.Sprintf("/%v/vanity/%v", accountID, domainID) return fmt.Sprintf("/%v/vanity/%v", accountID, domainIdentifier)
} }
// vanityNameServerResponse represents a response for vanity name server enable and disable operations. // vanityNameServerResponse represents a response for vanity name server enable and disable operations.
@ -35,8 +35,8 @@ type vanityNameServerResponse struct {
// EnableVanityNameServers Vanity Name Servers for the given domain // EnableVanityNameServers Vanity Name Servers for the given domain
// //
// See https://developer.dnsimple.com/v2/vanity/#enable // See https://developer.dnsimple.com/v2/vanity/#enable
func (s *VanityNameServersService) EnableVanityNameServers(accountID string, domainID string) (*vanityNameServerResponse, error) { func (s *VanityNameServersService) EnableVanityNameServers(accountID string, domainIdentifier string) (*vanityNameServerResponse, error) {
path := versioned(vanityNameServerPath(accountID, domainID)) path := versioned(vanityNameServerPath(accountID, domainIdentifier))
vanityNameServerResponse := &vanityNameServerResponse{} vanityNameServerResponse := &vanityNameServerResponse{}
resp, err := s.client.put(path, nil, vanityNameServerResponse) resp, err := s.client.put(path, nil, vanityNameServerResponse)
@ -51,8 +51,8 @@ func (s *VanityNameServersService) EnableVanityNameServers(accountID string, dom
// DisableVanityNameServers Vanity Name Servers for the given domain // DisableVanityNameServers Vanity Name Servers for the given domain
// //
// See https://developer.dnsimple.com/v2/vanity/#disable // See https://developer.dnsimple.com/v2/vanity/#disable
func (s *VanityNameServersService) DisableVanityNameServers(accountID string, domainID string) (*vanityNameServerResponse, error) { func (s *VanityNameServersService) DisableVanityNameServers(accountID string, domainIdentifier string) (*vanityNameServerResponse, error) {
path := versioned(vanityNameServerPath(accountID, domainID)) path := versioned(vanityNameServerPath(accountID, domainIdentifier))
vanityNameServerResponse := &vanityNameServerResponse{} vanityNameServerResponse := &vanityNameServerResponse{}
resp, err := s.client.delete(path, nil, nil) resp, err := s.client.delete(path, nil, nil)

View file

@ -14,11 +14,11 @@ type WebhooksService struct {
// Webhook represents a DNSimple webhook. // Webhook represents a DNSimple webhook.
type Webhook struct { type Webhook struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
} }
func webhookPath(accountID string, webhookID int) (path string) { func webhookPath(accountID string, webhookID int64) (path string) {
path = fmt.Sprintf("/%v/webhooks", accountID) path = fmt.Sprintf("/%v/webhooks", accountID)
if webhookID != 0 { if webhookID != 0 {
path = fmt.Sprintf("%v/%v", path, webhookID) path = fmt.Sprintf("%v/%v", path, webhookID)
@ -73,7 +73,7 @@ func (s *WebhooksService) CreateWebhook(accountID string, webhookAttributes Webh
// GetWebhook fetches a webhook. // GetWebhook fetches a webhook.
// //
// See https://developer.dnsimple.com/v2/webhooks#get // See https://developer.dnsimple.com/v2/webhooks#get
func (s *WebhooksService) GetWebhook(accountID string, webhookID int) (*webhookResponse, error) { func (s *WebhooksService) GetWebhook(accountID string, webhookID int64) (*webhookResponse, error) {
path := versioned(webhookPath(accountID, webhookID)) path := versioned(webhookPath(accountID, webhookID))
webhookResponse := &webhookResponse{} webhookResponse := &webhookResponse{}
@ -89,7 +89,7 @@ func (s *WebhooksService) GetWebhook(accountID string, webhookID int) (*webhookR
// DeleteWebhook PERMANENTLY deletes a webhook from the account. // DeleteWebhook PERMANENTLY deletes a webhook from the account.
// //
// See https://developer.dnsimple.com/v2/webhooks#delete // See https://developer.dnsimple.com/v2/webhooks#delete
func (s *WebhooksService) DeleteWebhook(accountID string, webhookID int) (*webhookResponse, error) { func (s *WebhooksService) DeleteWebhook(accountID string, webhookID int64) (*webhookResponse, error) {
path := versioned(webhookPath(accountID, webhookID)) path := versioned(webhookPath(accountID, webhookID))
webhookResponse := &webhookResponse{} webhookResponse := &webhookResponse{}

View file

@ -14,8 +14,8 @@ type ZonesService struct {
// Zone represents a Zone in DNSimple. // Zone represents a Zone in DNSimple.
type Zone struct { type Zone struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
AccountID int `json:"account_id,omitempty"` AccountID int64 `json:"account_id,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Reverse bool `json:"reverse,omitempty"` Reverse bool `json:"reverse,omitempty"`
CreatedAt string `json:"created_at,omitempty"` CreatedAt string `json:"created_at,omitempty"`

View file

@ -6,9 +6,9 @@ import (
// ZoneRecord represents a DNS record in DNSimple. // ZoneRecord represents a DNS record in DNSimple.
type ZoneRecord struct { type ZoneRecord struct {
ID int `json:"id,omitempty"` ID int64 `json:"id,omitempty"`
ZoneID string `json:"zone_id,omitempty"` ZoneID string `json:"zone_id,omitempty"`
ParentID int `json:"parent_id,omitempty"` ParentID int64 `json:"parent_id,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Name string `json:"name"` Name string `json:"name"`
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
@ -20,10 +20,10 @@ type ZoneRecord struct {
UpdatedAt string `json:"updated_at,omitempty"` UpdatedAt string `json:"updated_at,omitempty"`
} }
func zoneRecordPath(accountID string, zoneID string, recordID int) (path string) { func zoneRecordPath(accountID string, zoneName string, recordID int64) (path string) {
path = fmt.Sprintf("/%v/zones/%v/records", accountID, zoneID) path = fmt.Sprintf("/%v/zones/%v/records", accountID, zoneName)
if recordID != 0 { if recordID != 0 {
path += fmt.Sprintf("/%d", recordID) path += fmt.Sprintf("/%v", recordID)
} }
return return
} }
@ -51,16 +51,16 @@ type ZoneRecordListOptions struct {
// Select records of given type. // Select records of given type.
// Eg. TXT, A, NS. // Eg. TXT, A, NS.
Type string `url:"record_type,omitempty"` Type string `url:"type,omitempty"`
ListOptions ListOptions
} }
// ListRecords lists the zone records for a zone. // ListRecords lists the zone records for a zone.
// //
// See https://developer.dnsimple.com/v2/zones/#list // See https://developer.dnsimple.com/v2/zones/records/#listZoneRecords
func (s *ZonesService) ListRecords(accountID string, zoneID string, options *ZoneRecordListOptions) (*zoneRecordsResponse, error) { func (s *ZonesService) ListRecords(accountID string, zoneName string, options *ZoneRecordListOptions) (*zoneRecordsResponse, error) {
path := versioned(zoneRecordPath(accountID, zoneID, 0)) path := versioned(zoneRecordPath(accountID, zoneName, 0))
recordsResponse := &zoneRecordsResponse{} recordsResponse := &zoneRecordsResponse{}
path, err := addURLQueryOptions(path, options) path, err := addURLQueryOptions(path, options)
@ -79,9 +79,9 @@ func (s *ZonesService) ListRecords(accountID string, zoneID string, options *Zon
// CreateRecord creates a zone record. // CreateRecord creates a zone record.
// //
// See https://developer.dnsimple.com/v2/zones/#create // See https://developer.dnsimple.com/v2/zones/records/#createZoneRecord
func (s *ZonesService) CreateRecord(accountID string, zoneID string, recordAttributes ZoneRecord) (*zoneRecordResponse, error) { func (s *ZonesService) CreateRecord(accountID string, zoneName string, recordAttributes ZoneRecord) (*zoneRecordResponse, error) {
path := versioned(zoneRecordPath(accountID, zoneID, 0)) path := versioned(zoneRecordPath(accountID, zoneName, 0))
recordResponse := &zoneRecordResponse{} recordResponse := &zoneRecordResponse{}
resp, err := s.client.post(path, recordAttributes, recordResponse) resp, err := s.client.post(path, recordAttributes, recordResponse)
@ -95,9 +95,9 @@ func (s *ZonesService) CreateRecord(accountID string, zoneID string, recordAttri
// GetRecord fetches a zone record. // GetRecord fetches a zone record.
// //
// See https://developer.dnsimple.com/v2/zones/#get // See https://developer.dnsimple.com/v2/zones/records/#getZoneRecord
func (s *ZonesService) GetRecord(accountID string, zoneID string, recordID int) (*zoneRecordResponse, error) { func (s *ZonesService) GetRecord(accountID string, zoneName string, recordID int64) (*zoneRecordResponse, error) {
path := versioned(zoneRecordPath(accountID, zoneID, recordID)) path := versioned(zoneRecordPath(accountID, zoneName, recordID))
recordResponse := &zoneRecordResponse{} recordResponse := &zoneRecordResponse{}
resp, err := s.client.get(path, recordResponse) resp, err := s.client.get(path, recordResponse)
@ -111,9 +111,9 @@ func (s *ZonesService) GetRecord(accountID string, zoneID string, recordID int)
// UpdateRecord updates a zone record. // UpdateRecord updates a zone record.
// //
// See https://developer.dnsimple.com/v2/zones/#update // See https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
func (s *ZonesService) UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes ZoneRecord) (*zoneRecordResponse, error) { func (s *ZonesService) UpdateRecord(accountID string, zoneName string, recordID int64, recordAttributes ZoneRecord) (*zoneRecordResponse, error) {
path := versioned(zoneRecordPath(accountID, zoneID, recordID)) path := versioned(zoneRecordPath(accountID, zoneName, recordID))
recordResponse := &zoneRecordResponse{} recordResponse := &zoneRecordResponse{}
resp, err := s.client.patch(path, recordAttributes, recordResponse) resp, err := s.client.patch(path, recordAttributes, recordResponse)
@ -127,9 +127,9 @@ func (s *ZonesService) UpdateRecord(accountID string, zoneID string, recordID in
// DeleteRecord PERMANENTLY deletes a zone record from the zone. // DeleteRecord PERMANENTLY deletes a zone record from the zone.
// //
// See https://developer.dnsimple.com/v2/zones/#delete // See https://developer.dnsimple.com/v2/zones/records/#deleteZoneRecord
func (s *ZonesService) DeleteRecord(accountID string, zoneID string, recordID int) (*zoneRecordResponse, error) { func (s *ZonesService) DeleteRecord(accountID string, zoneName string, recordID int64) (*zoneRecordResponse, error) {
path := versioned(zoneRecordPath(accountID, zoneID, recordID)) path := versioned(zoneRecordPath(accountID, zoneName, recordID))
recordResponse := &zoneRecordResponse{} recordResponse := &zoneRecordResponse{}
resp, err := s.client.delete(path, nil, nil) resp, err := s.client.delete(path, nil, nil)

View file

@ -19,11 +19,18 @@ var (
// currentUserHome attempts to get current user's home directory // currentUserHome attempts to get current user's home directory
func currentUserHome() (string, error) { func currentUserHome() (string, error) {
userHome := ""
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
return "", err // Fallback by trying to read $HOME
userHome = os.Getenv("HOME")
if userHome != "" {
err = nil
}
} else {
userHome = usr.HomeDir
} }
return usr.HomeDir, nil return userHome, nil
} }
// appendConfigurationFile only if it exists. We need to do this because // appendConfigurationFile only if it exists. We need to do this because
@ -88,13 +95,13 @@ func (c *Client) loadConfig(endpointName string) error {
// If we still have no valid endpoint, AppKey or AppSecret, return an error // If we still have no valid endpoint, AppKey or AppSecret, return an error
if c.endpoint == "" { if c.endpoint == "" {
return fmt.Errorf("Unknown endpoint '%s'. Consider checking 'Endpoints' list of using an URL.", endpointName) return fmt.Errorf("unknown endpoint '%s', consider checking 'Endpoints' list of using an URL", endpointName)
} }
if c.AppKey == "" { if c.AppKey == "" {
return fmt.Errorf("Missing application key. Please check your configuration or consult the documentation to create one.") return fmt.Errorf("missing application key, please check your configuration or consult the documentation to create one")
} }
if c.AppSecret == "" { if c.AppSecret == "" {
return fmt.Errorf("Missing application secret. Please check your configuration or consult the documentation to create one.") return fmt.Errorf("missing application secret, please check your configuration or consult the documentation to create one")
} }
return nil return nil

View file

@ -21,6 +21,7 @@ const DefaultTimeout = 180 * time.Second
const ( const (
OvhEU = "https://eu.api.ovh.com/1.0" OvhEU = "https://eu.api.ovh.com/1.0"
OvhCA = "https://ca.api.ovh.com/1.0" OvhCA = "https://ca.api.ovh.com/1.0"
OvhUS = "https://api.ovh.us/1.0"
KimsufiEU = "https://eu.api.kimsufi.com/1.0" KimsufiEU = "https://eu.api.kimsufi.com/1.0"
KimsufiCA = "https://ca.api.kimsufi.com/1.0" KimsufiCA = "https://ca.api.kimsufi.com/1.0"
SoyoustartEU = "https://eu.api.soyoustart.com/1.0" SoyoustartEU = "https://eu.api.soyoustart.com/1.0"
@ -32,6 +33,7 @@ const (
var Endpoints = map[string]string{ var Endpoints = map[string]string{
"ovh-eu": OvhEU, "ovh-eu": OvhEU,
"ovh-ca": OvhCA, "ovh-ca": OvhCA,
"ovh-us": OvhUS,
"kimsufi-eu": KimsufiEU, "kimsufi-eu": KimsufiEU,
"kimsufi-ca": KimsufiCA, "kimsufi-ca": KimsufiCA,
"soyoustart-eu": SoyoustartEU, "soyoustart-eu": SoyoustartEU,
@ -170,39 +172,6 @@ func (c *Client) DeleteUnAuth(url string, resType interface{}) error {
return c.CallAPI("DELETE", url, nil, resType, false) return c.CallAPI("DELETE", url, nil, resType, false)
} }
//
// Low level API access
//
// getResult check the response and unmarshals it into the response type if needed.
// Helper function, called from CallAPI.
func (c *Client) getResponse(response *http.Response, resType interface{}) error {
// Read all the response body
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
// < 200 && >= 300 : API error
if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
apiError := &APIError{Code: response.StatusCode}
if err = json.Unmarshal(body, apiError); err != nil {
apiError.Message = string(body)
}
apiError.QueryID = response.Header.Get("X-Ovh-QueryID")
return apiError
}
// Nothing to unmarshal
if len(body) == 0 || resType == nil {
return nil
}
return json.Unmarshal(body, &resType)
}
// timeDelta returns the time delta between the host and the remote API // timeDelta returns the time delta between the host and the remote API
func (c *Client) getTimeDelta() (time.Duration, error) { func (c *Client) getTimeDelta() (time.Duration, error) {
@ -210,6 +179,9 @@ func (c *Client) getTimeDelta() (time.Duration, error) {
// Ensure only one thread is updating // Ensure only one thread is updating
c.timeDeltaMutex.Lock() c.timeDeltaMutex.Lock()
// Ensure that the mutex will be released on return
defer c.timeDeltaMutex.Unlock()
// Did we wait ? Maybe no more needed // Did we wait ? Maybe no more needed
if !c.timeDeltaDone { if !c.timeDeltaDone {
ovhTime, err := c.getTime() ovhTime, err := c.getTime()
@ -220,7 +192,6 @@ func (c *Client) getTimeDelta() (time.Duration, error) {
c.timeDelta = time.Since(*ovhTime) c.timeDelta = time.Since(*ovhTime)
c.timeDeltaDone = true c.timeDeltaDone = true
} }
c.timeDeltaMutex.Unlock()
} }
return c.timeDelta, nil return c.timeDelta, nil
@ -251,39 +222,22 @@ var getEndpointForSignature = func(c *Client) string {
return c.endpoint return c.endpoint
} }
// CallAPI is the lowest level call helper. If needAuth is true, // NewRequest returns a new HTTP request
// inject authentication headers and sign the request. func (c *Client) NewRequest(method, path string, reqBody interface{}, needAuth bool) (*http.Request, error) {
//
// Request signature is a sha1 hash on following fields, joined by '+':
// - applicationSecret (from Client instance)
// - consumerKey (from Client instance)
// - capitalized method (from arguments)
// - full request url, including any query string argument
// - full serialized request body
// - server current time (takes time delta into account)
//
// Call will automatically assemble the target url from the endpoint
// configured in the client instance and the path argument. If the reqBody
// argument is not nil, it will also serialize it as json and inject
// the required Content-Type header.
//
// If everyrthing went fine, unmarshall response into resType and return nil
// otherwise, return the error
func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error {
var body []byte var body []byte
var err error var err error
if reqBody != nil { if reqBody != nil {
body, err = json.Marshal(reqBody) body, err = json.Marshal(reqBody)
if err != nil { if err != nil {
return err return nil, err
} }
} }
target := fmt.Sprintf("%s%s", c.endpoint, path) target := fmt.Sprintf("%s%s", c.endpoint, path)
req, err := http.NewRequest(method, target, bytes.NewReader(body)) req, err := http.NewRequest(method, target, bytes.NewReader(body))
if err != nil { if err != nil {
return err return nil, err
} }
// Inject headers // Inject headers
@ -298,7 +252,7 @@ func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, need
if needAuth { if needAuth {
timeDelta, err := c.TimeDelta() timeDelta, err := c.TimeDelta()
if err != nil { if err != nil {
return err return nil, err
} }
timestamp := getLocalTime().Add(-timeDelta).Unix() timestamp := getLocalTime().Add(-timeDelta).Unix()
@ -321,12 +275,71 @@ func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, need
// Send the request with requested timeout // Send the request with requested timeout
c.Client.Timeout = c.Timeout c.Client.Timeout = c.Timeout
response, err := c.Client.Do(req)
return req, nil
}
// Do sends an HTTP request and returns an HTTP response
func (c *Client) Do(req *http.Request) (*http.Response, error) {
return c.Client.Do(req)
}
// CallAPI is the lowest level call helper. If needAuth is true,
// inject authentication headers and sign the request.
//
// Request signature is a sha1 hash on following fields, joined by '+':
// - applicationSecret (from Client instance)
// - consumerKey (from Client instance)
// - capitalized method (from arguments)
// - full request url, including any query string argument
// - full serialized request body
// - server current time (takes time delta into account)
//
// Call will automatically assemble the target url from the endpoint
// configured in the client instance and the path argument. If the reqBody
// argument is not nil, it will also serialize it as json and inject
// the required Content-Type header.
//
// If everything went fine, unmarshall response into resType and return nil
// otherwise, return the error
func (c *Client) CallAPI(method, path string, reqBody, resType interface{}, needAuth bool) error {
req, err := c.NewRequest(method, path, reqBody, needAuth)
if err != nil {
return err
}
response, err := c.Do(req)
if err != nil {
return err
}
return c.UnmarshalResponse(response, resType)
}
// UnmarshalResponse checks the response and unmarshals it into the response
// type if needed Helper function, called from CallAPI
func (c *Client) UnmarshalResponse(response *http.Response, resType interface{}) error {
// Read all the response body
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil { if err != nil {
return err return err
} }
// Unmarshal the result into the resType if possible // < 200 && >= 300 : API error
return c.getResponse(response, resType) if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
apiError := &APIError{Code: response.StatusCode}
if err = json.Unmarshal(body, apiError); err != nil {
apiError.Message = string(body)
}
apiError.QueryID = response.Header.Get("X-Ovh-QueryID")
return apiError
}
// Nothing to unmarshal
if len(body) == 0 || resType == nil {
return nil
}
return json.Unmarshal(body, &resType)
} }

View file

@ -1,13 +1,13 @@
package acmev2 package acme
// Challenge is a string that identifies a particular type and version of ACME challenge. // Challenge is a string that identifies a particular type and version of ACME challenge.
type Challenge string type Challenge string
const ( const (
// HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acmev2.md#http // HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http
// Note: HTTP01ChallengePath returns the URL path to fulfill this challenge // Note: HTTP01ChallengePath returns the URL path to fulfill this challenge
HTTP01 = Challenge("http-01") HTTP01 = Challenge("http-01")
// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acmev2.md#dns // DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
// Note: DNS01Record returns a DNS record which will fulfill this challenge // Note: DNS01Record returns a DNS record which will fulfill this challenge
DNS01 = Challenge("dns-01") DNS01 = Challenge("dns-01")
) )

View file

@ -1,5 +1,5 @@
// package acmev2 implements the ACME protocol for Let's Encrypt and other conforming providers. // Package acme implements the ACME protocol for Let's Encrypt and other conforming providers.
package acmev2 package acme
import ( import (
"crypto" "crypto"
@ -8,17 +8,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
)
var ( "github.com/xenolf/lego/log"
// Logger is an optional custom logger.
Logger *log.Logger
) )
const ( const (
@ -31,16 +27,6 @@ const (
overallRequestLimit = 18 overallRequestLimit = 18
) )
// logf writes a log entry. It uses Logger if not
// nil, otherwise it uses the default log.Logger.
func logf(format string, args ...interface{}) {
if Logger != nil {
Logger.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
// User interface is to be implemented by users of this library. // User interface is to be implemented by users of this library.
// It is used by the client type to get user specific information. // It is used by the client type to get user specific information.
type User interface { type User interface {
@ -86,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 {
@ -149,12 +132,17 @@ func (c *Client) GetToSURL() string {
return c.directory.Meta.TermsOfService return c.directory.Meta.TermsOfService
} }
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
func (c *Client) GetExternalAccountRequired() bool {
return c.directory.Meta.ExternalAccountRequired
}
// Register the current account to the ACME server. // Register the current account to the ACME server.
func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) { func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
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 for %s", c.user.GetEmail()) log.Printf("[INFO] acme: Registering account for %s", c.user.GetEmail())
accMsg := accountMessage{} accMsg := accountMessage{}
if c.user.GetEmail() != "" { if c.user.GetEmail() != "" {
@ -183,10 +171,58 @@ func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
return reg, nil return reg, nil
} }
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string, hmacEncoded string) (*RegistrationResource, error) {
if c == nil || c.user == nil {
return nil, errors.New("acme: cannot register a nil client or user")
}
log.Printf("[INFO] acme: Registering account (EAB) for %s", c.user.GetEmail())
accMsg := accountMessage{}
if c.user.GetEmail() != "" {
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
} else {
accMsg.Contact = []string{}
}
accMsg.TermsOfServiceAgreed = tosAgreed
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
if err != nil {
return nil, fmt.Errorf("acme: could not decode hmac key: %s", err.Error())
}
eabJWS, err := c.jws.signEABContent(c.directory.NewAccountURL, kid, hmac)
if err != nil {
return nil, fmt.Errorf("acme: error signing eab content: %s", err.Error())
}
eabPayload := eabJWS.FullSerialize()
accMsg.ExternalAccountBinding = []byte(eabPayload)
var serverReg accountMessage
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
if err != nil {
remoteErr, ok := err.(RemoteError)
if ok && remoteErr.StatusCode == 409 {
} else {
return nil, err
}
}
reg := &RegistrationResource{
URI: hdr.Get("Location"),
Body: serverReg,
}
c.jws.kid = reg.URI
return reg, nil
}
// ResolveAccountByKey will attempt to look up an account using the given account key // ResolveAccountByKey will attempt to look up an account using the given account key
// and return its registration resource. // and return its registration resource.
func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) { func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
logf("[INFO] acme: Trying to resolve account by key") log.Printf("[INFO] acme: Trying to resolve account by key")
acc := accountMessage{OnlyReturnExisting: true} acc := accountMessage{OnlyReturnExisting: true}
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil) hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil)
@ -201,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
} }
@ -215,18 +251,14 @@ func (c *Client) DeleteRegistration() error {
if c == nil || c.user == nil { if c == nil || c.user == nil {
return errors.New("acme: cannot unregister a nil client or user") return errors.New("acme: cannot unregister a nil client or user")
} }
logf("[INFO] acme: Deleting account for %s", c.user.GetEmail()) log.Printf("[INFO] acme: Deleting account for %s", c.user.GetEmail())
accMsg := accountMessage{ accMsg := accountMessage{
Status: "deactivated", Status: "deactivated",
} }
_, 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
@ -239,7 +271,7 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) {
return nil, errors.New("acme: cannot query the registration of a nil client or user") return nil, errors.New("acme: cannot query the registration of a nil client or user")
} }
// Log the URL here instead of the email as the email may not be set // Log the URL here instead of the email as the email may not be set
logf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI) log.Printf("[INFO] acme: Querying account for %s", c.user.GetRegistration().URI)
accMsg := accountMessage{} accMsg := accountMessage{}
@ -265,7 +297,7 @@ func (c *Client) QueryRegistration() (*RegistrationResource, error) {
// your issued certificate as a bundle. // your issued certificate as a bundle.
// This function will never return a partial certificate. If one domain in the list fails, // This function will never return a partial certificate. If one domain in the list fails,
// the whole certificate will fail. // the whole certificate will fail.
func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (CertificateResource, error) { func (c *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (*CertificateResource, error) {
// figure out what domains it concerns // figure out what domains it concerns
// start with the common name // start with the common name
domains := []string{csr.Subject.CommonName} domains := []string{csr.Subject.CommonName}
@ -285,14 +317,14 @@ DNSNames:
} }
if bundle { if bundle {
logf("[INFO][%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", ")) log.Printf("[INFO][%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
} else { } else {
logf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", ")) log.Printf("[INFO][%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
} }
order, err := c.createOrderForIdentifiers(domains) order, err := c.createOrderForIdentifiers(domains)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
authz, err := c.getAuthzForOrder(order) authz, err := c.getAuthzForOrder(order)
if err != nil { if err != nil {
@ -300,16 +332,16 @@ DNSNames:
/*for _, auth := range authz { /*for _, auth := range authz {
c.disableAuthz(auth) c.disableAuthz(auth)
}*/ }*/
return CertificateResource{}, err return nil, err
} }
err = c.solveChallengeForAuthz(authz) err = c.solveChallengeForAuthz(authz)
if err != nil { if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates. // If any challenge fails, return. Do not generate partial SAN certificates.
return CertificateResource{}, err return nil, err
} }
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) log.Printf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(ObtainError) failures := make(ObtainError)
cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil) cert, err := c.requestCertificateForCsr(order, bundle, csr.Raw, nil)
@ -339,20 +371,20 @@ DNSNames:
// your issued certificate as a bundle. // your issued certificate as a bundle.
// This function will never return a partial certificate. If one domain in the list fails, // This function will never return a partial certificate. If one domain in the list fails,
// the whole certificate will fail. // the whole certificate will fail.
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) { func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
if len(domains) == 0 { if len(domains) == 0 {
return CertificateResource{}, errors.New("No domains to obtain a certificate for") return nil, errors.New("No domains to obtain a certificate for")
} }
if bundle { if bundle {
logf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", ")) log.Printf("[INFO][%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
} else { } else {
logf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", ")) log.Printf("[INFO][%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
} }
order, err := c.createOrderForIdentifiers(domains) order, err := c.createOrderForIdentifiers(domains)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
authz, err := c.getAuthzForOrder(order) authz, err := c.getAuthzForOrder(order)
if err != nil { if err != nil {
@ -360,16 +392,16 @@ func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto
/*for _, auth := range authz { /*for _, auth := range authz {
c.disableAuthz(auth) c.disableAuthz(auth)
}*/ }*/
return CertificateResource{}, err return nil, err
} }
err = c.solveChallengeForAuthz(authz) err = c.solveChallengeForAuthz(authz)
if err != nil { if err != nil {
// If any challenge fails, return. Do not generate partial SAN certificates. // If any challenge fails, return. Do not generate partial SAN certificates.
return CertificateResource{}, err return nil, err
} }
logf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", ")) log.Printf("[INFO][%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
failures := make(ObtainError) failures := make(ObtainError)
cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple) cert, err := c.requestCertificateForOrder(order, bundle, privKey, mustStaple)
@ -413,22 +445,22 @@ func (c *Client) RevokeCertificate(certificate []byte) error {
// If bundle is true, the []byte contains both the issuer certificate and // If bundle is true, the []byte contains both the issuer certificate and
// your issued certificate as a bundle. // your issued certificate as a bundle.
// For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil. // For private key reuse the PrivateKey property of the passed in CertificateResource should be non-nil.
func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (CertificateResource, error) { func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (*CertificateResource, error) {
// Input certificate is PEM encoded. Decode it here as we may need the decoded // Input certificate is PEM encoded. Decode it here as we may need the decoded
// cert later on in the renewal process. The input may be a bundle or a single certificate. // cert later on in the renewal process. The input may be a bundle or a single certificate.
certificates, err := parsePEMBundle(cert.Certificate) certificates, err := parsePEMBundle(cert.Certificate)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
x509Cert := certificates[0] x509Cert := certificates[0]
if x509Cert.IsCA { if x509Cert.IsCA {
return CertificateResource{}, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain) return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", cert.Domain)
} }
// This is just meant to be informal for the user. // This is just meant to be informal for the user.
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC()) timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
logf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours())) log.Printf("[INFO][%s] acme: Trying renewal with %d hours remaining", cert.Domain, int(timeLeft.Hours()))
// We always need to request a new certificate to renew. // We always need to request a new certificate to renew.
// Start by checking to see if the certificate was based off a CSR, and // Start by checking to see if the certificate was based off a CSR, and
@ -436,7 +468,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
if len(cert.CSR) > 0 { if len(cert.CSR) > 0 {
csr, err := pemDecodeTox509CSR(cert.CSR) csr, err := pemDecodeTox509CSR(cert.CSR)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
newCert, failures := c.ObtainCertificateForCSR(*csr, bundle) newCert, failures := c.ObtainCertificateForCSR(*csr, bundle)
return newCert, failures return newCert, failures
@ -446,7 +478,7 @@ func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple b
if cert.PrivateKey != nil { if cert.PrivateKey != nil {
privKey, err = parsePEMPrivateKey(cert.PrivateKey) privKey, err = parsePEMPrivateKey(cert.PrivateKey)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
} }
@ -502,7 +534,7 @@ func (c *Client) solveChallengeForAuthz(authorizations []authorization) error {
for _, authz := range authorizations { for _, authz := range authorizations {
if authz.Status == "valid" { if authz.Status == "valid" {
// Boulder might recycle recent validated authz (see issue #267) // Boulder might recycle recent validated authz (see issue #267)
logf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value) log.Printf("[INFO][%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value)
continue continue
} }
@ -533,7 +565,7 @@ func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) {
if solver, ok := c.solvers[Challenge(challenge.Type)]; ok { if solver, ok := c.solvers[Challenge(challenge.Type)]; ok {
return i, solver return i, solver
} }
logf("[INFO][%s] acme: Could not find solver for: %s", domain, challenge.Type) log.Printf("[INFO][%s] acme: Could not find solver for: %s", domain, challenge.Type)
} }
return 0, nil return 0, nil
} }
@ -585,7 +617,7 @@ func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error)
func logAuthz(order orderResource) { func logAuthz(order orderResource) {
for i, auth := range order.Authorizations { for i, auth := range order.Authorizations {
logf("[INFO][%s] AuthURL: %s", order.Identifiers[i].Value, auth) log.Printf("[INFO][%s] AuthURL: %s", order.Identifiers[i].Value, auth)
} }
} }
@ -596,13 +628,13 @@ func (c *Client) disableAuthz(authURL string) error {
return err return err
} }
func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (CertificateResource, error) { func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
var err error var err error
if privKey == nil { if privKey == nil {
privKey, err = generatePrivateKey(c.keyType) privKey, err = generatePrivateKey(c.keyType)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
} }
@ -616,24 +648,24 @@ func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, pr
// TODO: should the CSR be customizable? // TODO: should the CSR be customizable?
csr, err := generateCsr(privKey, commonName, san, mustStaple) csr, err := generateCsr(privKey, commonName, san, mustStaple)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey)) return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey))
} }
func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (CertificateResource, error) { func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (*CertificateResource, error) {
commonName := order.Domains[0] commonName := order.Domains[0]
csrString := base64.RawURLEncoding.EncodeToString(csr) csrString := base64.RawURLEncoding.EncodeToString(csr)
var retOrder orderMessage var retOrder orderMessage
_, error := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder) _, error := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
if error != nil { if error != nil {
return CertificateResource{}, error return nil, error
} }
if retOrder.Status == "invalid" { if retOrder.Status == "invalid" {
return CertificateResource{}, error return nil, error
} }
certRes := CertificateResource{ certRes := CertificateResource{
@ -646,11 +678,11 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr
// if the certificate is available right away, short cut! // if the certificate is available right away, short cut!
ok, err := c.checkCertResponse(retOrder, &certRes, bundle) ok, err := c.checkCertResponse(retOrder, &certRes, bundle)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
if ok { if ok {
return certRes, nil return &certRes, nil
} }
} }
@ -658,21 +690,21 @@ func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr
for i := 0; i < maxChecks; i++ { for i := 0; i < maxChecks; i++ {
_, err := getJSON(order.URL, &retOrder) _, err := getJSON(order.URL, &retOrder)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
done, err := c.checkCertResponse(retOrder, &certRes, bundle) done, err := c.checkCertResponse(retOrder, &certRes, bundle)
if err != nil { if err != nil {
return CertificateResource{}, err return nil, err
} }
if done { if done {
break break
} }
if i == maxChecks-1 { if i == maxChecks-1 {
return CertificateResource{}, fmt.Errorf("polled for certificate %d times; giving up", i) return nil, fmt.Errorf("polled for certificate %d times; giving up", i)
} }
} }
return certRes, nil return &certRes, nil
} }
// checkCertResponse checks to see if the certificate is ready and a link is contained in the // checkCertResponse checks to see if the certificate is ready and a link is contained in the
@ -702,7 +734,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou
if err != nil { if err != nil {
// If we fail to acquire the issuer cert, return the issued certificate - do not fail. // If we fail to acquire the issuer cert, return the issued certificate - do not fail.
logf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err) log.Printf("[WARNING][%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
} else { } else {
issuerCert = pemEncode(derCertificateBytes(issuerCert)) issuerCert = pemEncode(derCertificateBytes(issuerCert))
@ -719,7 +751,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou
certRes.Certificate = cert certRes.Certificate = cert
certRes.CertURL = order.Certificate certRes.CertURL = order.Certificate
certRes.CertStableURL = order.Certificate certRes.CertStableURL = order.Certificate
logf("[INFO][%s] Server responded with a certificate.", certRes.Domain) log.Printf("[INFO][%s] Server responded with a certificate.", certRes.Domain)
return true, nil return true, nil
case "processing": case "processing":
@ -733,7 +765,7 @@ func (c *Client) checkCertResponse(order orderMessage, certRes *CertificateResou
// getIssuerCertificate requests the issuer certificate // getIssuerCertificate requests the issuer certificate
func (c *Client) getIssuerCertificate(url string) ([]byte, error) { func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
logf("[INFO] acme: Requesting issuer cert from %s", url) log.Printf("[INFO] acme: Requesting issuer cert from %s", url)
resp, err := httpGet(url) resp, err := httpGet(url)
if err != nil { if err != nil {
return nil, err return nil, err
@ -787,14 +819,13 @@ func validate(j *jws, domain, uri string, c challenge) error {
for { for {
switch chlng.Status { switch chlng.Status {
case "valid": case "valid":
logf("[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

@ -1,4 +1,4 @@
package acmev2 package acme
import ( import (
"bytes" "bytes"
@ -9,6 +9,7 @@ import (
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"errors" "errors"
@ -19,8 +20,6 @@ import (
"net/http" "net/http"
"time" "time"
"encoding/asn1"
"golang.org/x/crypto/ocsp" "golang.org/x/crypto/ocsp"
jose "gopkg.in/square/go-jose.v2" jose "gopkg.in/square/go-jose.v2"
) )
@ -118,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
@ -138,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 {
@ -173,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
@ -188,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")
} }
} }
@ -207,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) {
@ -239,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

@ -1,16 +1,16 @@
package acmev2 package acme
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"log"
"net" "net"
"strings" "strings"
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/xenolf/lego/log"
) )
type preCheckDNSFunc func(fqdn, value string) (bool, error) type preCheckDNSFunc func(fqdn, value string) (bool, error)
@ -72,7 +72,7 @@ type dnsChallenge struct {
} }
func (s *dnsChallenge) Solve(chlng challenge, domain string) error { func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
logf("[INFO][%s] acme: Trying to solve DNS-01", domain) log.Printf("[INFO][%s] acme: Trying to solve DNS-01", domain)
if s.provider == nil { if s.provider == nil {
return errors.New("No DNS Provider configured") return errors.New("No DNS Provider configured")
@ -97,7 +97,7 @@ func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
fqdn, value, _ := DNS01Record(domain, keyAuth) fqdn, value, _ := DNS01Record(domain, keyAuth)
logf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers) log.Printf("[INFO][%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers)
var timeout, interval time.Duration var timeout, interval time.Duration
switch provider := s.provider.(type) { switch provider := s.provider.(type) {

View file

@ -1,9 +1,11 @@
package acmev2 package acme
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"os" "os"
"github.com/xenolf/lego/log"
) )
const ( const (
@ -28,9 +30,9 @@ func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
return err return err
} }
logf("[INFO] acme: Please create the following TXT record in your %s zone:", authZone) log.Printf("[INFO] acme: Please create the following TXT record in your %s zone:", authZone)
logf("[INFO] acme: %s", dnsRecord) log.Printf("[INFO] acme: %s", dnsRecord)
logf("[INFO] acme: Press 'Enter' when you are done") log.Printf("[INFO] acme: Press 'Enter' when you are done")
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
_, _ = reader.ReadString('\n') _, _ = reader.ReadString('\n')
@ -47,7 +49,7 @@ func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
return err return err
} }
logf("[INFO] acme: You can now remove this TXT record from your %s zone:", authZone) log.Printf("[INFO] acme: You can now remove this TXT record from your %s zone:", authZone)
logf("[INFO] acme: %s", dnsRecord) log.Printf("[INFO] acme: %s", dnsRecord)
return nil return nil
} }

View file

@ -1,4 +1,4 @@
package acmev2 package acme
import ( import (
"bytes" "bytes"
@ -14,18 +14,6 @@ const (
invalidNonceError = "urn:ietf:params:acme:error:badNonce" invalidNonceError = "urn:ietf:params:acme:error:badNonce"
) )
// ObtainError is returned when there are specific errors available
// per domain. For example in ObtainCertificate
type ObtainError map[string]error
func (e ObtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
for dom, err := range e {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err))
}
return buffer.String()
}
// RemoteError is the base type for all errors specific to the ACME protocol. // RemoteError is the base type for all errors specific to the ACME protocol.
type RemoteError struct { type RemoteError struct {
StatusCode int `json:"status,omitempty"` StatusCode int `json:"status,omitempty"`
@ -55,6 +43,18 @@ type domainError struct {
Error error Error error
} }
// ObtainError is returned when there are specific errors available
// per domain. For example in ObtainCertificate
type ObtainError map[string]error
func (e ObtainError) Error() string {
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
for dom, err := range e {
buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err))
}
return buffer.String()
}
func handleHTTPError(resp *http.Response) error { func handleHTTPError(resp *http.Response) error {
var errorDetail RemoteError var errorDetail RemoteError

View file

@ -1,4 +1,4 @@
package acmev2 package acme
import ( import (
"encoding/json" "encoding/json"

View file

@ -1,8 +1,9 @@
package acmev2 package acme
import ( import (
"fmt" "fmt"
"log"
"github.com/xenolf/lego/log"
) )
type httpChallenge struct { type httpChallenge struct {
@ -18,7 +19,7 @@ func HTTP01ChallengePath(token string) string {
func (s *httpChallenge) Solve(chlng challenge, domain string) error { func (s *httpChallenge) Solve(chlng challenge, domain string) error {
logf("[INFO][%s] acme: Trying to solve HTTP-01", domain) log.Printf("[INFO][%s] acme: Trying to solve HTTP-01", domain)
// Generate the Key Authorization for the challenge // Generate the Key Authorization for the challenge
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey) keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)

View file

@ -1,10 +1,12 @@
package acmev2 package acme
import ( import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"github.com/xenolf/lego/log"
) )
// HTTPProviderServer implements ChallengeProvider for `http-01` challenge // HTTPProviderServer implements ChallengeProvider for `http-01` challenge
@ -61,9 +63,9 @@ func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
if strings.HasPrefix(r.Host, domain) && r.Method == "GET" { if strings.HasPrefix(r.Host, domain) && r.Method == "GET" {
w.Header().Add("Content-Type", "text/plain") w.Header().Add("Content-Type", "text/plain")
w.Write([]byte(keyAuth)) w.Write([]byte(keyAuth))
logf("[INFO][%s] Served key authentication", domain) log.Printf("[INFO][%s] Served key authentication", domain)
} else { } else {
logf("[WARN] Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method) log.Printf("[WARN] Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the HOST header properly.", r.Host, r.Method)
w.Write([]byte("TEST")) w.Write([]byte("TEST"))
} }
}) })

View file

@ -1,4 +1,4 @@
package acmev2 package acme
import ( import (
"bytes" "bytes"
@ -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,16 +77,45 @@ 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
} }
func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
jwk := jose.JSONWebKey{Key: j.privKey}
jwkJSON, err := jwk.Public().MarshalJSON()
if err != nil {
return nil, fmt.Errorf("acme: error encoding eab jwk key: %s", err.Error())
}
signer, err := jose.NewSigner(
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
&jose.SignerOptions{
EmbedJWK: false,
ExtraHeaders: map[jose.HeaderKey]interface{}{
"kid": kid,
"url": url,
},
},
)
if err != nil {
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %s", err.Error())
}
signed, err := signer.Sign(jwkJSON)
if err != nil {
return nil, fmt.Errorf("failed to External Account Binding sign content -> %s", err.Error())
}
return signed, nil
}
func (j *jws) Nonce() (string, error) { func (j *jws) Nonce() (string, error) {
if nonce, ok := j.nonces.Pop(); ok { if nonce, ok := j.nonces.Pop(); ok {
return nonce, nil return nonce, nil
@ -122,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)
@ -131,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

@ -1,6 +1,7 @@
package acmev2 package acme
import ( import (
"encoding/json"
"time" "time"
) )
@ -26,11 +27,12 @@ type directory struct {
} }
type accountMessage struct { type accountMessage struct {
Status string `json:"status,omitempty"` Status string `json:"status,omitempty"`
Contact []string `json:"contact,omitempty"` Contact []string `json:"contact,omitempty"`
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"` TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
Orders string `json:"orders,omitempty"` Orders string `json:"orders,omitempty"`
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"` OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
} }
type orderResource struct { type orderResource struct {
@ -76,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,4 +1,4 @@
package acmev2 package acme
import "time" import "time"

View file

@ -1,4 +1,4 @@
package acmev2 package acme
import ( import (
"fmt" "fmt"

View file

@ -1 +0,0 @@
package acmev2

59
vendor/github.com/xenolf/lego/log/logger.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
package log
import (
"log"
"os"
)
// Logger is an optional custom logger.
var Logger *log.Logger
// Fatal writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Fatal(args ...interface{}) {
if Logger == nil {
Logger = log.New(os.Stderr, "", log.LstdFlags)
}
Logger.Fatal(args...)
}
// Fatalf writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Fatalf(format string, args ...interface{}) {
if Logger == nil {
Logger = log.New(os.Stderr, "", log.LstdFlags)
}
Logger.Fatalf(format, args...)
}
// Print writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Print(args ...interface{}) {
if Logger == nil {
Logger = log.New(os.Stdout, "", log.LstdFlags)
}
Logger.Print(args...)
}
// Println writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Println(args ...interface{}) {
if Logger == nil {
Logger = log.New(os.Stdout, "", log.LstdFlags)
}
Logger.Println(args...)
}
// Printf writes a log entry.
// It uses Logger if not nil, otherwise it uses the default log.Logger.
func Printf(format string, args ...interface{}) {
if Logger == nil {
Logger = log.New(os.Stdout, "", log.LstdFlags)
}
Logger.Printf(format, args...)
}

View file

@ -8,7 +8,7 @@ import (
"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/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider describes a provider for AuroraDNS // DNSProvider describes a provider for AuroraDNS
@ -60,14 +60,14 @@ 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
func (provider *DNSProvider) Present(domain, token, keyAuth string) error { func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.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)
} }
@ -81,9 +81,12 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
subdomain := fqdn[0 : len(fqdn)-len(authZone)-1] subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]
authZone = acmev2.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{
@ -95,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()
@ -107,22 +110,22 @@ func (provider *DNSProvider) Present(domain, token, keyAuth string) error {
// CleanUp removes a given record that was generated by Present // CleanUp removes a given record that was generated by Present
func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (provider *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
provider.recordIDsMu.Lock() provider.recordIDsMu.Lock()
recordID, ok := provider.recordIDs[fqdn] recordID, ok := provider.recordIDs[fqdn]
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 := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.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 = acmev2.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
zoneRecord, err := provider.getZoneInformationByName(authZone) zoneRecord, err := provider.getZoneInformationByName(authZone)
if err != nil { if err != nil {

View file

@ -15,44 +15,49 @@ import (
"github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to" "github.com/Azure/go-autorest/autorest/to"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.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
} }
// NewDNSProvider returns a DNSProvider instance configured for azure. // NewDNSProvider returns a DNSProvider instance configured for azure.
// 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 == "" {
return nil, fmt.Errorf("Azure configuration missing") var missingEnvVars []string
for _, envVar := range []string{"AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET", "AZURE_SUBSCRIPTION_ID", "AZURE_TENANT_ID", "AZURE_RESOURCE_GROUP"} {
if os.Getenv(envVar) == "" {
missingEnvVars = append(missingEnvVars, envVar)
}
}
return nil, fmt.Errorf("Azure configuration missing: %s", strings.Join(missingEnvVars, ","))
} }
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(),
@ -67,74 +72,77 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
// Present creates a TXT record to fulfil the dns-01 challenge // Present creates a TXT record to fulfil the dns-01 challenge
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZoneID(fqdn) zone, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
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, acmev2.ToFqdn(zone)) relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
rec := dns.RecordSet{ rec := dns.RecordSet{
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
func toRelativeRecord(domain, zone string) string { func toRelativeRecord(domain, zone string) string {
return acmev2.UnFqdn(strings.TrimSuffix(domain, zone)) return acme.UnFqdn(strings.TrimSuffix(domain, zone))
} }
// CleanUp removes the TXT record matching the specified parameters // CleanUp removes the TXT record matching the specified parameters
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZoneID(fqdn) zone, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return err return err
} }
relative := toRelativeRecord(fqdn, acmev2.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.
func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }
// 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, acmev2.UnFqdn(authZone)) zone, err := dc.Get(c.context, c.resourceGroup, acme.UnFqdn(authZone))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -146,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

@ -15,26 +15,26 @@ import (
"io/ioutil" "io/ioutil"
"github.com/xenolf/lego/acmev2" "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"`
} }
// DNSProvider is an implementation of the acmev2.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
@ -56,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)
} }
@ -68,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,
@ -80,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 {
@ -160,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 = ""
@ -176,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,
@ -194,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,
} }
@ -222,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 != "" {
@ -237,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,
} }
@ -276,29 +275,32 @@ 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
// This will *not* create a subzone to contain the TXT record, // This will *not* create a subzone to contain the TXT record,
// so make sure the FQDN specified is within an extant zone. // so make sure the FQDN specified is within an extant zone.
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
err := d.login() err := d.login()
if err != nil { if err != nil {
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{
@ -322,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)
@ -353,25 +350,25 @@ func (d *DNSProvider) deploy(entityId uint) 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, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
err := d.login() err := d.login()
if err != nil { if err != nil {
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,
} }
@ -388,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)
@ -397,20 +394,15 @@ 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"`

View file

@ -7,18 +7,20 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"strings"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// CloudFlareAPIURL represents the API endpoint to call. // CloudFlareAPIURL represents the API endpoint to call.
// TODO: Unexport? // TODO: Unexport?
const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4" const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4"
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
authEmail string authEmail string
authKey string authKey string
@ -37,7 +39,14 @@ func NewDNSProvider() (*DNSProvider, error) {
// DNSProvider instance configured for cloudflare. // DNSProvider instance configured for cloudflare.
func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) { func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) {
if email == "" || key == "" { if email == "" || key == "" {
return nil, fmt.Errorf("CloudFlare credentials missing") missingEnvVars := []string{}
if email == "" {
missingEnvVars = append(missingEnvVars, "CLOUDFLARE_EMAIL")
}
if key == "" {
missingEnvVars = append(missingEnvVars, "CLOUDFLARE_API_KEY")
}
return nil, fmt.Errorf("CloudFlare credentials missing: %s", strings.Join(missingEnvVars, ","))
} }
return &DNSProvider{ return &DNSProvider{
@ -54,7 +63,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
// Present creates a TXT record to fulfil the dns-01 challenge // Present creates a TXT record to fulfil the dns-01 challenge
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
zoneID, err := c.getHostedZoneID(fqdn) zoneID, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return err return err
@ -62,7 +71,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
rec := cloudFlareRecord{ rec := cloudFlareRecord{
Type: "TXT", Type: "TXT",
Name: acmev2.UnFqdn(fqdn), Name: acme.UnFqdn(fqdn),
Content: value, Content: value,
TTL: 120, TTL: 120,
} }
@ -73,16 +82,12 @@ 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
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
record, err := c.findTxtRecord(fqdn) record, err := c.findTxtRecord(fqdn)
if err != nil { if err != nil {
@ -90,11 +95,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) {
@ -104,12 +105,12 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
Name string `json:"name"` Name string `json:"name"`
} }
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }
result, err := c.makeRequest("GET", "/zones?name="+acmev2.UnFqdn(authZone), nil) result, err := c.makeRequest("GET", "/zones?name="+acme.UnFqdn(authZone), nil)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -135,7 +136,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
result, err := c.makeRequest( result, err := c.makeRequest(
"GET", "GET",
fmt.Sprintf("/zones/%s/dns_records?per_page=1000&type=TXT&name=%s", zoneID, acmev2.UnFqdn(fqdn)), fmt.Sprintf("/zones/%s/dns_records?per_page=1000&type=TXT&name=%s", zoneID, acme.UnFqdn(fqdn)),
nil, nil,
) )
if err != nil { if err != nil {
@ -149,12 +150,12 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) {
} }
for _, rec := range records { for _, rec := range records {
if rec.Name == acmev2.UnFqdn(fqdn) { if rec.Name == acme.UnFqdn(fqdn) {
return &rec, nil return &rec, nil
} }
} }
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) {
@ -179,7 +180,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)
@ -206,7 +206,11 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM
} }
return nil, fmt.Errorf("Cloudflare API Error \n%s", errStr) return nil, fmt.Errorf("Cloudflare API Error \n%s", errStr)
} }
return nil, fmt.Errorf("Cloudflare API error") strBody := "Unreadable body"
if body, err := ioutil.ReadAll(resp.Body); err == nil {
strBody = string(body)
}
return nil, fmt.Errorf("Cloudflare API error: the request %s sent a response with a body which is not in JSON format: %s", req.URL.String(), strBody)
} }
return r.Result, nil return r.Result, nil

View file

@ -13,12 +13,12 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
const cloudXNSBaseURL = "https://www.cloudxns.net/api2/" const cloudXNSBaseURL = "https://www.cloudxns.net/api2/"
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
apiKey string apiKey string
secretKey string secretKey string
@ -48,7 +48,7 @@ func NewDNSProviderCredentials(apiKey, secretKey string) (*DNSProvider, error) {
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zoneID, err := c.getHostedZoneID(fqdn) zoneID, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return err return err
@ -59,7 +59,7 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zoneID, err := c.getHostedZoneID(fqdn) zoneID, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return err return err
@ -79,7 +79,7 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
Domain string `json:"domain"` Domain string `json:"domain"`
} }
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -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) {
@ -117,12 +117,12 @@ func (c *DNSProvider) findTxtRecord(zoneID, fqdn string) (string, error) {
} }
for _, record := range records { for _, record := range records {
if record.Host == acmev2.UnFqdn(fqdn) && record.Type == "TXT" { if record.Host == acme.UnFqdn(fqdn) && record.Type == "TXT" {
return record.RecordID, nil return record.RecordID, nil
} }
} }
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 {
@ -133,7 +133,7 @@ func (c *DNSProvider) addTxtRecord(zoneID, fqdn, value string, ttl int) error {
payload := cloudXNSRecord{ payload := cloudXNSRecord{
ID: id, ID: id,
Host: acmev2.UnFqdn(fqdn), Host: acme.UnFqdn(fqdn),
Value: value, Value: value,
Type: "TXT", Type: "TXT",
LineID: 1, LineID: 1,
@ -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 {
@ -183,7 +179,7 @@ func (c *DNSProvider) makeRequest(method, uri string, body []byte) (json.RawMess
req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body))) req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body)))
req.Header.Set("API-FORMAT", "json") req.Header.Set("API-FORMAT", "json")
resp, err := acmev2.HTTPClient.Do(req) resp, err := acme.HTTPClient.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -11,10 +11,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
// that uses DigitalOcean's REST API to manage TXT records for a domain. // that uses DigitalOcean's REST API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
apiAuthToken string apiAuthToken string
@ -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,32 +50,14 @@ 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 fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
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 authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
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, _ := acmev2.DNS01Record(domain, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.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 = acmev2.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, TTL: 30} reqData := txtRecordRequest{RecordType: "TXT", Name: fqdn, Data: value, TTL: 30}
@ -113,7 +101,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, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
// get the record's unique ID from when we created it // get the record's unique ID from when we created it
d.recordIDsMu.Lock() d.recordIDsMu.Lock()
@ -123,12 +111,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("unknown record ID for '%s'", fqdn) return fmt.Errorf("unknown record ID for '%s'", fqdn)
} }
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.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 = acmev2.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", digitalOceanBaseURL, authZone, recordID) reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", digitalOceanBaseURL, authZone, recordID)
req, err := http.NewRequest("DELETE", reqURL, nil) req, err := http.NewRequest("DELETE", reqURL, nil)
@ -166,8 +154,20 @@ type digitalOceanAPIError struct {
var digitalOceanBaseURL = "https://api.digitalocean.com" var digitalOceanBaseURL = "https://api.digitalocean.com"
// Timeout returns the timeout and interval to use when checking for DNS // txtRecordRequest represents the request body to DO's API to make a TXT record
// propagation. Adjusting here to cope with spikes in propagation times. type txtRecordRequest struct {
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { RecordType string `json:"type"`
return 60 * time.Second, 5 * time.Second 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

@ -1,10 +1,9 @@
// Factory for DNS providers
package dns package dns
import ( import (
"fmt" "fmt"
"github.com/xenolf/lego/acmev2" "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/bluecat"
@ -21,9 +20,9 @@ import (
"github.com/xenolf/lego/providers/dns/fastdns" "github.com/xenolf/lego/providers/dns/fastdns"
"github.com/xenolf/lego/providers/dns/gandi" "github.com/xenolf/lego/providers/dns/gandi"
"github.com/xenolf/lego/providers/dns/gandiv5" "github.com/xenolf/lego/providers/dns/gandiv5"
"github.com/xenolf/lego/providers/dns/gcloud"
"github.com/xenolf/lego/providers/dns/glesys" "github.com/xenolf/lego/providers/dns/glesys"
"github.com/xenolf/lego/providers/dns/godaddy" "github.com/xenolf/lego/providers/dns/godaddy"
"github.com/xenolf/lego/providers/dns/googlecloud"
"github.com/xenolf/lego/providers/dns/lightsail" "github.com/xenolf/lego/providers/dns/lightsail"
"github.com/xenolf/lego/providers/dns/linode" "github.com/xenolf/lego/providers/dns/linode"
"github.com/xenolf/lego/providers/dns/namecheap" "github.com/xenolf/lego/providers/dns/namecheap"
@ -38,9 +37,10 @@ import (
"github.com/xenolf/lego/providers/dns/vultr" "github.com/xenolf/lego/providers/dns/vultr"
) )
func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error) { // NewDNSChallengeProviderByName Factory for DNS providers
func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) {
var err error var err error
var provider acmev2.ChallengeProvider var provider acme.ChallengeProvider
switch name { switch name {
case "azure": case "azure":
provider, err = azure.NewDNSProvider() provider, err = azure.NewDNSProvider()
@ -75,7 +75,7 @@ func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error
case "glesys": case "glesys":
provider, err = glesys.NewDNSProvider() provider, err = glesys.NewDNSProvider()
case "gcloud": case "gcloud":
provider, err = googlecloud.NewDNSProvider() provider, err = gcloud.NewDNSProvider()
case "godaddy": case "godaddy":
provider, err = godaddy.NewDNSProvider() provider, err = godaddy.NewDNSProvider()
case "lightsail": case "lightsail":
@ -83,7 +83,7 @@ func NewDNSChallengeProviderByName(name string) (acmev2.ChallengeProvider, error
case "linode": case "linode":
provider, err = linode.NewDNSProvider() provider, err = linode.NewDNSProvider()
case "manual": case "manual":
provider, err = acmev2.NewDNSProviderManual() provider, err = acme.NewDNSProviderManual()
case "namecheap": case "namecheap":
provider, err = namecheap.NewDNSProvider() provider, err = namecheap.NewDNSProvider()
case "namedotcom": case "namedotcom":
@ -107,7 +107,7 @@ func NewDNSChallengeProviderByName(name string) (acmev2.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

@ -9,10 +9,10 @@ import (
"strings" "strings"
"github.com/dnsimple/dnsimple-go/dnsimple" "github.com/dnsimple/dnsimple-go/dnsimple"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
client *dnsimple.Client client *dnsimple.Client
} }
@ -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
@ -47,7 +47,7 @@ func NewDNSProviderCredentials(accessToken, baseUrl string) (*DNSProvider, error
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zoneName, err := c.getHostedZone(domain) zoneName, err := c.getHostedZone(domain)
@ -71,7 +71,7 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
records, err := c.findTxtRecords(domain, fqdn) records, err := c.findTxtRecords(domain, fqdn)
if err != nil { if err != nil {
@ -94,7 +94,7 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
func (c *DNSProvider) getHostedZone(domain string) (string, error) { func (c *DNSProvider) getHostedZone(domain string) (string, error) {
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -104,7 +104,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) {
return "", err return "", err
} }
zoneName := acmev2.UnFqdn(authZone) zoneName := acme.UnFqdn(authZone)
zones, err := c.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName}) zones, err := c.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName})
if err != nil { if err != 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
@ -159,7 +158,7 @@ func (c *DNSProvider) newTxtRecord(zoneName, fqdn, value string, ttl int) *dnsim
} }
func (c *DNSProvider) extractRecordName(fqdn, domain string) string { func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
if idx := strings.Index(name, "."+domain); idx != -1 { if idx := strings.Index(name, "."+domain); idx != -1 {
return name[:idx] return name[:idx]
} }
@ -173,8 +172,8 @@ 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.Itoa(whoamiResponse.Data.Account.ID), nil return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil
} }

View file

@ -14,10 +14,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
// DNSMadeEasy's DNS API to manage TXT records for a domain. // DNSMadeEasy's DNS API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
baseURL string baseURL string
@ -77,9 +77,9 @@ func NewDNSProviderCredentials(baseURL, apiKey, apiSecret string) (*DNSProvider,
// Present creates a TXT record using the specified parameters // Present creates a TXT record using the specified parameters
func (d *DNSProvider) Present(domainName, token, keyAuth string) error { func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domainName, keyAuth) fqdn, value, ttl := acme.DNS01Record(domainName, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return err return err
} }
@ -95,18 +95,14 @@ 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
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domainName, keyAuth) fqdn, _, _ := acme.DNS01Record(domainName, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return err return err
} }
@ -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

@ -8,10 +8,10 @@ import (
"strings" "strings"
"github.com/decker502/dnspod-go" "github.com/decker502/dnspod-go"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
client *dnspod.Client client *dnspod.Client
} }
@ -38,7 +38,7 @@ func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zoneID, zoneName, err := c.getHostedZone(domain) zoneID, zoneName, err := c.getHostedZone(domain)
if err != nil { if err != nil {
return err return err
@ -55,7 +55,7 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
records, err := c.findTxtRecords(domain, fqdn) records, err := c.findTxtRecords(domain, fqdn)
if err != nil { if err != nil {
@ -82,14 +82,14 @@ func (c *DNSProvider) getHostedZone(domain string) (string, string, error) {
return "", "", fmt.Errorf("dnspod API call failed: %v", err) return "", "", fmt.Errorf("dnspod API call failed: %v", err)
} }
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
var hostedZone dnspod.Domain var hostedZone dnspod.Domain
for _, zone := range zones { for _, zone := range zones {
if zone.Name == acmev2.UnFqdn(authZone) { if zone.Name == acme.UnFqdn(authZone) {
hostedZone = zone hostedZone = zone
} }
} }
@ -138,7 +138,7 @@ func (c *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, erro
} }
func (c *DNSProvider) extractRecordName(fqdn, domain string) string { func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
if idx := strings.Index(name, "."+domain); idx != -1 { if idx := strings.Index(name, "."+domain); idx != -1 {
return name[:idx] return name[:idx]
} }

View file

@ -1,5 +1,4 @@
// 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
@ -9,7 +8,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"github.com/xenolf/lego/acmev2" "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
@ -47,7 +46,7 @@ func makeDuckdnsURL(domain, token, txt string) string {
} }
func issueDuckdnsRequest(url string) error { func issueDuckdnsRequest(url string) error {
response, err := acmev2.HTTPClient.Get(url) response, err := acme.HTTPClient.Get(url)
if err != nil { if err != nil {
return err return err
} }
@ -70,7 +69,7 @@ func issueDuckdnsRequest(url string) error {
// //
// 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, _ := acmev2.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)
} }

View file

@ -11,7 +11,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
var dynBaseURL = "https://api.dynect.net/REST" var dynBaseURL = "https://api.dynect.net/REST"
@ -30,7 +30,7 @@ type dynResponse struct {
Messages json.RawMessage `json:"msgs"` Messages json.RawMessage `json:"msgs"`
} }
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
// Dyn's Managed DNS API to manage TXT records for a domain. // Dyn's Managed DNS API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
customerName string customerName string
@ -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
@ -176,9 +176,9 @@ func (d *DNSProvider) logout() 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 {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
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,19 +217,16 @@ 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
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return err return err
} }
@ -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

@ -31,7 +31,7 @@ import (
"os/exec" "os/exec"
"strconv" "strconv"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider adds and removes the record for the DNS challenge by calling a // DNSProvider adds and removes the record for the DNS challenge by calling a
@ -53,7 +53,7 @@ func NewDNSProvider() (*DNSProvider, error) {
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
cmd := exec.Command(d.program, "present", fqdn, value, strconv.Itoa(ttl)) cmd := exec.Command(d.program, "present", fqdn, value, strconv.Itoa(ttl))
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@ -63,7 +63,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, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
cmd := exec.Command(d.program, "cleanup", fqdn, value, strconv.Itoa(ttl)) cmd := exec.Command(d.program, "cleanup", fqdn, value, strconv.Itoa(ttl))
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr

View file

@ -8,15 +8,15 @@ import (
"os" "os"
"github.com/exoscale/egoscale" "github.com/exoscale/egoscale"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { 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 == "" {
@ -42,13 +42,13 @@ func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) {
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain) zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain)
if err != nil { if err != nil {
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
} }
@ -78,13 +78,13 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
// CleanUp removes the record matching the specified parameters. // CleanUp removes the record matching the specified parameters.
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain) zone, recordName, err := c.FindZoneAndRecordName(fqdn, domain)
if err != nil { if err != nil {
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,14 +114,14 @@ 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 := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
zone = acmev2.UnFqdn(zone) zone = acme.UnFqdn(zone)
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
name = name[:len(name)-len("."+zone)] name = name[:len(name)-len("."+zone)]
return zone, name, nil return zone, name, nil

View file

@ -7,10 +7,10 @@ import (
configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1" configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1"
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
config edgegrid.Config config edgegrid.Config
} }
@ -47,7 +47,7 @@ func NewDNSProviderClient(host, clientToken, clientSecret, accessToken string) (
// Present creates a TXT record to fullfil the dns-01 challenge. // Present creates a TXT record to fullfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain) zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain)
if err != nil { if err != nil {
return err return err
@ -81,7 +81,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
// CleanUp removes the record matching the specified parameters. // CleanUp removes the record matching the specified parameters.
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain) zoneName, recordName, err := c.findZoneAndRecordName(fqdn, domain)
if err != nil { if err != nil {
return err return err
@ -108,12 +108,12 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
func (c *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) { func (c *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) {
zone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
zone = acmev2.UnFqdn(zone) zone = acme.UnFqdn(zone)
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
name = name[:len(name)-len("."+zone)] name = name[:len(name)-len("."+zone)]
return zone, name, nil return zone, name, nil

View file

@ -14,7 +14,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// Gandi API reference: http://doc.rpc.gandi.net/index.html // Gandi API reference: http://doc.rpc.gandi.net/index.html
@ -26,7 +26,7 @@ var (
endpoint = "https://rpc.gandi.net/xmlrpc/" endpoint = "https://rpc.gandi.net/xmlrpc/"
// 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 = acmev2.FindZoneByFqdn findZoneByFqdn = acme.FindZoneByFqdn
) )
// inProgressInfo contains information about an in-progress challenge // inProgressInfo contains information about an in-progress challenge
@ -37,7 +37,7 @@ type inProgressInfo struct {
} }
// DNSProvider is an implementation of the // DNSProvider is an implementation of the
// acmev2.ChallengeProviderTimeout interface that uses Gandi's XML-RPC // acme.ChallengeProviderTimeout interface that uses Gandi's XML-RPC
// API to manage TXT records for a domain. // API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
apiKey string apiKey string
@ -70,19 +70,22 @@ func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
// does this by creating and activating a new temporary Gandi DNS // does this by creating and activating a new temporary Gandi DNS
// zone. This new zone contains the TXT record. // zone. This new zone contains the TXT record.
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
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, acmev2.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]",
acmev2.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,
@ -138,29 +150,29 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
// parameters. It does this by restoring the old Gandi DNS zone and // parameters. It does this by restoring the old Gandi DNS zone and
// removing the temporary one created by Present. // removing the temporary one created by Present.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
// 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

@ -12,7 +12,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// Gandi API reference: http://doc.livedns.gandi.net/ // Gandi API reference: http://doc.livedns.gandi.net/
@ -21,9 +21,10 @@ 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 = acmev2.FindZoneByFqdn findZoneByFqdn = acme.FindZoneByFqdn
) )
// inProgressInfo contains information about an in-progress challenge // inProgressInfo contains information about an in-progress challenge
@ -33,7 +34,7 @@ type inProgressInfo struct {
} }
// DNSProvider is an implementation of the // DNSProvider is an implementation of the
// acmev2.ChallengeProviderTimeout interface that uses Gandi's LiveDNS // acme.ChallengeProviderTimeout interface that uses Gandi's LiveDNS
// API to manage TXT records for a domain. // API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
apiKey string apiKey string
@ -62,15 +63,17 @@ func NewDNSProviderCredentials(apiKey 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 {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
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, acmev2.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(acmev2.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,
@ -97,7 +103,8 @@ 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, _, _ := acmev2.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(acmev2.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,17 +1,17 @@
// Package googlecloud implements a DNS provider for solving the DNS-01 // Package gcloud implements a DNS provider for solving the DNS-01
// challenge using Google Cloud DNS. // challenge using Google Cloud DNS.
package googlecloud package gcloud
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"time" "time"
"github.com/xenolf/lego/acmev2" "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"
@ -28,10 +28,10 @@ type DNSProvider struct {
// A Service Account file can be passed in the environment variable: // A Service Account file can be passed in the environment variable:
// GCE_SERVICE_ACCOUNT_FILE // GCE_SERVICE_ACCOUNT_FILE
func NewDNSProvider() (*DNSProvider, error) { func NewDNSProvider() (*DNSProvider, error) {
project := os.Getenv("GCE_PROJECT")
if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok { if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok {
return NewDNSProviderServiceAccount(project, saFile) return NewDNSProviderServiceAccount(saFile)
} }
project := os.Getenv("GCE_PROJECT")
return NewDNSProviderCredentials(project) return NewDNSProviderCredentials(project)
} }
@ -58,10 +58,7 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
// NewDNSProviderServiceAccount uses the supplied service account JSON file to // NewDNSProviderServiceAccount uses the supplied service account JSON file to
// return a DNSProvider instance configured for Google Cloud DNS. // return a DNSProvider instance configured for Google Cloud DNS.
func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider, error) { func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
if project == "" {
return nil, fmt.Errorf("Google Cloud project name missing")
}
if saFile == "" { if saFile == "" {
return nil, fmt.Errorf("Google Cloud Service Account file missing") return nil, fmt.Errorf("Google Cloud Service Account file missing")
} }
@ -70,11 +67,22 @@ func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider,
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to read Service Account file: %v", err) return nil, fmt.Errorf("Unable to read Service Account file: %v", err)
} }
// read project id from service account file
var datJSON struct {
ProjectID string `json:"project_id"`
}
err = json.Unmarshal(dat, &datJSON)
if err != nil || datJSON.ProjectID == "" {
return nil, fmt.Errorf("Project ID not found in Google Cloud Service Account file")
}
project := datJSON.ProjectID
conf, err := google.JWTConfigFromJSON(dat, dns.NdevClouddnsReadwriteScope) conf, err := google.JWTConfigFromJSON(dat, dns.NdevClouddnsReadwriteScope)
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 {
@ -88,7 +96,7 @@ func NewDNSProviderServiceAccount(project string, saFile string) (*DNSProvider,
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZone(domain) zone, err := c.getHostedZone(domain)
if err != nil { if err != nil {
@ -135,7 +143,7 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZone(domain) zone, err := c.getHostedZone(domain)
if err != nil { if err != nil {
@ -167,7 +175,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
// getHostedZone returns the managed-zone // getHostedZone returns the managed-zone
func (c *DNSProvider) getHostedZone(domain string) (string, error) { func (c *DNSProvider) getHostedZone(domain string) (string, error) {
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }

View file

@ -6,14 +6,14 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"net/http" "net/http"
"os" "os"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
"github.com/xenolf/lego/log"
) )
// GleSYS API reference: https://github.com/GleSYS/API/wiki/API-Documentation // GleSYS API reference: https://github.com/GleSYS/API/wiki/API-Documentation
@ -21,24 +21,8 @@ import (
// domainAPI is the GleSYS API endpoint used by Present and CleanUp. // domainAPI is the GleSYS API endpoint used by Present and CleanUp.
const domainAPI = "https://api.glesys.com/domain" const domainAPI = "https://api.glesys.com/domain"
var (
// Logger is used to log API communication results;
// if nil, the default log.Logger is used.
Logger *log.Logger
)
// logf writes a log entry. It uses Logger if not
// nil, otherwise it uses the default log.Logger.
func logf(format string, args ...interface{}) {
if Logger != nil {
Logger.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
// DNSProvider is an implementation of the // DNSProvider is an implementation of the
// acmev2.ChallengeProviderTimeout interface that uses GleSYS // acme.ChallengeProviderTimeout interface that uses GleSYS
// API to manage TXT records for a domain. // API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
apiUser string apiUser string
@ -71,15 +55,16 @@ func NewDNSProviderCredentials(apiUser string, apiKey string) (*DNSProvider, err
// 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 {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
if ttl < 60 { if ttl < 60 {
ttl = 60 // 60 is GleSYS minimum value for ttl ttl = 60 // 60 is GleSYS minimum value for ttl
} }
// find authZone // find authZone
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
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)) {
@ -87,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, acmev2.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, _, _ := acmev2.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()
@ -111,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
@ -135,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 {
@ -160,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
@ -177,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)
@ -191,10 +181,10 @@ 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 {
logf("[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)
return response.Response.Record.Recordid, nil return response.Response.Record.Recordid, nil
} }
return 0, err return 0, err
@ -205,7 +195,7 @@ func (d *DNSProvider) deleteTXTRecord(fqdn string, recordid int) error {
Recordid: recordid, Recordid: recordid,
}) })
if response != nil && response.Response.Status.Code == 200 { if response != nil && response.Response.Status.Code == 200 {
logf("[INFO][%s] GleSYS DNS: Successfully deleted recordid %d", fqdn, recordid) log.Printf("[INFO][%s] GleSYS DNS: Successfully deleted recordid %d", fqdn, recordid)
} }
return err return err
} }

View file

@ -13,13 +13,13 @@ import (
"io/ioutil" "io/ioutil"
"strings" "strings"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// GoDaddyAPIURL represents the API endpoint to call. // GoDaddyAPIURL represents the API endpoint to call.
const apiURL = "https://api.godaddy.com" const apiURL = "https://api.godaddy.com"
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
apiKey string apiKey string
apiSecret string apiSecret string
@ -51,7 +51,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
} }
func (c *DNSProvider) extractRecordName(fqdn, domain string) string { func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
if idx := strings.Index(name, "."+domain); idx != -1 { if idx := strings.Index(name, "."+domain); idx != -1 {
return name[:idx] return name[:idx]
} }
@ -60,7 +60,7 @@ func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
// Present creates a TXT record to fulfil the dns-01 challenge // Present creates a TXT record to fulfil the dns-01 challenge
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
domainZone, err := c.getZone(fqdn) domainZone, err := c.getZone(fqdn)
if err != nil { if err != nil {
return err return err
@ -76,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,
}, },
} }
@ -99,14 +99,14 @@ 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
} }
// CleanUp sets null value in the TXT DNS record as GoDaddy has no proper DELETE record method // CleanUp sets null value in the TXT DNS record as GoDaddy has no proper DELETE record method
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
domainZone, err := c.getZone(fqdn) domainZone, err := c.getZone(fqdn)
if err != nil { if err != nil {
return err return err
@ -125,12 +125,12 @@ func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
func (c *DNSProvider) getZone(fqdn string) (string, error) { func (c *DNSProvider) getZone(fqdn string) (string, error) {
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }
return acmev2.UnFqdn(authZone), nil return acme.UnFqdn(authZone), nil
} }
func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Response, error) { func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Response, error) {
@ -147,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,14 +11,14 @@ import (
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lightsail" "github.com/aws/aws-sdk-go/service/lightsail"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
const ( const (
maxRetries = 5 maxRetries = 5
) )
// DNSProvider implements the acmev2.ChallengeProvider interface // DNSProvider implements the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
client *lightsail.Lightsail client *lightsail.Lightsail
} }
@ -71,7 +71,7 @@ func NewDNSProvider() (*DNSProvider, error) {
// 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 (r *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
value = `"` + value + `"` value = `"` + value + `"`
err := r.newTxtRecord(domain, fqdn, value) err := r.newTxtRecord(domain, fqdn, value)
return err return err
@ -79,7 +79,7 @@ func (r *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 (r *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
value = `"` + value + `"` value = `"` + value + `"`
params := &lightsail.DeleteDomainEntryInput{ params := &lightsail.DeleteDomainEntryInput{
DomainName: aws.String(domain), DomainName: aws.String(domain),

View file

@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/timewasted/linode/dns" "github.com/timewasted/linode/dns"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
const ( const (
@ -19,11 +19,11 @@ const (
) )
type hostedZoneInfo struct { type hostedZoneInfo struct {
domainId int domainID int
resourceName string resourceName string
} }
// DNSProvider implements the acmev2.ChallengeProvider interface. // DNSProvider implements the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
linode *dns.DNS linode *dns.DNS
} }
@ -66,13 +66,13 @@ func (p *DNSProvider) Timeout() (timeout, interval time.Duration) {
// Present creates a TXT record using the specified parameters. // Present creates a TXT record using the specified parameters.
func (p *DNSProvider) Present(domain, token, keyAuth string) error { func (p *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
zone, err := p.getHostedZoneInfo(fqdn) zone, err := p.getHostedZoneInfo(fqdn)
if err != nil { if err != nil {
return err return err
} }
if _, err = p.linode.CreateDomainResourceTXT(zone.domainId, acmev2.UnFqdn(fqdn), value, 60); err != nil { if _, err = p.linode.CreateDomainResourceTXT(zone.domainID, acme.UnFqdn(fqdn), value, 60); err != nil {
return err return err
} }
@ -81,14 +81,14 @@ func (p *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 (p *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
zone, err := p.getHostedZoneInfo(fqdn) zone, err := p.getHostedZoneInfo(fqdn)
if err != nil { if err != nil {
return err return err
} }
// 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
} }
@ -112,20 +112,20 @@ func (p *DNSProvider) CleanUp(domain, token, keyAuth string) error {
func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) { func (p *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
// Lookup the zone that handles the specified FQDN. // Lookup the zone that handles the specified FQDN.
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resourceName := strings.TrimSuffix(fqdn, "."+authZone) resourceName := strings.TrimSuffix(fqdn, "."+authZone)
// Query the authority zone. // Query the authority zone.
domain, err := p.linode.GetDomain(acmev2.UnFqdn(authZone)) domain, err := p.linode.GetDomain(acme.UnFqdn(authZone))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &hostedZoneInfo{ return &hostedZoneInfo{
domainId: domain.DomainID, domainID: domain.DomainID,
resourceName: resourceName, resourceName: resourceName,
}, nil }, nil
} }

View file

@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// Notes about namecheap's tool API: // Notes about namecheap's tool API:
@ -132,7 +132,7 @@ type challenge struct {
// newChallenge builds a challenge record from a domain name, a challenge // newChallenge builds a challenge record from a domain name, a challenge
// authentication key, and a map of available TLDs. // authentication key, and a map of available TLDs.
func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, error) { func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, error) {
domain = acmev2.UnFqdn(domain) domain = acme.UnFqdn(domain)
parts := strings.Split(domain, ".") parts := strings.Split(domain, ".")
// Find the longest matching TLD. // Find the longest matching TLD.
@ -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:], ".")
@ -155,7 +155,7 @@ func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, e
host = strings.Join(parts[:longest-1], ".") host = strings.Join(parts[:longest-1], ".")
} }
key, keyValue, _ := acmev2.DNS01Record(domain, keyAuth) key, keyValue, _ := acme.DNS01Record(domain, keyAuth)
return &challenge{ return &challenge{
domain: domain, domain: domain,
@ -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

@ -8,10 +8,10 @@ import (
"strings" "strings"
"github.com/namedotcom/go/namecom" "github.com/namedotcom/go/namecom"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
client *namecom.NameCom client *namecom.NameCom
} }
@ -47,7 +47,7 @@ func NewDNSProviderCredentials(username, apiToken, server string) (*DNSProvider,
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
request := &namecom.Record{ request := &namecom.Record{
DomainName: domain, DomainName: domain,
@ -67,7 +67,7 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
records, err := c.getRecords(domain) records, err := c.getRecords(domain)
if err != nil { if err != nil {
@ -116,7 +116,7 @@ func (c *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) {
} }
func (c *DNSProvider) extractRecordName(fqdn, domain string) string { func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
if idx := strings.Index(name, "."+domain); idx != -1 { if idx := strings.Index(name, "."+domain); idx != -1 {
return name[:idx] return name[:idx]
} }

View file

@ -8,12 +8,12 @@ import (
"os" "os"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
"gopkg.in/ns1/ns1-go.v2/rest" "gopkg.in/ns1/ns1-go.v2/rest"
"gopkg.in/ns1/ns1-go.v2/rest/model/dns" "gopkg.in/ns1/ns1-go.v2/rest/model/dns"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
client *rest.Client client *rest.Client
} }
@ -43,7 +43,7 @@ func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
// Present creates a TXT record to fulfil the dns-01 challenge. // Present creates a TXT record to fulfil the dns-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZone(domain) zone, err := c.getHostedZone(domain)
if err != nil { if err != nil {
@ -61,14 +61,14 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZone(domain) zone, err := c.getHostedZone(domain)
if err != nil { if err != nil {
return err return err
} }
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
_, err = c.client.Records.Delete(zone.Zone, name, "TXT") _, err = c.client.Records.Delete(zone.Zone, name, "TXT")
return err return err
} }
@ -83,7 +83,7 @@ func (c *DNSProvider) getHostedZone(domain string) (*dns.Zone, error) {
} }
func (c *DNSProvider) newTxtRecord(zone *dns.Zone, fqdn, value string, ttl int) *dns.Record { func (c *DNSProvider) newTxtRecord(zone *dns.Zone, fqdn, value string, ttl int) *dns.Record {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
return &dns.Record{ return &dns.Record{
Type: "TXT", Type: "TXT",

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

@ -12,10 +12,10 @@ import (
"os" "os"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that uses // DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
// OTC's Managed DNS API to manage TXT records for a domain. // OTC's Managed DNS API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
identityEndpoint string identityEndpoint string
@ -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,21 +301,18 @@ 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
func (d *DNSProvider) Present(domain, token, keyAuth string) error { func (d *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
if ttl < 300 { if ttl < 300 {
ttl = 300 // 300 is otc minimum value for ttl ttl = 300 // 300 is otc minimum value for ttl
} }
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return err return err
} }
@ -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,23 +341,18 @@ 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
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return err return err
} }
@ -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
@ -9,13 +9,13 @@ import (
"sync" "sync"
"github.com/ovh/go-ovh/ovh" "github.com/ovh/go-ovh/ovh"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// OVH API reference: https://eu.api.ovh.com/ // OVH API reference: https://eu.api.ovh.com/
// Create a Token: https://eu.api.ovh.com/createToken/ // Create a Token: https://eu.api.ovh.com/createToken/
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
// that uses OVH's REST API to manage TXT records for a domain. // that uses OVH's REST API to manage TXT records for a domain.
type DNSProvider struct { type DNSProvider struct {
client *ovh.Client client *ovh.Client
@ -78,15 +78,15 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
Zone string `json:"zone"` Zone string `json:"zone"`
} }
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
// Parse domain name // Parse domain name
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.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 = acmev2.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
subDomain := d.extractRecordName(fqdn, authZone) subDomain := d.extractRecordName(fqdn, authZone)
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone) reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
@ -117,7 +117,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, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
// get the record's unique ID from when we created it // get the record's unique ID from when we created it
d.recordIDsMu.Lock() d.recordIDsMu.Lock()
@ -127,12 +127,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
return fmt.Errorf("unknown record ID for '%s'", fqdn) return fmt.Errorf("unknown record ID for '%s'", fqdn)
} }
authZone, err := acmev2.FindZoneByFqdn(acmev2.ToFqdn(domain), acmev2.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 = acmev2.UnFqdn(authZone) authZone = acme.UnFqdn(authZone)
reqURL := fmt.Sprintf("/domain/zone/%s/record/%d", authZone, recordID) reqURL := fmt.Sprintf("/domain/zone/%s/record/%d", authZone, recordID)
@ -151,7 +151,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
} }
func (d *DNSProvider) extractRecordName(fqdn, domain string) string { func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
if idx := strings.Index(name, "."+domain); idx != -1 { if idx := strings.Index(name, "."+domain); idx != -1 {
return name[:idx] return name[:idx]
} }

View file

@ -14,10 +14,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
apiKey string apiKey string
host *url.URL host *url.URL
@ -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
@ -65,7 +65,7 @@ func (c *DNSProvider) Timeout() (timeout, interval time.Duration) {
// Present creates a TXT record to fulfil the dns-01 challenge // Present creates a TXT record to fulfil the dns-01 challenge
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZone(fqdn) zone, err := c.getHostedZone(fqdn)
if err != nil { if err != nil {
return err return err
@ -75,7 +75,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
// pre-v1 API wants non-fqdn // pre-v1 API wants non-fqdn
if c.apiVersion == 0 { if c.apiVersion == 0 {
name = acmev2.UnFqdn(fqdn) name = acme.UnFqdn(fqdn)
} }
rec := pdnsRecord{ rec := pdnsRecord{
@ -90,7 +90,7 @@ func (c *DNSProvider) Present(domain, token, keyAuth string) error {
rrsets := rrSets{ rrsets := rrSets{
RRSets: []rrSet{ RRSets: []rrSet{
rrSet{ {
Name: name, Name: name,
ChangeType: "REPLACE", ChangeType: "REPLACE",
Type: "TXT", Type: "TXT",
@ -107,17 +107,12 @@ 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
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zone, err := c.getHostedZone(fqdn) zone, err := c.getHostedZone(fqdn)
if err != nil { if err != nil {
@ -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",
@ -144,16 +139,12 @@ func (c *DNSProvider) CleanUp(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
return err
}
return nil
} }
func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) { func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) {
var zone hostedZone var zone hostedZone
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -172,7 +163,7 @@ func (c *DNSProvider) getHostedZone(fqdn string) (*hostedZone, error) {
url = "" url = ""
for _, zone := range zones { for _, zone := range zones {
if acmev2.UnFqdn(zone.Name) == acmev2.UnFqdn(authZone) { if acme.UnFqdn(zone.Name) == acme.UnFqdn(authZone) {
url = zone.URL url = zone.URL
} }
} }
@ -215,12 +206,12 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*rrSet, error) {
} }
for _, set := range zone.RRSets { for _, set := range zone.RRSets {
if (set.Name == acmev2.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" { if (set.Name == acme.UnFqdn(fqdn) || set.Name == fqdn) && set.Type == "TXT" {
return &set, nil return &set, nil
} }
} }
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

@ -11,13 +11,13 @@ import (
"os" "os"
"time" "time"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// rackspaceAPIURL represents the Identity API endpoint to call // rackspaceAPIURL represents the Identity API endpoint to call
var rackspaceAPIURL = "https://identity.api.rackspacecloud.com/v2.0/tokens" var rackspaceAPIURL = "https://identity.api.rackspacecloud.com/v2.0/tokens"
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface // DNSProvider is an implementation of the acme.ChallengeProvider interface
// used to store the reusable token and DNS API endpoint // used to store the reusable token and DNS API endpoint
type DNSProvider struct { type DNSProvider struct {
token string token string
@ -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{
@ -126,15 +126,15 @@ func NewDNSProviderCredentials(user, key string) (*DNSProvider, error) {
// Present creates a TXT record to fulfil the dns-01 challenge // Present creates a TXT record to fulfil the dns-01 challenge
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
zoneID, err := c.getHostedZoneID(fqdn) zoneID, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return err return err
} }
rec := RackspaceRecords{ rec := Records{
RackspaceRecord: []RackspaceRecord{{ Record: []Record{{
Name: acmev2.UnFqdn(fqdn), Name: acme.UnFqdn(fqdn),
Type: "TXT", Type: "TXT",
Data: value, Data: value,
TTL: 300, TTL: 300,
@ -147,16 +147,12 @@ 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
func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zoneID, err := c.getHostedZoneID(fqdn) zoneID, err := c.getHostedZoneID(fqdn)
if err != nil { if err != nil {
return err return err
@ -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
@ -187,12 +179,12 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (int, error) {
} `json:"domains"` } `json:"domains"`
} }
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return 0, err return 0, err
} }
result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acmev2.UnFqdn(authZone)), nil) result, err := c.makeRequest("GET", fmt.Sprintf("/domains?name=%s", acme.UnFqdn(authZone)), nil)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -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, acmev2.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

@ -10,10 +10,10 @@ import (
"time" "time"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface that // DNSProvider is an implementation of the acme.ChallengeProvider interface that
// uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver. // uses dynamic DNS updates (RFC 2136) to create TXT records on a nameserver.
type DNSProvider struct { type DNSProvider struct {
nameserver string nameserver string
@ -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 := acmev2.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 := acmev2.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 := acmev2.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

@ -14,7 +14,7 @@ import (
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/route53"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
const ( const (
@ -22,7 +22,7 @@ const (
route53TTL = 10 route53TTL = 10
) )
// DNSProvider implements the acmev2.ChallengeProvider interface // DNSProvider implements the acme.ChallengeProvider interface
type DNSProvider struct { type DNSProvider struct {
client *route53.Route53 client *route53.Route53
hostedZoneID string hostedZoneID string
@ -70,7 +70,11 @@ func NewDNSProvider() (*DNSProvider, error) {
r := customRetryer{} r := customRetryer{}
r.NumMaxRetries = maxRetries r.NumMaxRetries = maxRetries
config := request.WithRetryer(aws.NewConfig(), r) config := request.WithRetryer(aws.NewConfig(), r)
client := route53.New(session.New(config)) session, err := session.NewSessionWithOptions(session.Options{Config: *config})
if err != nil {
return nil, err
}
client := route53.New(session)
return &DNSProvider{ return &DNSProvider{
client: client, client: client,
@ -80,14 +84,14 @@ func NewDNSProvider() (*DNSProvider, error) {
// 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 (r *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
value = `"` + value + `"` value = `"` + value + `"`
return r.changeRecord("UPSERT", fqdn, value, route53TTL) return r.changeRecord("UPSERT", fqdn, value, route53TTL)
} }
// 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 (r *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, value, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
value = `"` + value + `"` value = `"` + value + `"`
return r.changeRecord("DELETE", fqdn, value, route53TTL) return r.changeRecord("DELETE", fqdn, value, route53TTL)
} }
@ -95,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)
@ -114,20 +118,20 @@ 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
return acmev2.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) { return acme.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
reqParams := &route53.GetChangeInput{ reqParams := &route53.GetChangeInput{
Id: statusID, Id: statusID,
} }
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
@ -139,14 +143,14 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) {
return r.hostedZoneID, nil return r.hostedZoneID, nil
} }
authZone, err := acmev2.FindZoneByFqdn(fqdn, acmev2.RecursiveNameservers) authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
if err != nil { if err != nil {
return "", err return "", err
} }
// .DNSName should not have a trailing dot // .DNSName should not have a trailing dot
reqParams := &route53.ListHostedZonesByNameInput{ reqParams := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(acmev2.UnFqdn(authZone)), DNSName: aws.String(acme.UnFqdn(authZone)),
} }
resp, err := r.client.ListHostedZonesByName(reqParams) resp, err := r.client.ListHostedZonesByName(reqParams)
if err != nil { if err != nil {
@ -156,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

@ -9,10 +9,10 @@ import (
"strings" "strings"
vultr "github.com/JamesClonk/vultr/lib" vultr "github.com/JamesClonk/vultr/lib"
"github.com/xenolf/lego/acmev2" "github.com/xenolf/lego/acme"
) )
// DNSProvider is an implementation of the acmev2.ChallengeProvider interface. // DNSProvider is an implementation of the acme.ChallengeProvider interface.
type DNSProvider struct { type DNSProvider struct {
client *vultr.Client client *vultr.Client
} }
@ -40,7 +40,7 @@ func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
// Present creates a TXT record to fulfil the DNS-01 challenge. // Present creates a TXT record to fulfil the DNS-01 challenge.
func (c *DNSProvider) Present(domain, token, keyAuth string) error { func (c *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value, ttl := acmev2.DNS01Record(domain, keyAuth) fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
zoneDomain, err := c.getHostedZone(domain) zoneDomain, err := c.getHostedZone(domain)
if err != nil { if err != nil {
@ -59,7 +59,7 @@ func (c *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 (c *DNSProvider) CleanUp(domain, token, keyAuth string) error { func (c *DNSProvider) CleanUp(domain, token, keyAuth string) error {
fqdn, _, _ := acmev2.DNS01Record(domain, keyAuth) fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
zoneDomain, records, err := c.findTxtRecords(domain, fqdn) zoneDomain, records, err := c.findTxtRecords(domain, fqdn)
if err != nil { if err != nil {
@ -119,7 +119,7 @@ func (c *DNSProvider) findTxtRecords(domain, fqdn string) (string, []vultr.DNSRe
} }
func (c *DNSProvider) extractRecordName(fqdn, domain string) string { func (c *DNSProvider) extractRecordName(fqdn, domain string) string {
name := acmev2.UnFqdn(fqdn) name := acme.UnFqdn(fqdn)
if idx := strings.Index(name, "."+domain); idx != -1 { if idx := strings.Index(name, "."+domain); idx != -1 {
return name[:idx] return name[:idx]
} }

2
vendor/golang.org/x/oauth2/LICENSE generated vendored
View file

@ -1,4 +1,4 @@
Copyright (c) 2009 The oauth2 Authors. All rights reserved. Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are

View file

@ -1,25 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
// App Engine hooks.
package oauth2
import (
"net/http"
"golang.org/x/net/context"
"golang.org/x/oauth2/internal"
"google.golang.org/appengine/urlfetch"
)
func init() {
internal.RegisterContextClientFunc(contextClientAppEngine)
}
func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
return urlfetch.Client(ctx), nil
}

View file

@ -18,14 +18,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
// DefaultCredentials holds "Application Default Credentials".
// For more details, see:
// https://developers.google.com/accounts/docs/application-default-credentials
type DefaultCredentials struct {
ProjectID string // may be empty
TokenSource oauth2.TokenSource
}
// DefaultClient returns an HTTP Client that uses the // DefaultClient returns an HTTP Client that uses the
// DefaultTokenSource to obtain authentication credentials. // DefaultTokenSource to obtain authentication credentials.
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
@ -47,25 +39,12 @@ func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSourc
return creds.TokenSource, nil return creds.TokenSource, nil
} }
// FindDefaultCredentials searches for "Application Default Credentials". // Common implementation for FindDefaultCredentials.
// func findDefaultCredentials(ctx context.Context, scopes []string) (*DefaultCredentials, error) {
// It looks for credentials in the following places,
// preferring the first location found:
//
// 1. A JSON file whose path is specified by the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// 2. A JSON file in a location known to the gcloud command-line tool.
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
// 3. On Google App Engine it uses the appengine.AccessToken function.
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
// credentials from the metadata server.
// (In this final case any provided scopes are ignored.)
func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCredentials, error) {
// First, try the environment variable. // First, try the environment variable.
const envVar = "GOOGLE_APPLICATION_CREDENTIALS" const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
if filename := os.Getenv(envVar); filename != "" { if filename := os.Getenv(envVar); filename != "" {
creds, err := readCredentialsFile(ctx, filename, scope) creds, err := readCredentialsFile(ctx, filename, scopes)
if err != nil { if err != nil {
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
} }
@ -74,7 +53,7 @@ func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCrede
// Second, try a well-known file. // Second, try a well-known file.
filename := wellKnownFile() filename := wellKnownFile()
if creds, err := readCredentialsFile(ctx, filename, scope); err == nil { if creds, err := readCredentialsFile(ctx, filename, scopes); err == nil {
return creds, nil return creds, nil
} else if !os.IsNotExist(err) { } else if !os.IsNotExist(err) {
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
@ -84,7 +63,7 @@ func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCrede
if appengineTokenFunc != nil && !appengineFlex { if appengineTokenFunc != nil && !appengineFlex {
return &DefaultCredentials{ return &DefaultCredentials{
ProjectID: appengineAppIDFunc(ctx), ProjectID: appengineAppIDFunc(ctx),
TokenSource: AppEngineTokenSource(ctx, scope...), TokenSource: AppEngineTokenSource(ctx, scopes...),
}, nil }, nil
} }
@ -102,6 +81,23 @@ func FindDefaultCredentials(ctx context.Context, scope ...string) (*DefaultCrede
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url)
} }
// Common implementation for CredentialsFromJSON.
func credentialsFromJSON(ctx context.Context, jsonData []byte, scopes []string) (*DefaultCredentials, error) {
var f credentialsFile
if err := json.Unmarshal(jsonData, &f); err != nil {
return nil, err
}
ts, err := f.tokenSource(ctx, append([]string(nil), scopes...))
if err != nil {
return nil, err
}
return &DefaultCredentials{
ProjectID: f.ProjectID,
TokenSource: ts,
JSON: jsonData,
}, nil
}
func wellKnownFile() string { func wellKnownFile() string {
const f = "application_default_credentials.json" const f = "application_default_credentials.json"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
@ -115,16 +111,5 @@ func readCredentialsFile(ctx context.Context, filename string, scopes []string)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var f credentialsFile return CredentialsFromJSON(ctx, b, scopes...)
if err := json.Unmarshal(b, &f); err != nil {
return nil, err
}
ts, err := f.tokenSource(ctx, append([]string(nil), scopes...))
if err != nil {
return nil, err
}
return &DefaultCredentials{
ProjectID: f.ProjectID,
TokenSource: ts,
}, nil
} }

42
vendor/golang.org/x/oauth2/google/doc_go19.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.9
// Package google provides support for making OAuth2 authorized and authenticated
// HTTP requests to Google APIs. It supports the Web server flow, client-side
// credentials, service accounts, Google Compute Engine service accounts, and Google
// App Engine service accounts.
//
// A brief overview of the package follows. For more information, please read
// https://developers.google.com/accounts/docs/OAuth2
// and
// https://developers.google.com/accounts/docs/application-default-credentials.
//
// OAuth2 Configs
//
// Two functions in this package return golang.org/x/oauth2.Config values from Google credential
// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON,
// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or
// create an http.Client.
//
//
// Credentials
//
// The Credentials type represents Google credentials, including Application Default
// Credentials.
//
// Use FindDefaultCredentials to obtain Application Default Credentials.
// FindDefaultCredentials looks in some well-known places for a credentials file, and
// will call AppEngineTokenSource or ComputeTokenSource as needed.
//
// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials,
// then use the credentials to construct an http.Client or an oauth2.TokenSource.
//
// Use CredentialsFromJSON to obtain credentials from either of the two JSON formats
// described in OAuth2 Configs, above. The TokenSource in the returned value is the
// same as the one obtained from the oauth2.Config returned from ConfigFromJSON or
// JWTConfigFromJSON, but the Credentials may contain additional information
// that is useful is some circumstances.
package google // import "golang.org/x/oauth2/google"

43
vendor/golang.org/x/oauth2/google/doc_not_go19.go generated vendored Normal file
View file

@ -0,0 +1,43 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.9
// Package google provides support for making OAuth2 authorized and authenticated
// HTTP requests to Google APIs. It supports the Web server flow, client-side
// credentials, service accounts, Google Compute Engine service accounts, and Google
// App Engine service accounts.
//
// A brief overview of the package follows. For more information, please read
// https://developers.google.com/accounts/docs/OAuth2
// and
// https://developers.google.com/accounts/docs/application-default-credentials.
//
// OAuth2 Configs
//
// Two functions in this package return golang.org/x/oauth2.Config values from Google credential
// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON,
// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or
// create an http.Client.
//
//
// Credentials
//
// The DefaultCredentials type represents Google Application Default Credentials, as
// well as other forms of credential.
//
// Use FindDefaultCredentials to obtain Application Default Credentials.
// FindDefaultCredentials looks in some well-known places for a credentials file, and
// will call AppEngineTokenSource or ComputeTokenSource as needed.
//
// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials,
// then use the credentials to construct an http.Client or an oauth2.TokenSource.
//
// Use CredentialsFromJSON to obtain credentials from either of the two JSON
// formats described in OAuth2 Configs, above. (The DefaultCredentials returned may
// not be "Application Default Credentials".) The TokenSource in the returned value
// is the same as the one obtained from the oauth2.Config returned from
// ConfigFromJSON or JWTConfigFromJSON, but the DefaultCredentials may contain
// additional information that is useful is some circumstances.
package google // import "golang.org/x/oauth2/google"

57
vendor/golang.org/x/oauth2/google/go19.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.9
package google
import (
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
// Credentials holds Google credentials, including "Application Default Credentials".
// For more details, see:
// https://developers.google.com/accounts/docs/application-default-credentials
type Credentials struct {
ProjectID string // may be empty
TokenSource oauth2.TokenSource
// JSON contains the raw bytes from a JSON credentials file.
// This field may be nil if authentication is provided by the
// environment and not with a credentials file, e.g. when code is
// running on Google Cloud Platform.
JSON []byte
}
// DefaultCredentials is the old name of Credentials.
//
// Deprecated: use Credentials instead.
type DefaultCredentials = Credentials
// FindDefaultCredentials searches for "Application Default Credentials".
//
// It looks for credentials in the following places,
// preferring the first location found:
//
// 1. A JSON file whose path is specified by the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// 2. A JSON file in a location known to the gcloud command-line tool.
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
// 3. On Google App Engine it uses the appengine.AccessToken function.
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
// credentials from the metadata server.
// (In this final case any provided scopes are ignored.)
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) {
return findDefaultCredentials(ctx, scopes)
}
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
// represent either a Google Developers Console client_credentials.json file (as in
// ConfigFromJSON) or a Google Developers service account key file (as in
// JWTConfigFromJSON).
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) {
return credentialsFromJSON(ctx, jsonData, scopes)
}

View file

@ -2,17 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package google provides support for making OAuth2 authorized and package google
// authenticated HTTP requests to Google APIs.
// It supports the Web server flow, client-side credentials, service accounts,
// Google Compute Engine service accounts, and Google App Engine service
// accounts.
//
// For more information, please read
// https://developers.google.com/accounts/docs/OAuth2
// and
// https://developers.google.com/accounts/docs/application-default-credentials.
package google // import "golang.org/x/oauth2/google"
import ( import (
"encoding/json" "encoding/json"

54
vendor/golang.org/x/oauth2/google/not_go19.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.9
package google
import (
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
// DefaultCredentials holds Google credentials, including "Application Default Credentials".
// For more details, see:
// https://developers.google.com/accounts/docs/application-default-credentials
type DefaultCredentials struct {
ProjectID string // may be empty
TokenSource oauth2.TokenSource
// JSON contains the raw bytes from a JSON credentials file.
// This field may be nil if authentication is provided by the
// environment and not with a credentials file, e.g. when code is
// running on Google Cloud Platform.
JSON []byte
}
// FindDefaultCredentials searches for "Application Default Credentials".
//
// It looks for credentials in the following places,
// preferring the first location found:
//
// 1. A JSON file whose path is specified by the
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
// 2. A JSON file in a location known to the gcloud command-line tool.
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
// 3. On Google App Engine it uses the appengine.AccessToken function.
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
// credentials from the metadata server.
// (In this final case any provided scopes are ignored.)
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*DefaultCredentials, error) {
return findDefaultCredentials(ctx, scopes)
}
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
// represent either a Google Developers Console client_credentials.json file (as in
// ConfigFromJSON) or a Google Developers service account key file (as in
// JWTConfigFromJSON).
//
// Note: despite the name, the returned credentials may not be Application Default Credentials.
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*DefaultCredentials, error) {
return credentialsFromJSON(ctx, jsonData, scopes)
}

View file

@ -5,9 +5,11 @@
package google package google
import ( import (
"bufio"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"os" "os"
"os/user" "os/user"
@ -18,7 +20,6 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/internal"
) )
type sdkCredentials struct { type sdkCredentials struct {
@ -76,7 +77,7 @@ func NewSDKConfig(account string) (*SDKConfig, error) {
return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err) return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err)
} }
defer f.Close() defer f.Close()
ini, err := internal.ParseINI(f) ini, err := parseINI(f)
if err != nil { if err != nil {
return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err) return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err)
} }
@ -146,6 +147,34 @@ func (c *SDKConfig) Scopes() []string {
return c.conf.Scopes return c.conf.Scopes
} }
func parseINI(ini io.Reader) (map[string]map[string]string, error) {
result := map[string]map[string]string{
"": {}, // root section
}
scanner := bufio.NewScanner(ini)
currentSection := ""
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, ";") {
// comment.
continue
}
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
currentSection = strings.TrimSpace(line[1 : len(line)-1])
result[currentSection] = map[string]string{}
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 && parts[0] != "" {
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning ini: %v", err)
}
return result, nil
}
// sdkConfigPath tries to guess where the gcloud config is located. // sdkConfigPath tries to guess where the gcloud config is located.
// It can be overridden during tests. // It can be overridden during tests.
var sdkConfigPath = func() (string, error) { var sdkConfigPath = func() (string, error) {

View file

@ -0,0 +1,13 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build appengine
package internal
import "google.golang.org/appengine/urlfetch"
func init() {
appengineClientHook = urlfetch.Client
}

6
vendor/golang.org/x/oauth2/internal/doc.go generated vendored Normal file
View file

@ -0,0 +1,6 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package internal contains support packages for oauth2 package.
package internal

View file

@ -2,18 +2,14 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package internal contains support packages for oauth2 package.
package internal package internal
import ( import (
"bufio"
"crypto/rsa" "crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"io"
"strings"
) )
// ParseKey converts the binary contents of a private key file // ParseKey converts the binary contents of a private key file
@ -39,38 +35,3 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) {
} }
return parsed, nil return parsed, nil
} }
func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
result := map[string]map[string]string{
"": {}, // root section
}
scanner := bufio.NewScanner(ini)
currentSection := ""
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, ";") {
// comment.
continue
}
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
currentSection = strings.TrimSpace(line[1 : len(line)-1])
result[currentSection] = map[string]string{}
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 && parts[0] != "" {
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning ini: %v", err)
}
return result, nil
}
func CondVal(v string) []string {
if v == "" {
return nil
}
return []string{v}
}

View file

@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package internal contains support packages for oauth2 package.
package internal package internal
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -18,9 +18,10 @@ import (
"time" "time"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/net/context/ctxhttp"
) )
// Token represents the crendentials used to authorize // Token represents the credentials used to authorize
// the requests to access protected resources on the OAuth 2.0 // the requests to access protected resources on the OAuth 2.0
// provider's backend. // provider's backend.
// //
@ -102,9 +103,11 @@ var brokenAuthHeaderProviders = []string{
"https://api.twitch.tv/", "https://api.twitch.tv/",
"https://app.box.com/", "https://app.box.com/",
"https://connect.stripe.com/", "https://connect.stripe.com/",
"https://graph.facebook.com", // see https://github.com/golang/oauth2/issues/214 "https://login.mailchimp.com/",
"https://login.microsoftonline.com/", "https://login.microsoftonline.com/",
"https://login.salesforce.com/", "https://login.salesforce.com/",
"https://login.windows.net",
"https://login.live.com/",
"https://oauth.sandbox.trainingpeaks.com/", "https://oauth.sandbox.trainingpeaks.com/",
"https://oauth.trainingpeaks.com/", "https://oauth.trainingpeaks.com/",
"https://oauth.vk.com/", "https://oauth.vk.com/",
@ -120,11 +123,18 @@ var brokenAuthHeaderProviders = []string{
"https://www.wunderlist.com/oauth/", "https://www.wunderlist.com/oauth/",
"https://api.patreon.com/", "https://api.patreon.com/",
"https://sandbox.codeswholesale.com/oauth/token", "https://sandbox.codeswholesale.com/oauth/token",
"https://api.sipgate.com/v1/authorization/oauth",
"https://api.medium.com/v1/tokens",
"https://log.finalsurge.com/oauth/token",
"https://multisport.todaysplan.com.au/rest/oauth/access_token",
"https://whats.todaysplan.com.au/rest/oauth/access_token",
} }
// brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints. // brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints.
var brokenAuthHeaderDomains = []string{ var brokenAuthHeaderDomains = []string{
".auth0.com",
".force.com", ".force.com",
".myshopify.com",
".okta.com", ".okta.com",
".oktapreview.com", ".oktapreview.com",
} }
@ -165,10 +175,6 @@ func providerAuthHeaderWorks(tokenURL string) bool {
} }
func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) { func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values) (*Token, error) {
hc, err := ContextClient(ctx)
if err != nil {
return nil, err
}
bustedAuth := !providerAuthHeaderWorks(tokenURL) bustedAuth := !providerAuthHeaderWorks(tokenURL)
if bustedAuth { if bustedAuth {
if clientID != "" { if clientID != "" {
@ -184,9 +190,9 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if !bustedAuth { if !bustedAuth {
req.SetBasicAuth(clientID, clientSecret) req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
} }
r, err := hc.Do(req) r, err := ctxhttp.Do(ctx, ContextClient(ctx), req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -196,7 +202,10 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
} }
if code := r.StatusCode; code < 200 || code > 299 { if code := r.StatusCode; code < 200 || code > 299 {
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body) return nil, &RetrieveError{
Response: r,
Body: body,
}
} }
var token *Token var token *Token
@ -243,5 +252,17 @@ func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string,
if token.RefreshToken == "" { if token.RefreshToken == "" {
token.RefreshToken = v.Get("refresh_token") token.RefreshToken = v.Get("refresh_token")
} }
if token.AccessToken == "" {
return token, errors.New("oauth2: server response missing access_token")
}
return token, nil return token, nil
} }
type RetrieveError struct {
Response *http.Response
Body []byte
}
func (r *RetrieveError) Error() string {
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
}

View file

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package internal contains support packages for oauth2 package.
package internal package internal
import ( import (
@ -20,50 +19,16 @@ var HTTPClient ContextKey
// because nobody else can create a ContextKey, being unexported. // because nobody else can create a ContextKey, being unexported.
type ContextKey struct{} type ContextKey struct{}
// ContextClientFunc is a func which tries to return an *http.Client var appengineClientHook func(context.Context) *http.Client
// given a Context value. If it returns an error, the search stops
// with that error. If it returns (nil, nil), the search continues
// down the list of registered funcs.
type ContextClientFunc func(context.Context) (*http.Client, error)
var contextClientFuncs []ContextClientFunc func ContextClient(ctx context.Context) *http.Client {
func RegisterContextClientFunc(fn ContextClientFunc) {
contextClientFuncs = append(contextClientFuncs, fn)
}
func ContextClient(ctx context.Context) (*http.Client, error) {
if ctx != nil { if ctx != nil {
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
return hc, nil return hc
} }
} }
for _, fn := range contextClientFuncs { if appengineClientHook != nil {
c, err := fn(ctx) return appengineClientHook(ctx)
if err != nil {
return nil, err
}
if c != nil {
return c, nil
}
} }
return http.DefaultClient, nil return http.DefaultClient
}
func ContextTransport(ctx context.Context) http.RoundTripper {
hc, err := ContextClient(ctx)
// This is a rare error case (somebody using nil on App Engine).
if err != nil {
return ErrorTransport{err}
}
return hc.Transport
}
// ErrorTransport returns the specified error on RoundTrip.
// This RoundTripper should be used in rare error cases where
// error handling can be postponed to response handling time.
type ErrorTransport struct{ Err error }
func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) {
return nil, t.Err
} }

View file

@ -124,7 +124,10 @@ func (js jwtSource) Token() (*oauth2.Token, error) {
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
} }
if c := resp.StatusCode; c < 200 || c > 299 { if c := resp.StatusCode; c < 200 || c > 299 {
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, body) return nil, &oauth2.RetrieveError{
Response: resp,
Body: body,
}
} }
// tokenRes is the JSON response body. // tokenRes is the JSON response body.
var tokenRes struct { var tokenRes struct {

49
vendor/golang.org/x/oauth2/oauth2.go generated vendored
View file

@ -117,7 +117,7 @@ func SetAuthURLParam(key, value string) AuthCodeOption {
// that asks for permissions for the required scopes explicitly. // that asks for permissions for the required scopes explicitly.
// //
// State is a token to protect the user from CSRF attacks. You must // State is a token to protect the user from CSRF attacks. You must
// always provide a non-zero string and validate that it matches the // always provide a non-empty string and validate that it matches the
// the state query parameter on your redirect callback. // the state query parameter on your redirect callback.
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info. // See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
// //
@ -129,9 +129,16 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
v := url.Values{ v := url.Values{
"response_type": {"code"}, "response_type": {"code"},
"client_id": {c.ClientID}, "client_id": {c.ClientID},
"redirect_uri": internal.CondVal(c.RedirectURL), }
"scope": internal.CondVal(strings.Join(c.Scopes, " ")), if c.RedirectURL != "" {
"state": internal.CondVal(state), v.Set("redirect_uri", c.RedirectURL)
}
if len(c.Scopes) > 0 {
v.Set("scope", strings.Join(c.Scopes, " "))
}
if state != "" {
// TODO(light): Docs say never to omit state; don't allow empty.
v.Set("state", state)
} }
for _, opt := range opts { for _, opt := range opts {
opt.setValue(v) opt.setValue(v)
@ -157,12 +164,15 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
// The HTTP client to use is derived from the context. // The HTTP client to use is derived from the context.
// If nil, http.DefaultClient is used. // If nil, http.DefaultClient is used.
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
return retrieveToken(ctx, c, url.Values{ v := url.Values{
"grant_type": {"password"}, "grant_type": {"password"},
"username": {username}, "username": {username},
"password": {password}, "password": {password},
"scope": internal.CondVal(strings.Join(c.Scopes, " ")), }
}) if len(c.Scopes) > 0 {
v.Set("scope", strings.Join(c.Scopes, " "))
}
return retrieveToken(ctx, c, v)
} }
// Exchange converts an authorization code into a token. // Exchange converts an authorization code into a token.
@ -176,11 +186,14 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor
// The code will be in the *http.Request.FormValue("code"). Before // The code will be in the *http.Request.FormValue("code"). Before
// calling Exchange, be sure to validate FormValue("state"). // calling Exchange, be sure to validate FormValue("state").
func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) { func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) {
return retrieveToken(ctx, c, url.Values{ v := url.Values{
"grant_type": {"authorization_code"}, "grant_type": {"authorization_code"},
"code": {code}, "code": {code},
"redirect_uri": internal.CondVal(c.RedirectURL), }
}) if c.RedirectURL != "" {
v.Set("redirect_uri", c.RedirectURL)
}
return retrieveToken(ctx, c, v)
} }
// Client returns an HTTP client using the provided token. // Client returns an HTTP client using the provided token.
@ -291,20 +304,20 @@ var HTTPClient internal.ContextKey
// NewClient creates an *http.Client from a Context and TokenSource. // NewClient creates an *http.Client from a Context and TokenSource.
// The returned client is not valid beyond the lifetime of the context. // The returned client is not valid beyond the lifetime of the context.
// //
// Note that if a custom *http.Client is provided via the Context it
// is used only for token acquisition and is not used to configure the
// *http.Client returned from NewClient.
//
// As a special case, if src is nil, a non-OAuth2 client is returned // As a special case, if src is nil, a non-OAuth2 client is returned
// using the provided context. This exists to support related OAuth2 // using the provided context. This exists to support related OAuth2
// packages. // packages.
func NewClient(ctx context.Context, src TokenSource) *http.Client { func NewClient(ctx context.Context, src TokenSource) *http.Client {
if src == nil { if src == nil {
c, err := internal.ContextClient(ctx) return internal.ContextClient(ctx)
if err != nil {
return &http.Client{Transport: internal.ErrorTransport{Err: err}}
}
return c
} }
return &http.Client{ return &http.Client{
Transport: &Transport{ Transport: &Transport{
Base: internal.ContextTransport(ctx), Base: internal.ContextClient(ctx).Transport,
Source: ReuseTokenSource(nil, src), Source: ReuseTokenSource(nil, src),
}, },
} }

21
vendor/golang.org/x/oauth2/token.go generated vendored
View file

@ -5,6 +5,7 @@
package oauth2 package oauth2
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -20,7 +21,7 @@ import (
// expirations due to client-server time mismatches. // expirations due to client-server time mismatches.
const expiryDelta = 10 * time.Second const expiryDelta = 10 * time.Second
// Token represents the crendentials used to authorize // Token represents the credentials used to authorize
// the requests to access protected resources on the OAuth 2.0 // the requests to access protected resources on the OAuth 2.0
// provider's backend. // provider's backend.
// //
@ -123,7 +124,7 @@ func (t *Token) expired() bool {
if t.Expiry.IsZero() { if t.Expiry.IsZero() {
return false return false
} }
return t.Expiry.Add(-expiryDelta).Before(time.Now()) return t.Expiry.Round(0).Add(-expiryDelta).Before(time.Now())
} }
// Valid reports whether t is non-nil, has an AccessToken, and is not expired. // Valid reports whether t is non-nil, has an AccessToken, and is not expired.
@ -152,7 +153,23 @@ func tokenFromInternal(t *internal.Token) *Token {
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v) tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
if err != nil { if err != nil {
if rErr, ok := err.(*internal.RetrieveError); ok {
return nil, (*RetrieveError)(rErr)
}
return nil, err return nil, err
} }
return tokenFromInternal(tk), nil return tokenFromInternal(tk), nil
} }
// RetrieveError is the error returned when the token endpoint returns a
// non-2XX HTTP status code.
type RetrieveError struct {
Response *http.Response
// Body is the body that was consumed by reading Response.Body.
// It may be truncated.
Body []byte
}
func (r *RetrieveError) Error() string {
return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
}

View file

@ -31,9 +31,17 @@ type Transport struct {
} }
// RoundTrip authorizes and authenticates the request with an // RoundTrip authorizes and authenticates the request with an
// access token. If no token exists or token is expired, // access token from Transport's Source.
// tries to refresh/fetch a new token.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
reqBodyClosed := false
if req.Body != nil {
defer func() {
if !reqBodyClosed {
req.Body.Close()
}
}()
}
if t.Source == nil { if t.Source == nil {
return nil, errors.New("oauth2: Transport's Source is nil") return nil, errors.New("oauth2: Transport's Source is nil")
} }
@ -46,6 +54,10 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
token.SetAuthHeader(req2) token.SetAuthHeader(req2)
t.setModReq(req, req2) t.setModReq(req, req2)
res, err := t.base().RoundTrip(req2) res, err := t.base().RoundTrip(req2)
// req.Body is assumed to have been closed by the base RoundTripper.
reqBodyClosed = true
if err != nil { if err != nil {
t.setModReq(req, nil) t.setModReq(req, nil)
return nil, err return nil, err

Some files were not shown because too many files have changed in this diff Show more