Merge tag 'v1.6.3' into master
This commit is contained in:
commit
a5beeb4f04
142 changed files with 5276 additions and 1868 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -1,5 +1,31 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## [v1.6.3](https://github.com/containous/traefik/tree/v1.6.3) (2018-06-05)
|
||||||
|
[All Commits](https://github.com/containous/traefik/compare/v1.6.2...v1.6.3)
|
||||||
|
|
||||||
|
**Enhancements:**
|
||||||
|
- **[acme]** Add user agent for ACME ([#3431](https://github.com/containous/traefik/pull/3431) by [ldez](https://github.com/ldez))
|
||||||
|
- **[acme]** Use to the stable version of Lego ([#3418](https://github.com/containous/traefik/pull/3418) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme,cluster]** Improve ACME account registration URI management ([#3398](https://github.com/containous/traefik/pull/3398) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[acme,cluster]** Remove ACME empty certificates from KV store ([#3389](https://github.com/containous/traefik/pull/3389) by [nmengin](https://github.com/nmengin))
|
||||||
|
- **[consulcatalog]** Reflect changes in catalog healthy nodes in healthCheck watch ([#3390](https://github.com/containous/traefik/pull/3390) by [thebinary](https://github.com/thebinary))
|
||||||
|
- **[consulcatalog]** Detect change when service or node are in maintenance mode ([#3434](https://github.com/containous/traefik/pull/3434) by [mmatur](https://github.com/mmatur))
|
||||||
|
- **[k8s]** Update Kubernetes provider to support IPv6 Backends ([#3432](https://github.com/containous/traefik/pull/3432) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[logs,middleware]** Add URL and Host for some access logs. ([#3430](https://github.com/containous/traefik/pull/3430) by [ldez](https://github.com/ldez))
|
||||||
|
- **[metrics]** Improve Prometheus metrics removal ([#3287](https://github.com/containous/traefik/pull/3287) by [marco-jantke](https://github.com/marco-jantke))
|
||||||
|
- **[middleware]** Whitelist and XFF. ([#3411](https://github.com/containous/traefik/pull/3411) by [ldez](https://github.com/ldez))
|
||||||
|
- **[middleware]** Error pages and header merge ([#3394](https://github.com/containous/traefik/pull/3394) by [ldez](https://github.com/ldez))
|
||||||
|
- **[websocket]** Includes the headers in the HTTP response of a websocket request ([#3425](https://github.com/containous/traefik/pull/3425) by [geraldcroes](https://github.com/geraldcroes))
|
||||||
|
- **[webui]** Webui Whitelist overflow. ([#3412](https://github.com/containous/traefik/pull/3412) by [ldez](https://github.com/ldez))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme]** Docs: ACME Overhaul ([#3421](https://github.com/containous/traefik/pull/3421) by [Dargmuesli](https://github.com/Dargmuesli))
|
||||||
|
- **[acme]** Minor documentation changes ([#3405](https://github.com/containous/traefik/pull/3405) by [amincheloh](https://github.com/amincheloh))
|
||||||
|
- **[k8s]** Helm installation using values ([#3392](https://github.com/containous/traefik/pull/3392) by [erikaulin](https://github.com/erikaulin))
|
||||||
|
- **[k8s]** Update Kubernetes Port Documentation ([#3368](https://github.com/containous/traefik/pull/3368) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
|
||||||
## [v1.6.2](https://github.com/containous/traefik/tree/v1.6.2) (2018-05-22)
|
## [v1.6.2](https://github.com/containous/traefik/tree/v1.6.2) (2018-05-22)
|
||||||
[All Commits](https://github.com/containous/traefik/compare/v1.6.1...v1.6.2)
|
[All Commits](https://github.com/containous/traefik/compare/v1.6.1...v1.6.2)
|
||||||
|
|
||||||
|
|
30
Gopkg.lock
generated
30
Gopkg.lock
generated
|
@ -318,9 +318,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"
|
||||||
|
@ -757,6 +758,7 @@
|
||||||
version = "v1.3.7"
|
version = "v1.3.7"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
name = "github.com/jjcollinge/servicefabric"
|
name = "github.com/jjcollinge/servicefabric"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe"
|
revision = "8eebe170fa1ba25d3dfb928b3f86a7313b13b9fe"
|
||||||
|
@ -987,9 +989,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"
|
||||||
|
@ -1212,7 +1215,7 @@
|
||||||
"roundrobin",
|
"roundrobin",
|
||||||
"utils"
|
"utils"
|
||||||
]
|
]
|
||||||
revision = "6956548a7fa4272adeadf828455109c53933ea86"
|
revision = "7a2284ad8d6f4d362a6b38f3cdcc812291dce293"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/vulcand/predicate"
|
name = "github.com/vulcand/predicate"
|
||||||
|
@ -1238,10 +1241,11 @@
|
||||||
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
|
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "containous-fork"
|
branch = "master"
|
||||||
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",
|
||||||
|
@ -1259,9 +1263,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",
|
||||||
|
@ -1275,8 +1279,7 @@
|
||||||
"providers/dns/route53",
|
"providers/dns/route53",
|
||||||
"providers/dns/vultr"
|
"providers/dns/vultr"
|
||||||
]
|
]
|
||||||
revision = "3d653ee2ee38f1d71beb5f09b37b23344eff0ab3"
|
revision = "7fedfd1388f016c7ca7ed92a7f2024d06a7e20d8"
|
||||||
source = "github.com/containous/lego"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -1316,6 +1319,7 @@
|
||||||
revision = "22ae77b79946ea320088417e4d50825671d82d57"
|
revision = "22ae77b79946ea320088417e4d50825671d82d57"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
name = "golang.org/x/oauth2"
|
name = "golang.org/x/oauth2"
|
||||||
packages = [
|
packages = [
|
||||||
".",
|
".",
|
||||||
|
@ -1324,7 +1328,7 @@
|
||||||
"jws",
|
"jws",
|
||||||
"jwt"
|
"jwt"
|
||||||
]
|
]
|
||||||
revision = "7fdf09982454086d5570c7db3e11f360194830ca"
|
revision = "ec22f46f877b4505e0117eeaab541714644fdd28"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -1363,6 +1367,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",
|
||||||
|
@ -1370,7 +1375,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"
|
||||||
|
@ -1451,6 +1456,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",
|
||||||
|
@ -1460,7 +1466,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"
|
||||||
|
@ -1681,6 +1687,6 @@
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "4d18e76b6d54a2698d3f1acd9f43110c609e3f61e72d480b9acd2327720dcc5c"
|
inputs-digest = "f9bdfe14aca106f6bfc75da6f7282791c12c503b65581797bbb484566858a2f3"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -181,9 +181,9 @@
|
||||||
name = "github.com/vulcand/oxy"
|
name = "github.com/vulcand/oxy"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "containous-fork"
|
branch = "master"
|
||||||
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"
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -16,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
|
||||||
|
@ -44,6 +45,11 @@ func (a *Account) Init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.RemoveAccountV1Values()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to remove ACME Account V1 values during account initialization: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
for _, cert := range a.ChallengeCerts {
|
for _, cert := range a.ChallengeCerts {
|
||||||
if cert.certificate == nil {
|
if cert.certificate == nil {
|
||||||
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
certificate, err := tls.X509KeyPair(cert.Certificate, cert.PrivateKey)
|
||||||
|
@ -108,6 +114,29 @@ func (a *Account) GetPrivateKey() crypto.PrivateKey {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveAccountV1Values removes ACME account V1 values
|
||||||
|
func (a *Account) RemoveAccountV1Values() error {
|
||||||
|
// Check if ACME Account is in ACME V1 format
|
||||||
|
if a.Registration != nil {
|
||||||
|
isOldRegistration, err := regexp.MatchString(acmeprovider.RegistrationURLPathV1Regexp, a.Registration.URI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isOldRegistration {
|
||||||
|
a.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) reset() {
|
||||||
|
log.Debug("Reset ACME account object.")
|
||||||
|
a.Email = ""
|
||||||
|
a.Registration = nil
|
||||||
|
a.PrivateKey = nil
|
||||||
|
}
|
||||||
|
|
||||||
// Certificate is used to store certificate info
|
// Certificate is used to store certificate info
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
Domain string
|
Domain string
|
||||||
|
@ -157,11 +186,23 @@ func (dc *DomainsCertificates) removeDuplicates() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc *DomainsCertificates) removeEmpty() {
|
||||||
|
certs := []*DomainsCertificate{}
|
||||||
|
for _, cert := range dc.Certs {
|
||||||
|
if cert.Certificate != nil && len(cert.Certificate.Certificate) > 0 && len(cert.Certificate.PrivateKey) > 0 {
|
||||||
|
certs = append(certs, cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc.Certs = certs
|
||||||
|
}
|
||||||
|
|
||||||
// Init DomainsCertificates
|
// Init DomainsCertificates
|
||||||
func (dc *DomainsCertificates) Init() error {
|
func (dc *DomainsCertificates) Init() error {
|
||||||
dc.lock.Lock()
|
dc.lock.Lock()
|
||||||
defer dc.lock.Unlock()
|
defer dc.lock.Unlock()
|
||||||
|
|
||||||
|
dc.removeEmpty()
|
||||||
|
|
||||||
for _, domainsCertificate := range dc.Certs {
|
for _, domainsCertificate := range dc.Certs {
|
||||||
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
tlsCert, err := tls.X509KeyPair(domainsCertificate.Certificate.Certificate, domainsCertificate.Certificate.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
15
acme/acme.go
15
acme/acme.go
|
@ -25,8 +25,10 @@ import (
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
"github.com/containous/traefik/tls/generate"
|
"github.com/containous/traefik/tls/generate"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/containous/traefik/version"
|
||||||
"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 +44,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."`
|
||||||
|
@ -64,10 +66,11 @@ type ACME struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ACME) init() error {
|
func (a *ACME) init() error {
|
||||||
|
acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||||
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()
|
||||||
|
@ -180,6 +183,10 @@ func (a *ACME) leadershipListener(elected bool) error {
|
||||||
|
|
||||||
account := object.(*Account)
|
account := object.(*Account)
|
||||||
account.Init()
|
account.Init()
|
||||||
|
// Reset Account values if caServer changed, thus registration URI can be updated
|
||||||
|
if account != nil && account.Registration != nil && !strings.HasPrefix(account.Registration.URI, a.CAServer) {
|
||||||
|
account.reset()
|
||||||
|
}
|
||||||
|
|
||||||
var needRegister bool
|
var needRegister bool
|
||||||
if account == nil || len(account.Email) == 0 {
|
if account == nil || len(account.Email) == 0 {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,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) {
|
||||||
|
@ -550,3 +551,268 @@ func TestAcme_getCertificateForDomain(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRemoveEmptyCertificates(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
fooCert, fooKey, _ := generate.KeyPair("foo.com", now)
|
||||||
|
acmeCert, acmeKey, _ := generate.KeyPair("acme.wtf", now.Add(24*time.Hour))
|
||||||
|
barCert, barKey, _ := generate.KeyPair("bar.com", now)
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
dc *DomainsCertificates
|
||||||
|
expectedDc *DomainsCertificates
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "No empty certificate",
|
||||||
|
dc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: fooCert,
|
||||||
|
PrivateKey: fooKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: barCert,
|
||||||
|
PrivateKey: barKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: fooCert,
|
||||||
|
PrivateKey: fooKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: barCert,
|
||||||
|
PrivateKey: barKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "First certificate is nil",
|
||||||
|
dc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: barCert,
|
||||||
|
PrivateKey: barKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: nil,
|
||||||
|
PrivateKey: barKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Last certificate is empty",
|
||||||
|
dc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: fooCert,
|
||||||
|
PrivateKey: fooKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: fooCert,
|
||||||
|
PrivateKey: fooKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "First and last certificates are nil or empty",
|
||||||
|
dc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{
|
||||||
|
Certificate: acmeCert,
|
||||||
|
PrivateKey: acmeKey,
|
||||||
|
},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "acme.wtf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "All certificates are nil or empty",
|
||||||
|
dc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{
|
||||||
|
{
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "foo24.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Certificate: &Certificate{},
|
||||||
|
Domains: types.Domain{
|
||||||
|
Main: "bar.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedDc: &DomainsCertificates{
|
||||||
|
Certs: []*DomainsCertificate{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
a := &Account{DomainsCertificate: *test.dc}
|
||||||
|
a.Init()
|
||||||
|
|
||||||
|
assert.Equal(t, len(test.expectedDc.Certs), len(a.DomainsCertificate.Certs))
|
||||||
|
sort.Sort(&a.DomainsCertificate)
|
||||||
|
sort.Sort(test.expectedDc)
|
||||||
|
for key, value := range test.expectedDc.Certs {
|
||||||
|
assert.Equal(t, value.Domains.Main, a.DomainsCertificate.Certs[key].Domains.Main)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/containous/traefik/log"
|
"github.com/containous/traefik/log"
|
||||||
"github.com/containous/traefik/provider/acme"
|
"github.com/containous/traefik/provider/acme"
|
||||||
|
@ -51,25 +50,6 @@ func (s *LocalStore) Get() (*Account, error) {
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAccountV1Values removes ACME account V1 values
|
|
||||||
func RemoveAccountV1Values(account *Account) error {
|
|
||||||
// Check if ACME Account is in ACME V1 format
|
|
||||||
if account != nil && account.Registration != nil {
|
|
||||||
isOldRegistration, err := regexp.MatchString(acme.RegistrationURLPathV1Regexp, account.Registration.URI)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if isOldRegistration {
|
|
||||||
account.Email = ""
|
|
||||||
account.Registration = nil
|
|
||||||
account.PrivateKey = nil
|
|
||||||
account.KeyType = "RSA4096"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility)
|
// ConvertToNewFormat converts old acme.json format to the new one and store the result into the file (used for the backward compatibility)
|
||||||
func ConvertToNewFormat(fileName string) {
|
func ConvertToNewFormat(fileName string) {
|
||||||
localStore := acme.NewLocalStore(fileName)
|
localStore := acme.NewLocalStore(fileName)
|
||||||
|
@ -100,13 +80,13 @@ func ConvertToNewFormat(fileName string) {
|
||||||
if account != nil && len(account.Email) > 0 {
|
if account != nil && len(account.Email) > 0 {
|
||||||
err = backupACMEFile(fileName, account)
|
err = backupACMEFile(fileName, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to create a backup for the V1 formatted ACME file: %s", err.Error())
|
log.Errorf("Unable to create a backup for the V1 formatted ACME file: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = RemoveAccountV1Values(account)
|
err = account.RemoveAccountV1Values()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to remove ACME Account V1 values: %s", err.Error())
|
log.Errorf("Unable to remove ACME Account V1 values during format conversion: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ func migrateACMEData(fileName string) (*acme.Account, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = acme.RemoveAccountV1Values(account)
|
err = account.RemoveAccountV1Values()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# ACME (Let's Encrypt) configuration
|
# ACME (Let's Encrypt) Configuration
|
||||||
|
|
||||||
See also [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt).
|
See [Let's Encrypt examples](/user-guide/examples/#lets-encrypt-support) and [Docker & Let's Encrypt user guide](/user-guide/docker-and-lets-encrypt) as well.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
@ -70,14 +70,14 @@ entryPoint = "https"
|
||||||
#
|
#
|
||||||
# overrideCertificates = true
|
# overrideCertificates = true
|
||||||
|
|
||||||
# Enable on demand certificate generation.
|
# Deprecated. Enable on demand certificate generation.
|
||||||
#
|
#
|
||||||
# Optional (Deprecated)
|
# Optional
|
||||||
# Default: false
|
# Default: false
|
||||||
#
|
#
|
||||||
# onDemand = true
|
# onDemand = true
|
||||||
|
|
||||||
# Enable certificate generation on frontends Host rules.
|
# Enable certificate generation on frontends host rules.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: false
|
# Default: false
|
||||||
|
@ -85,8 +85,8 @@ entryPoint = "https"
|
||||||
# onHostRule = true
|
# onHostRule = true
|
||||||
|
|
||||||
# CA server to use.
|
# CA server to use.
|
||||||
# - Uncomment the line to run on the staging let's encrypt server.
|
# Uncomment the line to use Let's Encrypt's staging server,
|
||||||
# - Leave comment to go to prod.
|
# leave commented to go to prod.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
# Default: "https://acme-v02.api.letsencrypt.org/directory"
|
||||||
|
@ -110,15 +110,13 @@ entryPoint = "https"
|
||||||
# sans = ["test1.local1.com", "test2.local1.com"]
|
# sans = ["test1.local1.com", "test2.local1.com"]
|
||||||
# [[acme.domains]]
|
# [[acme.domains]]
|
||||||
# main = "local2.com"
|
# main = "local2.com"
|
||||||
# sans = ["test1.local2.com", "test2.local2.com"]
|
|
||||||
# [[acme.domains]]
|
# [[acme.domains]]
|
||||||
# main = "local3.com"
|
# main = "*.local3.com"
|
||||||
# [[acme.domains]]
|
# sans = ["local3.com", "test1.test1.local3.com"]
|
||||||
# main = "local4.com"
|
|
||||||
|
|
||||||
# Use a HTTP-01 acme challenge.
|
# Use a HTTP-01 ACME challenge.
|
||||||
#
|
#
|
||||||
# Optional but recommend
|
# Optional (but recommended)
|
||||||
#
|
#
|
||||||
[acme.httpChallenge]
|
[acme.httpChallenge]
|
||||||
|
|
||||||
|
@ -128,21 +126,21 @@ entryPoint = "https"
|
||||||
#
|
#
|
||||||
entryPoint = "http"
|
entryPoint = "http"
|
||||||
|
|
||||||
# Use a DNS-01/DNS-01 acme challenge rather than HTTP-01 challenge.
|
# Use a DNS-01 ACME challenge rather than HTTP-01 challenge.
|
||||||
# Note : Mandatory for wildcard certificates generation.
|
# Note: mandatory for wildcard certificate generation.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# [acme.dnsChallenge]
|
# [acme.dnsChallenge]
|
||||||
|
|
||||||
# Provider used.
|
# DNS provider used.
|
||||||
#
|
#
|
||||||
# Required
|
# Required
|
||||||
#
|
#
|
||||||
# provider = "digitalocean"
|
# provider = "digitalocean"
|
||||||
|
|
||||||
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
# By default, the provider will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
# If delayBeforeCheck is greater than zero, avoid this & instead just wait so many seconds.
|
# If delayBeforeCheck is greater than zero, this check is delayed for the configured duration in seconds.
|
||||||
# Useful if internal networks block external DNS queries.
|
# Useful if internal networks block external DNS queries.
|
||||||
#
|
#
|
||||||
# Optional
|
# Optional
|
||||||
|
@ -151,98 +149,134 @@ entryPoint = "https"
|
||||||
# delayBeforeCheck = 0
|
# delayBeforeCheck = 0
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
### `caServer`
|
||||||
If `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through the port 80.
|
|
||||||
These are Let's Encrypt limitations as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
|
||||||
|
|
||||||
!!! note
|
The CA server to use.
|
||||||
Wildcard certificates can be generated only if `acme.dnsChallenge`
|
|
||||||
option is enable.
|
|
||||||
|
|
||||||
### Let's Encrypt downtime
|
This example shows the usage of Let's Encrypt's staging server:
|
||||||
|
|
||||||
Let's Encrypt functionality will be limited until Træfik is restarted.
|
|
||||||
|
|
||||||
If Let's Encrypt is not reachable, these certificates will be used :
|
|
||||||
|
|
||||||
- ACME certificates already generated before downtime
|
|
||||||
- Expired ACME certificates
|
|
||||||
- Provided certificates
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
Default Træfik certificate will be used instead of ACME certificates for new (sub)domains (which need Let's Encrypt challenge).
|
|
||||||
|
|
||||||
### `storage`
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
[acme]
|
||||||
# ...
|
# ...
|
||||||
storage = "acme.json"
|
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
The `storage` option sets where are stored your ACME certificates.
|
### `dnsChallenge`
|
||||||
|
|
||||||
There are two kind of `storage` :
|
Use the `DNS-01` challenge to generate and renew ACME certificates by provisioning a DNS record.
|
||||||
|
|
||||||
- a JSON file,
|
```toml
|
||||||
- a KV store entry.
|
[acme]
|
||||||
|
# ...
|
||||||
|
[acme.dnsChallenge]
|
||||||
|
provider = "digitalocean"
|
||||||
|
delayBeforeCheck = 0
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
!!! danger "DEPRECATED"
|
#### `delayBeforeCheck`
|
||||||
`storage` replaces `storageFile` which is deprecated.
|
|
||||||
|
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
||||||
|
If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds.
|
||||||
|
|
||||||
|
Useful if internal networks block external DNS queries.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
During Træfik configuration migration from a configuration file to a KV store (thanks to `storeconfig` subcommand as described [here](/user-guide/kv-config/#store-configuration-in-key-value-store)), if ACME certificates have to be migrated too, use both `storageFile` and `storage`.
|
A `provider` is mandatory.
|
||||||
|
|
||||||
- `storageFile` will contain the path to the `acme.json` file to migrate.
|
#### `provider`
|
||||||
- `storage` will contain the key where the certificates will be stored.
|
|
||||||
|
|
||||||
#### Store data in a file
|
Here is a list of supported `provider`s, that can automate the DNS verification, along with the required environment variables and their [wildcard & root domain support](/configuration/acme/#wildcard-domains) for each. Do not hesitate to complete it.
|
||||||
|
|
||||||
ACME certificates can be stored in a JSON file which with the `600` right mode.
|
| Provider Name | Provider Code | Environment Variables | Wildcard & Root Domain Support |
|
||||||
|
|--------------------------------------------------------|----------------|-----------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||||
|
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` | Not tested yet |
|
||||||
|
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` | Not tested yet |
|
||||||
|
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` | Not tested yet |
|
||||||
|
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The `Global API Key` needs to be used, not the `Origin CA Key` | YES |
|
||||||
|
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` | Not tested yet |
|
||||||
|
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` | YES |
|
||||||
|
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` | Not tested yet |
|
||||||
|
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` | Not tested yet |
|
||||||
|
| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` | Not tested yet |
|
||||||
|
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` | Not tested yet |
|
||||||
|
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | Not tested yet |
|
||||||
|
| External Program | `exec` | `EXEC_PATH` | Not tested yet |
|
||||||
|
| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | Not tested yet |
|
||||||
|
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | Not tested yet |
|
||||||
|
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | Not tested yet |
|
||||||
|
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | Not tested yet |
|
||||||
|
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | Not tested yet |
|
||||||
|
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` | Not tested yet |
|
||||||
|
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` | YES |
|
||||||
|
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | Not tested yet |
|
||||||
|
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | Not tested yet |
|
||||||
|
| manual | - | none, but you need to run Træfik interactively, turn on `acmeLogging` to see instructions and press <kbd>Enter</kbd>. | YES |
|
||||||
|
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` | Not tested yet |
|
||||||
|
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` | Not tested yet |
|
||||||
|
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | Not tested yet |
|
||||||
|
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | Not tested yet |
|
||||||
|
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | YES |
|
||||||
|
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` | Not tested yet |
|
||||||
|
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` | Not tested yet |
|
||||||
|
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet |
|
||||||
|
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or a configured user/instance IAM profile. | YES |
|
||||||
|
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
|
||||||
|
|
||||||
There are two ways to store ACME certificates in a file from Docker:
|
### `domains`
|
||||||
|
|
||||||
|
You can provide SANs (alternative domains) to each main domain.
|
||||||
|
All domains must have A/AAAA records pointing to Træfik.
|
||||||
|
Each domain & SAN will lead to a certificate request.
|
||||||
|
|
||||||
- create a file on your host and mount it as a volume:
|
|
||||||
```toml
|
```toml
|
||||||
storage = "acme.json"
|
[acme]
|
||||||
```
|
# ...
|
||||||
```bash
|
[[acme.domains]]
|
||||||
docker run -v "/my/host/acme.json:acme.json" traefik
|
main = "local1.com"
|
||||||
```
|
sans = ["test1.local1.com", "test2.local1.com"]
|
||||||
- mount the folder containing the file as a volume
|
[[acme.domains]]
|
||||||
```toml
|
main = "local2.com"
|
||||||
storage = "/etc/traefik/acme/acme.json"
|
[[acme.domains]]
|
||||||
```
|
main = "*.local3.com"
|
||||||
```bash
|
sans = ["local3.com", "test1.test1.local3.com"]
|
||||||
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
This file cannot be shared per many instances of Træfik at the same time.
|
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||||
If you have to use Træfik cluster mode, please use [a KV Store entry](/configuration/acme/#storage-kv-entry).
|
|
||||||
|
|
||||||
#### Store data in a KV store entry
|
|
||||||
|
|
||||||
ACME certificates can be stored in a KV Store entry.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
storage = "traefik/acme/account"
|
|
||||||
```
|
|
||||||
|
|
||||||
**This kind of storage is mandatory in cluster mode.**
|
|
||||||
|
|
||||||
Because KV stores (like Consul) have limited entries size, the certificates list is compressed before to be set in a KV store entry.
|
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
It's possible to store up to approximately 100 ACME certificates in Consul.
|
Wildcard certificates can only be verified through a `DNS-01` challenge.
|
||||||
|
|
||||||
|
#### Wildcard Domains
|
||||||
|
|
||||||
|
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support.
|
||||||
|
As described in [Let's Encrypt's post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605) wildcard certificates can only be generated through a [`DNS-01` challenge](/configuration/acme/#dnschallenge).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[acme]
|
||||||
|
# ...
|
||||||
|
[[acme.domains]]
|
||||||
|
main = "*.local1.com"
|
||||||
|
sans = ["local1.com"]
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
It is not possible to request a double wildcard certificate for a domain (for example `*.*.local.com`).
|
||||||
|
Due to ACME limitation it is not possible to define wildcards in SANs (alternative domains). Thus, the wildcard domain has to be defined as a main domain.
|
||||||
|
Most likely the root domain should receive a certificate too, so it needs to be specified as SAN and 2 `DNS-01` challenges are executed.
|
||||||
|
In this case the generated DNS TXT record for both domains is the same.
|
||||||
|
Eventhough this behaviour is [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) compliant, it can lead to problems as all DNS providers keep DNS records cached for a certain time (TTL) and this TTL can be superior to the challenge timeout making the `DNS-01` challenge fail.
|
||||||
|
The Træfik ACME client library [LEGO](https://github.com/xenolf/lego) supports some but not all DNS providers to work around this issue.
|
||||||
|
The [`provider` table](/configuration/acme/#provider) indicates if they allow generating certificates for a wildcard domain and its root domain.
|
||||||
|
|
||||||
### `httpChallenge`
|
### `httpChallenge`
|
||||||
|
|
||||||
Use `HTTP-01` challenge to generate/renew ACME certificates.
|
Use the `HTTP-01` challenge to generate and renew ACME certificates by provisioning a HTTP resource under a well-known URI.
|
||||||
|
|
||||||
The redirection is fully compatible with the HTTP-01 challenge.
|
Redirection is fully compatible with the `HTTP-01` challenge.
|
||||||
You can use redirection with HTTP-01 challenge without problem.
|
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
[acme]
|
||||||
|
@ -252,6 +286,10 @@ entryPoint = "https"
|
||||||
entryPoint = "http"
|
entryPoint = "http"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
If the `HTTP-01` challenge is used, `acme.httpChallenge.entryPoint` has to be defined and reachable by Let's Encrypt through port 80.
|
||||||
|
This is a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||||
|
|
||||||
#### `entryPoint`
|
#### `entryPoint`
|
||||||
|
|
||||||
Specify the entryPoint to use during the challenges.
|
Specify the entryPoint to use during the challenges.
|
||||||
|
@ -275,73 +313,7 @@ defaultEntryPoints = ["http", "https"]
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
`acme.httpChallenge.entryPoint` has to be reachable by Let's Encrypt through the port 80.
|
`acme.httpChallenge.entryPoint` has to be reachable through port 80. It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
||||||
It's a Let's Encrypt limitation as described on the [community forum](https://community.letsencrypt.org/t/support-for-ports-other-than-80-and-443/3419/72).
|
|
||||||
|
|
||||||
### `dnsChallenge`
|
|
||||||
|
|
||||||
Use `DNS-01/DNS-01` challenge to generate/renew ACME certificates.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[acme]
|
|
||||||
# ...
|
|
||||||
[acme.dnsChallenge]
|
|
||||||
provider = "digitalocean"
|
|
||||||
delayBeforeCheck = 0
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
ACME wildcard certificates can only be generated thanks to a `DNS-01` challenge.
|
|
||||||
|
|
||||||
#### `provider`
|
|
||||||
|
|
||||||
Select the provider that matches the DNS domain that will host the challenge TXT record, and provide environment variables to enable setting it:
|
|
||||||
|
|
||||||
| Provider Name | Provider code | Configuration |
|
|
||||||
|--------------------------------------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | `AURORA_USER_ID`, `AURORA_KEY`, `AURORA_ENDPOINT` |
|
|
||||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `AZURE_TENANT_ID`, `AZURE_RESOURCE_GROUP` |
|
|
||||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | `BLUECAT_SERVER_URL`, `BLUECAT_USER_NAME`, `BLUECAT_PASSWORD`, `BLUECAT_CONFIG_NAME`, `BLUECAT_DNS_VIEW` |
|
|
||||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | `CLOUDFLARE_EMAIL`, `CLOUDFLARE_API_KEY` - The Cloudflare `Global API Key` needs to be used and not the `Origin CA Key` |
|
|
||||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | `CLOUDXNS_API_KEY`, `CLOUDXNS_SECRET_KEY` |
|
|
||||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | `DO_AUTH_TOKEN` |
|
|
||||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | `DNSIMPLE_OAUTH_TOKEN`, `DNSIMPLE_BASE_URL` |
|
|
||||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | `DNSMADEEASY_API_KEY`, `DNSMADEEASY_API_SECRET`, `DNSMADEEASY_SANDBOX` |
|
|
||||||
| [DNSPod](http://www.dnspod.net/) | `dnspod` | `DNSPOD_API_KEY` |
|
|
||||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | `DUCKDNS_TOKEN` |
|
|
||||||
| [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` |
|
|
||||||
| External Program | `exec` | `EXEC_PATH` |
|
|
||||||
| [Exoscale](https://www.exoscale.ch) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` |
|
|
||||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` |
|
|
||||||
| [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` |
|
|
||||||
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` |
|
|
||||||
| [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` |
|
|
||||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | `GODADDY_API_KEY`, `GODADDY_API_SECRET` |
|
|
||||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | `GCE_PROJECT`, `GCE_SERVICE_ACCOUNT_FILE` |
|
|
||||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` |
|
|
||||||
| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` |
|
|
||||||
| manual | - | none, but run Træfik interactively & turn on `acmeLogging` to see instructions & press <kbd>Enter</kbd>. |
|
|
||||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY` |
|
|
||||||
| [name.com](https://www.name.com/) | `namedotcom` | `NAMECOM_USERNAME`, `NAMECOM_API_TOKEN`, `NAMECOM_SERVER` |
|
|
||||||
| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` |
|
|
||||||
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` |
|
|
||||||
| [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` |
|
|
||||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | `PDNS_API_KEY`, `PDNS_API_URL` |
|
|
||||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | `RACKSPACE_USER`, `RACKSPACE_API_KEY` |
|
|
||||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` |
|
|
||||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_HOSTED_ZONE_ID` or configured user/instance IAM profile. |
|
|
||||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` |
|
|
||||||
|
|
||||||
#### `delayBeforeCheck`
|
|
||||||
|
|
||||||
By default, the `provider` will verify the TXT DNS challenge record before letting ACME verify.
|
|
||||||
If `delayBeforeCheck` is greater than zero, avoid this & instead just wait so many seconds.
|
|
||||||
|
|
||||||
Useful if internal networks block external DNS queries.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
This field has no sense if a `provider` is not defined.
|
|
||||||
|
|
||||||
### `onDemand` (Deprecated)
|
### `onDemand` (Deprecated)
|
||||||
|
|
||||||
|
@ -355,15 +327,15 @@ onDemand = true
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable on demand certificate.
|
Enable on demand certificate generation.
|
||||||
|
|
||||||
This will request a certificate from Let's Encrypt during the first TLS handshake for a host name that does not yet have a certificate.
|
This will request certificates from Let's Encrypt during the first TLS handshake for host names that do not yet have certificates.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
TLS handshakes will be slow when requesting a host name certificate for the first time, this can lead to DoS attacks.
|
TLS handshakes are slow when requesting a host name certificate for the first time. This can lead to DoS attacks!
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
Take note that Let's Encrypt applies [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||||
|
|
||||||
### `onHostRule`
|
### `onHostRule`
|
||||||
|
|
||||||
|
@ -374,199 +346,94 @@ onHostRule = true
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable certificate generation on frontends `Host` rules (for frontends wired on the `acme.entryPoint`).
|
Enable certificate generation on frontend `Host` rules (for frontends wired to the `acme.entryPoint`).
|
||||||
|
|
||||||
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
This will request a certificate from Let's Encrypt for each frontend with a Host rule.
|
||||||
|
|
||||||
For example, a rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
For example, the rule `Host:test1.traefik.io,test2.traefik.io` will request a certificate with main domain `test1.traefik.io` and SAN `test2.traefik.io`.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
`onHostRule` option can not be used to generate wildcard certificates.
|
`onHostRule` option can not be used to generate wildcard certificates.
|
||||||
Refer to [the wildcard generation section](/configuration/acme/#wildcard-domain) for more information.
|
Refer to [wildcard generation](/configuration/acme/#wildcard-domain) for further information.
|
||||||
|
|
||||||
### `caServer`
|
### `storage`
|
||||||
|
|
||||||
|
The `storage` option sets the location where your ACME certificates are saved to.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
[acme]
|
||||||
# ...
|
# ...
|
||||||
caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
storage = "acme.json"
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
CA server to use.
|
The value can refer to two kinds of storage:
|
||||||
|
|
||||||
- Uncomment the line to run on the staging Let's Encrypt server.
|
- a JSON file
|
||||||
- Leave comment to go to prod.
|
- a KV store entry
|
||||||
|
|
||||||
### `domains`
|
!!! danger "DEPRECATED"
|
||||||
|
`storage` replaces `storageFile` which is deprecated.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
During migration to a KV store use both `storageFile` and `storage` to migrate ACME certificates too. See [`storeconfig` subcommand](/user-guide/kv-config/#store-configuration-in-key-value-store) for further information.
|
||||||
|
|
||||||
|
#### As a File
|
||||||
|
|
||||||
|
ACME certificates can be stored in a JSON file that needs to have file mode `600`.
|
||||||
|
|
||||||
|
In Docker you can either mount the JSON file or the folder containing it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -v "/my/host/acme.json:acme.json" traefik
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
This file cannot be shared across multiple instances of Træfik at the same time. Please use a [KV Store entry](/configuration/acme/#as-a-key-value-store-entry) instead.
|
||||||
|
|
||||||
|
#### As a Key Value Store Entry
|
||||||
|
|
||||||
|
ACME certificates can be stored in a KV Store entry. This kind of storage is **mandatory in cluster mode**.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[acme]
|
storage = "traefik/acme/account"
|
||||||
# ...
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "local1.com"
|
|
||||||
sans = ["test1.local1.com", "test2.local1.com"]
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "local2.com"
|
|
||||||
sans = ["test1.local2.com", "test2.local2.com"]
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "local3.com"
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "*.local4.com"
|
|
||||||
sans = ["local4.com", "test1.test1.local4.com"]
|
|
||||||
# ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Wildcard domains
|
Because KV stores (like Consul) have limited entry size the certificates list is compressed before it is saved as KV store entry.
|
||||||
|
|
||||||
Wildcard domain has to be defined as a main domain.
|
!!! note
|
||||||
All domains must have A/AAAA records pointing to Træfik.
|
It is possible to store up to approximately 100 ACME certificates in Consul.
|
||||||
|
|
||||||
Due to ACME limitation, it's not possible to define a wildcard as a SAN (alternative domains).
|
#### ACME v2 Migration
|
||||||
It's neither possible to define a wildcard on a wildcard domain (for example `*.*.local.com`).
|
|
||||||
|
|
||||||
!!! warning
|
During migration from ACME v1 to ACME v2, using a storage file, a backup of the original file is created in the same place as the latter (with a `.bak` extension).
|
||||||
Note that Let's Encrypt has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
|
||||||
|
|
||||||
Each domain & SANs will lead to a certificate request.
|
For example: if `acme.storage`'s value is `/etc/traefik/acme/acme.json`, the backup file will be `/etc/traefik/acme/acme.json.bak`.
|
||||||
|
|
||||||
#### Others domains
|
!!! note
|
||||||
|
When Træfik is launched in a container, the storage file's parent directory needs to be mounted to be able to access the backup file on the host.
|
||||||
You can provide SANs (alternative domains) to each main domain.
|
Otherwise the backup file will be deleted when the container is stopped. Træfik will only generate it once!
|
||||||
All domains must have A/AAAA records pointing to Træfik.
|
|
||||||
|
|
||||||
!!! warning
|
|
||||||
Take note that Let's Encrypt have [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
|
||||||
|
|
||||||
Each domain & SANs will lead to a certificate request.
|
|
||||||
|
|
||||||
### `dnsProvider` (Deprecated)
|
### `dnsProvider` (Deprecated)
|
||||||
|
|
||||||
!!! danger "DEPRECATED"
|
!!! danger "DEPRECATED"
|
||||||
This option is deprecated, use [dnsChallenge.provider](/configuration/acme/#dnschallenge) instead.
|
This option is deprecated. Please use [dnsChallenge.provider](/configuration/acme/#provider) instead.
|
||||||
|
|
||||||
### `delayDontCheckDNS` (Deprecated)
|
### `delayDontCheckDNS` (Deprecated)
|
||||||
|
|
||||||
!!! danger "DEPRECATED"
|
!!! danger "DEPRECATED"
|
||||||
This option is deprecated, use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead.
|
This option is deprecated. Please use [dnsChallenge.delayBeforeCheck](/configuration/acme/#dnschallenge) instead.
|
||||||
|
|
||||||
## Wildcard certificates
|
## Fallbacks
|
||||||
|
|
||||||
[ACME V2](https://community.letsencrypt.org/t/acme-v2-and-wildcard-certificate-support-is-live/55579) allows wildcard certificate support.
|
If Let's Encrypt is not reachable, these certificates will be used:
|
||||||
However, this feature needs a specific configuration.
|
|
||||||
|
|
||||||
### DNS-01 Challenge
|
1. ACME certificates already generated before downtime
|
||||||
|
1. Expired ACME certificates
|
||||||
As described in [Let's Encrypt post](https://community.letsencrypt.org/t/staging-endpoint-for-acme-v2/49605), wildcard certificates can only be generated through a `DNS-01` Challenge.
|
1. Provided certificates
|
||||||
This challenge is linked to the Træfik option `acme.dnsChallenge`.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[acme]
|
|
||||||
# ...
|
|
||||||
[acme.dnsChallenge]
|
|
||||||
provider = "digitalocean"
|
|
||||||
delayBeforeCheck = 0
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information about this option, please refer to the [dnsChallenge section](/configuration/acme/#dnschallenge).
|
|
||||||
|
|
||||||
### Wildcard domain
|
|
||||||
|
|
||||||
Wildcard domains can currently be provided only by to the `acme.domains` option.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[acme]
|
|
||||||
# ...
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "*.local1.com"
|
|
||||||
sans = ["local1.com"]
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "*.local2.com"
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information about this option, please refer to the [domains section](/configuration/acme/#domains).
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
|
|
||||||
Let's Encrypt wildcard support have some limitations to take into account :
|
|
||||||
|
|
||||||
- Wildcard domain can not be a SAN (alternative domain),
|
|
||||||
- Wildcard domain on a wildcard domain is forbidden (for example `*.*.local.com`),
|
|
||||||
- A DNS-01 Challenge is executed for each domain (CN and SANs), DNS provider can not manage correctly this behavior as explained in the [DNS provider support section](/configuration/acme/#dns-provider-support)
|
|
||||||
|
|
||||||
|
|
||||||
### DNS provider support
|
|
||||||
|
|
||||||
All DNS providers allow creating ACME wildcard certificates.
|
|
||||||
However, many troubles can appear for wildcard domains with SANs.
|
|
||||||
|
|
||||||
If a wildcard domain is defined with it root domain as SAN, as described below, 2 DNS-01 Challenges will be executed.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[acme]
|
|
||||||
# ...
|
|
||||||
[[acme.domains]]
|
|
||||||
main = "*.local1.com"
|
|
||||||
sans = ["local1.com"]
|
|
||||||
# ...
|
|
||||||
```
|
|
||||||
|
|
||||||
When a DNS-01 Challenge is done, Let's Encrypt checks if a TXT record is created with a given name and a given value.
|
|
||||||
When a certificate is generated for a wildcard domain is defined with it root domain as SAN, the requested TXT record name for both the wildcard domain and the root domain is the same.
|
|
||||||
|
|
||||||
The [DNS RFC](https://community.letsencrypt.org/t/wildcard-issuance-two-txt-records-for-the-same-name/54528/2) allows this behavior.
|
|
||||||
But all DNS providers keep TXT records values in a cache with a TTL.
|
|
||||||
In function of the parameters given by the Træfik ACME client library ([LEGO](https://github.com/xenolf/lego)), the TXT record TTL can be superior to challenge Timeout.
|
|
||||||
In that event, the DNS-01 Challenge will not work correctly.
|
|
||||||
|
|
||||||
[LEGO](https://github.com/xenolf/lego) will involve in the way to be adapted to all of DNS providers.
|
|
||||||
Meanwhile, the table described below contains all the DNS providers supported by Træfik and indicates if they allow generating certificates for a wildcard domain and its root domain.
|
|
||||||
Do not hesitate to complete it.
|
|
||||||
|
|
||||||
| Provider Name | Provider code | Wildcard and Root Domain Support |
|
|
||||||
|--------------------------------------------------------|----------------|----------------------------------|
|
|
||||||
| [Auroradns](https://www.pcextreme.com/aurora/dns) | `auroradns` | Not tested yet |
|
|
||||||
| [Azure](https://azure.microsoft.com/services/dns/) | `azure` | Not tested yet |
|
|
||||||
| [Blue Cat](https://www.bluecatnetworks.com/) | `bluecat` | Not tested yet |
|
|
||||||
| [Cloudflare](https://www.cloudflare.com) | `cloudflare` | YES |
|
|
||||||
| [CloudXNS](https://www.cloudxns.net) | `cloudxns` | Not tested yet |
|
|
||||||
| [DigitalOcean](https://www.digitalocean.com) | `digitalocean` | YES |
|
|
||||||
| [DNSimple](https://dnsimple.com) | `dnsimple` | Not tested yet |
|
|
||||||
| [DNS Made Easy](https://dnsmadeeasy.com) | `dnsmadeeasy` | Not tested yet |
|
|
||||||
| [DNSPod](http://www.dnspod.net/) | `dnspod` | Not tested yet |
|
|
||||||
| [Duck DNS](https://www.duckdns.org/) | `duckdns` | Not tested yet |
|
|
||||||
| [Dyn](https://dyn.com) | `dyn` | Not tested yet |
|
|
||||||
| External Program | `exec` | Not tested yet |
|
|
||||||
| [Exoscale](https://www.exoscale.ch) | `exoscale` | Not tested yet |
|
|
||||||
| [Fast DNS](https://www.akamai.com/) | `fastdns` | Not tested yet |
|
|
||||||
| [Gandi](https://www.gandi.net) | `gandi` | Not tested yet |
|
|
||||||
| [Gandi V5](http://doc.livedns.gandi.net) | `gandiv5` | Not tested yet |
|
|
||||||
| [Glesys](https://glesys.com/) | `glesys` | Not tested yet |
|
|
||||||
| [GoDaddy](https://godaddy.com/domains) | `godaddy` | Not tested yet |
|
|
||||||
| [Google Cloud DNS](https://cloud.google.com/dns/docs/) | `gcloud` | YES |
|
|
||||||
| [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | Not tested yet |
|
|
||||||
| [Linode](https://www.linode.com) | `linode` | Not tested yet |
|
|
||||||
| manual | - | YES |
|
|
||||||
| [Namecheap](https://www.namecheap.com) | `namecheap` | Not tested yet |
|
|
||||||
| [name.com](https://www.name.com/) | `namedotcom` | Not tested yet |
|
|
||||||
| [Ns1](https://ns1.com/) | `ns1` | Not tested yet |
|
|
||||||
| [Open Telekom Cloud](https://cloud.telekom.de/en/) | `otc` | Not tested yet |
|
|
||||||
| [OVH](https://www.ovh.com) | `ovh` | YES |
|
|
||||||
| [PowerDNS](https://www.powerdns.com) | `pdns` | Not tested yet |
|
|
||||||
| [Rackspace](https://www.rackspace.com/cloud/dns) | `rackspace` | Not tested yet |
|
|
||||||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | Not tested yet |
|
|
||||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | YES |
|
|
||||||
| [VULTR](https://www.vultr.com) | `vultr` | Not tested yet |
|
|
||||||
|
|
||||||
## ACME V2 migration
|
|
||||||
|
|
||||||
During migration from ACME V1 to ACME V2 with a storage file, a backup is created with the content of the ACME V1 file.
|
|
||||||
To obtain the name of the backup file, Træfik concatenates the option `acme.storage` and the suffix `.bak`.
|
|
||||||
|
|
||||||
For example : if `acme.storage` value is `/etc/traefik/acme/acme.json`, the backup file will be named `/etc/traefik/acme/acme.json.bak`.
|
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
When Træfik is launched in a container, do not forget to create a volume of the parent folder to get the backup file on the host.
|
For new (sub)domains which need Let's Encrypt authentification, the default Træfik certificate will be used until Træfik is restarted.
|
||||||
Otherwise, the backup file will be deleted when the container will be stopped and Træfik will not generate it again.
|
|
||||||
|
|
|
@ -81,9 +81,11 @@ For namespaced restrictions, one RoleBinding is required per watched namespace a
|
||||||
It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object,
|
It is possible to use Træfik with a [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or a [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) object,
|
||||||
whereas both options have their own pros and cons:
|
whereas both options have their own pros and cons:
|
||||||
|
|
||||||
- The scalability is much better when using a Deployment, because you will have a Single-Pod-per-Node model when using the DaemonSet.
|
- The scalability can be much better when using a Deployment, because you will have a Single-Pod-per-Node model when using a DaemonSet, whereas you may need less replicas based on your environment when using a Deployment.
|
||||||
- It is possible to exclusively run a Service on a dedicated set of machines using taints and tolerations with a DaemonSet.
|
- DaemonSets automatically scale to new nodes, when the nodes join the cluster, whereas Deployment pods are only scheduled on new nodes if required.
|
||||||
- On the other hand the DaemonSet allows you to access any Node directly on Port 80 and 443, where you have to setup a [Service](https://kubernetes.io/docs/concepts/services-networking/service/) object with a Deployment.
|
- DaemonSets ensure that only one replica of pods run on any single node. Deployments require affinity settings if you want to ensure that two pods don't end up on the same node.
|
||||||
|
- DaemonSets can be run with the `NET_BIND_SERVICE` capability, which will allow it to bind to port 80/443/etc on each host. This will allow bypassing the kube-proxy, and reduce traffic hops. Note that this is against the Kubernetes Best Practices [Guidelines](https://kubernetes.io/docs/concepts/configuration/overview/#services), and raises the potential for scheduling/scaling issues. Despite potential issues, this remains the choice for most ingress controllers.
|
||||||
|
- If you are unsure which to choose, start with the Daemonset.
|
||||||
|
|
||||||
The Deployment objects looks like this:
|
The Deployment objects looks like this:
|
||||||
|
|
||||||
|
@ -118,6 +120,11 @@ spec:
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
- name: admin
|
||||||
|
containerPort: 8080
|
||||||
args:
|
args:
|
||||||
- --api
|
- --api
|
||||||
- --kubernetes
|
- --kubernetes
|
||||||
|
@ -172,7 +179,6 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: traefik-ingress-controller
|
serviceAccountName: traefik-ingress-controller
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
hostNetwork: true
|
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
|
@ -208,11 +214,13 @@ spec:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8080
|
port: 8080
|
||||||
name: admin
|
name: admin
|
||||||
type: NodePort
|
|
||||||
```
|
```
|
||||||
|
|
||||||
[examples/k8s/traefik-ds.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-ds.yaml)
|
[examples/k8s/traefik-ds.yaml](https://github.com/containous/traefik/tree/master/examples/k8s/traefik-ds.yaml)
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This will create a Daemonset that uses privileged ports 80/8080 on the host. This may not work on all providers, but illustrates the static (non-NodePort) hostPort binding. The `traefik-ingress-service` can still be used inside the cluster to access the DaemonSet pods.
|
||||||
|
|
||||||
To deploy Træfik to your cluster start by submitting one of the YAML files to the cluster with `kubectl`:
|
To deploy Træfik to your cluster start by submitting one of the YAML files to the cluster with `kubectl`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -293,7 +301,21 @@ Install the Træfik chart by:
|
||||||
```shell
|
```shell
|
||||||
helm install stable/traefik
|
helm install stable/traefik
|
||||||
```
|
```
|
||||||
|
Install the Træfik chart using a values.yaml file.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
helm install --values values.yaml stable/traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dashboard:
|
||||||
|
enabled: true
|
||||||
|
domain: traefik-ui.minikube
|
||||||
|
kubernetes:
|
||||||
|
namespaces:
|
||||||
|
- default
|
||||||
|
- kube-system
|
||||||
|
```
|
||||||
For more information, check out [the documentation](https://github.com/kubernetes/charts/tree/master/stable/traefik).
|
For more information, check out [the documentation](https://github.com/kubernetes/charts/tree/master/stable/traefik).
|
||||||
|
|
||||||
## Submitting an Ingress to the Cluster
|
## Submitting an Ingress to the Cluster
|
||||||
|
|
|
@ -28,6 +28,11 @@ spec:
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
- name: admin
|
||||||
|
containerPort: 8080
|
||||||
args:
|
args:
|
||||||
- --api
|
- --api
|
||||||
- --kubernetes
|
- --kubernetes
|
||||||
|
|
|
@ -21,7 +21,6 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: traefik-ingress-controller
|
serviceAccountName: traefik-ingress-controller
|
||||||
terminationGracePeriodSeconds: 60
|
terminationGracePeriodSeconds: 60
|
||||||
hostNetwork: true
|
|
||||||
containers:
|
containers:
|
||||||
- image: traefik
|
- image: traefik
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
|
@ -31,6 +30,7 @@ spec:
|
||||||
hostPort: 80
|
hostPort: 80
|
||||||
- name: admin
|
- name: admin
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
|
hostport: 8080
|
||||||
securityContext:
|
securityContext:
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
|
@ -57,4 +57,3 @@ spec:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 8080
|
port: 8080
|
||||||
name: admin
|
name: admin
|
||||||
type: NodePort
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ type accessLogValue struct {
|
||||||
code string
|
code string
|
||||||
user string
|
user string
|
||||||
frontendName string
|
frontendName string
|
||||||
backendName string
|
backendURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccessLogSuite) SetUpSuite(c *check.C) {
|
func (s *AccessLogSuite) SetUpSuite(c *check.C) {
|
||||||
|
@ -103,7 +103,7 @@ func (s *AccessLogSuite) TestAccessLogAuthFrontend(c *check.C) {
|
||||||
code: "401",
|
code: "401",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "Auth for frontend-Host-frontend-auth-docker-local",
|
frontendName: "Auth for frontend-Host-frontend-auth-docker-local",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypoint(c *check.C) {
|
||||||
code: "401",
|
code: "401",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "Auth for entrypoint",
|
frontendName: "Auth for entrypoint",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ func (s *AccessLogSuite) TestAccessLogAuthEntrypointSuccess(c *check.C) {
|
||||||
code: "200",
|
code: "200",
|
||||||
user: "test",
|
user: "test",
|
||||||
frontendName: "Host-entrypoint-auth-docker",
|
frontendName: "Host-entrypoint-auth-docker",
|
||||||
backendName: "http://172.17.0",
|
backendURL: "http://172.17.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,14 +247,14 @@ func (s *AccessLogSuite) TestAccessLogDigestAuthEntrypoint(c *check.C) {
|
||||||
code: "401",
|
code: "401",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "Auth for entrypoint",
|
frontendName: "Auth for entrypoint",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatOnly: false,
|
formatOnly: false,
|
||||||
code: "200",
|
code: "200",
|
||||||
user: "test",
|
user: "test",
|
||||||
frontendName: "Host-entrypoint-digest-auth-docker",
|
frontendName: "Host-entrypoint-digest-auth-docker",
|
||||||
backendName: "http://172.17.0",
|
backendURL: "http://172.17.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,7 +355,7 @@ func (s *AccessLogSuite) TestAccessLogEntrypointRedirect(c *check.C) {
|
||||||
code: "302",
|
code: "302",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "entrypoint redirect for frontend-",
|
frontendName: "entrypoint redirect for frontend-",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatOnly: true,
|
formatOnly: true,
|
||||||
|
@ -405,7 +405,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendRedirect(c *check.C) {
|
||||||
code: "302",
|
code: "302",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "frontend redirect for frontend-Path-",
|
frontendName: "frontend redirect for frontend-Path-",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
formatOnly: true,
|
formatOnly: true,
|
||||||
|
@ -461,7 +461,7 @@ func (s *AccessLogSuite) TestAccessLogRateLimit(c *check.C) {
|
||||||
code: "429",
|
code: "429",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "rate limit for frontend-Host-ratelimit",
|
frontendName: "rate limit for frontend-Host-ratelimit",
|
||||||
backendName: "/",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,7 +512,7 @@ func (s *AccessLogSuite) TestAccessLogBackendNotFound(c *check.C) {
|
||||||
code: "404",
|
code: "404",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "backend not found",
|
frontendName: "backend not found",
|
||||||
backendName: "/",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +557,7 @@ func (s *AccessLogSuite) TestAccessLogEntrypointWhitelist(c *check.C) {
|
||||||
code: "403",
|
code: "403",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "ipwhitelister for entrypoint httpWhitelistReject",
|
frontendName: "ipwhitelister for entrypoint httpWhitelistReject",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,7 +604,7 @@ func (s *AccessLogSuite) TestAccessLogFrontendWhitelist(c *check.C) {
|
||||||
code: "403",
|
code: "403",
|
||||||
user: "-",
|
user: "-",
|
||||||
frontendName: "ipwhitelister for frontend-Host-frontend-whitelist",
|
frontendName: "ipwhitelister for frontend-Host-frontend-whitelist",
|
||||||
backendName: "-",
|
backendURL: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -734,7 +734,7 @@ func checkAccessLogExactValues(c *check.C, line string, i int, v accessLogValue)
|
||||||
c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code)
|
c.Assert(results[accesslog.OriginStatus], checker.Equals, v.code)
|
||||||
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
|
c.Assert(results[accesslog.RequestCount], checker.Equals, fmt.Sprintf("%d", i+1))
|
||||||
c.Assert(results[accesslog.FrontendName], checker.Matches, `^"?`+v.frontendName+`.*$`)
|
c.Assert(results[accesslog.FrontendName], checker.Matches, `^"?`+v.frontendName+`.*$`)
|
||||||
c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendName+`.*$`)
|
c.Assert(results[accesslog.BackendURL], checker.Matches, `^"?`+v.backendURL+`.*$`)
|
||||||
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
|
c.Assert(results[accesslog.Duration], checker.Matches, `^\d+ms$`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
@ -41,7 +42,7 @@ func (s *ConsulCatalogSuite) waitToElectConsulLeader() error {
|
||||||
leader, err := s.consulClient.Status().Leader()
|
leader, err := s.consulClient.Status().Leader()
|
||||||
|
|
||||||
if err != nil || len(leader) == 0 {
|
if err != nil || len(leader) == 0 {
|
||||||
return fmt.Errorf("Leader not found. %v", err)
|
return fmt.Errorf("leader not found. %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -55,9 +56,6 @@ func (s *ConsulCatalogSuite) createConsulClient(config *api.Config, c *check.C)
|
||||||
s.consulClient = consulClient
|
s.consulClient = consulClient
|
||||||
return consulClient
|
return consulClient
|
||||||
}
|
}
|
||||||
func (s *ConsulCatalogSuite) startConsulService(c *check.C) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error {
|
func (s *ConsulCatalogSuite) registerService(name string, address string, port int, tags []string) error {
|
||||||
catalog := s.consulClient.Catalog()
|
catalog := s.consulClient.Catalog()
|
||||||
|
@ -78,28 +76,45 @@ func (s *ConsulCatalogSuite) registerService(name string, address string, port i
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string) error {
|
func (s *ConsulCatalogSuite) registerAgentService(name string, address string, port int, tags []string, withHealthCheck bool) error {
|
||||||
agent := s.consulClient.Agent()
|
agent := s.consulClient.Agent()
|
||||||
err := agent.ServiceRegister(
|
var healthCheck *api.AgentServiceCheck
|
||||||
|
if withHealthCheck {
|
||||||
|
healthCheck = &api.AgentServiceCheck{
|
||||||
|
HTTP: "http://" + address,
|
||||||
|
Interval: "10s",
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
healthCheck = nil
|
||||||
|
}
|
||||||
|
return agent.ServiceRegister(
|
||||||
&api.AgentServiceRegistration{
|
&api.AgentServiceRegistration{
|
||||||
ID: address,
|
ID: address,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Name: name,
|
Name: name,
|
||||||
Address: address,
|
Address: address,
|
||||||
Port: port,
|
Port: port,
|
||||||
Check: &api.AgentServiceCheck{
|
Check: healthCheck,
|
||||||
HTTP: "http://" + address,
|
|
||||||
Interval: "10s",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return err
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) registerCheck(name string, address string, port int) error {
|
||||||
|
agent := s.consulClient.Agent()
|
||||||
|
checkRegistration := &api.AgentCheckRegistration{
|
||||||
|
ID: fmt.Sprintf("%s-%s", name, address),
|
||||||
|
Name: name,
|
||||||
|
ServiceID: address,
|
||||||
|
}
|
||||||
|
checkRegistration.HTTP = fmt.Sprintf("http://%s:%d/health", address, port)
|
||||||
|
checkRegistration.Interval = "2s"
|
||||||
|
checkRegistration.CheckID = address
|
||||||
|
return agent.CheckRegister(checkRegistration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) deregisterAgentService(address string) error {
|
func (s *ConsulCatalogSuite) deregisterAgentService(address string) error {
|
||||||
agent := s.consulClient.Agent()
|
agent := s.consulClient.Agent()
|
||||||
err := agent.ServiceDeregister(address)
|
return agent.ServiceDeregister(address)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
|
func (s *ConsulCatalogSuite) deregisterService(name string, address string) error {
|
||||||
|
@ -115,6 +130,22 @@ func (s *ConsulCatalogSuite) deregisterService(name string, address string) erro
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) consulEnableServiceMaintenance(name string) error {
|
||||||
|
return s.consulClient.Agent().EnableServiceMaintenance(name, fmt.Sprintf("Maintenance mode for service %s", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) consulDisableServiceMaintenance(name string) error {
|
||||||
|
return s.consulClient.Agent().DisableServiceMaintenance(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) consulEnableNodeMaintenance() error {
|
||||||
|
return s.consulClient.Agent().EnableNodeMaintenance("Maintenance mode for node")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) consulDisableNodeMaintenance() error {
|
||||||
|
return s.consulClient.Agent().DisableNodeMaintenance()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
func (s *ConsulCatalogSuite) TestSimpleConfiguration(c *check.C) {
|
||||||
cmd, display := s.traefikCmd(
|
cmd, display := s.traefikCmd(
|
||||||
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||||
|
@ -273,7 +304,7 @@ func (s *ConsulCatalogSuite) TestRefreshConfigWithMultipleNodeWithoutHealthCheck
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
defer s.deregisterService("test", whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"})
|
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}, true)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
||||||
defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress)
|
defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
@ -514,3 +545,132 @@ func (s *ConsulCatalogSuite) TestRetryWithConsulServer(c *check.C) {
|
||||||
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) TestServiceWithMultipleHealthCheck(c *check.C) {
|
||||||
|
//Scale consul to 0 to be able to start traefik before and test retry
|
||||||
|
s.composeProject.Scale(c, "consul", 0)
|
||||||
|
|
||||||
|
cmd, display := s.traefikCmd(
|
||||||
|
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||||
|
"--consulCatalog",
|
||||||
|
"--consulCatalog.watch=false",
|
||||||
|
"--consulCatalog.exposedByDefault=true",
|
||||||
|
"--consulCatalog.endpoint="+s.consulIP+":8500",
|
||||||
|
"--consulCatalog.domain=consul.localhost")
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// Wait for Traefik to turn ready.
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
|
||||||
|
// Request should fail
|
||||||
|
err = try.Request(req, 2*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Scale consul to 1
|
||||||
|
s.composeProject.Scale(c, "consul", 1)
|
||||||
|
s.waitToElectConsulLeader()
|
||||||
|
|
||||||
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
// Register service
|
||||||
|
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{"name=whoami1"}, true)
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering agent service"))
|
||||||
|
defer s.deregisterAgentService(whoami.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
// Register one healthcheck
|
||||||
|
err = s.registerCheck("test", whoami.NetworkSettings.IPAddress, 80)
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering check"))
|
||||||
|
|
||||||
|
// Provider consul catalog should be present
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 10*time.Second, try.BodyContains("consul_catalog"))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Should be ok
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Change health value of service to critical
|
||||||
|
reqHealth, err := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:80/health", whoami.NetworkSettings.IPAddress), bytes.NewBuffer([]byte("500")))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
reqHealth.Host = "test.consul.localhost"
|
||||||
|
|
||||||
|
err = try.Request(reqHealth, 10*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Should be a 404
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Change health value of service to passing
|
||||||
|
reqHealth, err = http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s:80/health", whoami.NetworkSettings.IPAddress), bytes.NewBuffer([]byte("200")))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
err = try.Request(reqHealth, 10*time.Second, try.StatusCodeIs(http.StatusOK))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Should be a 200
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConsulCatalogSuite) TestMaintenanceMode(c *check.C) {
|
||||||
|
cmd, display := s.traefikCmd(
|
||||||
|
withConfigFile("fixtures/consul_catalog/simple.toml"),
|
||||||
|
"--consulCatalog",
|
||||||
|
"--consulCatalog.endpoint="+s.consulIP+":8500",
|
||||||
|
"--consulCatalog.domain=consul.localhost")
|
||||||
|
defer display(c)
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
// Wait for Traefik to turn ready.
|
||||||
|
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
whoami := s.composeProject.Container(c, "whoami1")
|
||||||
|
|
||||||
|
err = s.registerAgentService("test", whoami.NetworkSettings.IPAddress, 80, []string{}, false)
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Enable service maintenance mode
|
||||||
|
err = s.consulEnableServiceMaintenance(whoami.NetworkSettings.IPAddress)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Disable service maintenance mode
|
||||||
|
err = s.consulDisableServiceMaintenance(whoami.NetworkSettings.IPAddress)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Enable node maintenance mode
|
||||||
|
err = s.consulEnableNodeMaintenance()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusNotFound), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
// Disable node maintenance mode
|
||||||
|
err = s.consulDisableNodeMaintenance()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
}
|
||||||
|
|
|
@ -36,25 +36,18 @@ const (
|
||||||
backendServerUpName = metricNamePrefix + "backend_server_up"
|
backendServerUpName = metricNamePrefix + "backend_server_up"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// generationAgeForever indicates that a metric never gets outdated.
|
|
||||||
generationAgeForever = 0
|
|
||||||
// generationAgeDefault is the default age of three generations.
|
|
||||||
generationAgeDefault = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// promState holds all metric state internally and acts as the only Collector we register for Prometheus.
|
// promState holds all metric state internally and acts as the only Collector we register for Prometheus.
|
||||||
//
|
//
|
||||||
// This enables control to remove metrics that belong to outdated configuration.
|
// This enables control to remove metrics that belong to outdated configuration.
|
||||||
// As an example why this is required, consider Traefik learns about a new service.
|
// As an example why this is required, consider Traefik learns about a new service.
|
||||||
// It populates the 'traefik_server_backend_up' metric for it with a value of 1 (alive).
|
// It populates the 'traefik_server_backend_up' metric for it with a value of 1 (alive).
|
||||||
// When the backend is undeployed now the metric is still there in the client library
|
// When the backend is undeployed now the metric is still there in the client library
|
||||||
// and will be until Traefik would be restarted.
|
// and will be returned on the metrics endpoint until Traefik would be restarted.
|
||||||
//
|
//
|
||||||
// To solve this problem promState keeps track of configuration generations.
|
// To solve this problem promState keeps track of Traefik's dynamic configuration.
|
||||||
// Every time a new configuration is loaded, the generation is increased by one.
|
// Metrics that "belong" to a dynamic configuration part like backends or entrypoints
|
||||||
// Metrics that "belong" to a dynamic configuration part of Traefik (e.g. backend, entrypoint)
|
// are removed after they were scraped at least once when the corresponding object
|
||||||
// are removed, given they were tracked more than 3 generations ago.
|
// doesn't exist anymore.
|
||||||
var promState = newPrometheusState()
|
var promState = newPrometheusState()
|
||||||
|
|
||||||
// PrometheusHandler exposes Prometheus routes.
|
// PrometheusHandler exposes Prometheus routes.
|
||||||
|
@ -163,40 +156,66 @@ func RegisterPrometheus(config *types.Prometheus) Registry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnConfigurationUpdate increases the current generation of the prometheus state.
|
// OnConfigurationUpdate receives the current configuration from Traefik.
|
||||||
func OnConfigurationUpdate() {
|
// It then converts the configuration to the optimized package internal format
|
||||||
promState.IncGeneration()
|
// and sets it to the promState.
|
||||||
|
func OnConfigurationUpdate(configurations types.Configurations) {
|
||||||
|
dynamicConfig := newDynamicConfig()
|
||||||
|
|
||||||
|
for _, config := range configurations {
|
||||||
|
for _, frontend := range config.Frontends {
|
||||||
|
for _, entrypointName := range frontend.EntryPoints {
|
||||||
|
dynamicConfig.entrypoints[entrypointName] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for backendName, backend := range config.Backends {
|
||||||
|
dynamicConfig.backends[backendName] = make(map[string]bool)
|
||||||
|
for _, server := range backend.Servers {
|
||||||
|
dynamicConfig.backends[backendName][server.URL] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
promState.SetDynamicConfig(dynamicConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPrometheusState() *prometheusState {
|
func newPrometheusState() *prometheusState {
|
||||||
collectors := make(chan *collector)
|
|
||||||
state := make(map[string]*collector)
|
|
||||||
|
|
||||||
return &prometheusState{
|
return &prometheusState{
|
||||||
collectors: collectors,
|
collectors: make(chan *collector),
|
||||||
state: state,
|
dynamicConfig: newDynamicConfig(),
|
||||||
|
state: make(map[string]*collector),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type prometheusState struct {
|
type prometheusState struct {
|
||||||
currentGeneration int
|
|
||||||
collectors chan *collector
|
collectors chan *collector
|
||||||
describers []func(ch chan<- *stdprometheus.Desc)
|
describers []func(ch chan<- *stdprometheus.Desc)
|
||||||
|
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
|
dynamicConfig *dynamicConfig
|
||||||
state map[string]*collector
|
state map[string]*collector
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *prometheusState) IncGeneration() {
|
// reset is a utility method for unit testing. It should be called after each
|
||||||
|
// test run that changes promState internally in order to avoid dependencies
|
||||||
|
// between unit tests.
|
||||||
|
func (ps *prometheusState) reset() {
|
||||||
|
ps.collectors = make(chan *collector)
|
||||||
|
ps.describers = []func(ch chan<- *stdprometheus.Desc){}
|
||||||
|
ps.dynamicConfig = newDynamicConfig()
|
||||||
|
ps.state = make(map[string]*collector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ps *prometheusState) SetDynamicConfig(dynamicConfig *dynamicConfig) {
|
||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
ps.currentGeneration++
|
ps.dynamicConfig = dynamicConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *prometheusState) ListenValueUpdates() {
|
func (ps *prometheusState) ListenValueUpdates() {
|
||||||
for collector := range ps.collectors {
|
for collector := range ps.collectors {
|
||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
collector.lastTrackedGeneration = ps.currentGeneration
|
|
||||||
ps.state[collector.id] = collector
|
ps.state[collector.id] = collector
|
||||||
ps.mtx.Unlock()
|
ps.mtx.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -212,42 +231,89 @@ func (ps *prometheusState) Describe(ch chan<- *stdprometheus.Desc) {
|
||||||
|
|
||||||
// Collect implements prometheus.Collector. It calls the Collect
|
// Collect implements prometheus.Collector. It calls the Collect
|
||||||
// method of all metrics it received on the collectors channel.
|
// method of all metrics it received on the collectors channel.
|
||||||
// It's also responsible to remove metrics that were tracked
|
// It's also responsible to remove metrics that belong to an outdated configuration.
|
||||||
// at least three generations ago. Those metrics are cleaned up
|
// The removal happens only after their Collect method was called to ensure that
|
||||||
// after the Collect of them were called.
|
// also those metrics will be exported on the current scrape.
|
||||||
func (ps *prometheusState) Collect(ch chan<- stdprometheus.Metric) {
|
func (ps *prometheusState) Collect(ch chan<- stdprometheus.Metric) {
|
||||||
ps.mtx.Lock()
|
ps.mtx.Lock()
|
||||||
defer ps.mtx.Unlock()
|
defer ps.mtx.Unlock()
|
||||||
|
|
||||||
outdatedKeys := []string{}
|
var outdatedKeys []string
|
||||||
for key, cs := range ps.state {
|
for key, cs := range ps.state {
|
||||||
cs.collector.Collect(ch)
|
cs.collector.Collect(ch)
|
||||||
|
|
||||||
if cs.maxAge == generationAgeForever {
|
if ps.isOutdated(cs) {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ps.currentGeneration-cs.lastTrackedGeneration >= cs.maxAge {
|
|
||||||
outdatedKeys = append(outdatedKeys, key)
|
outdatedKeys = append(outdatedKeys, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, key := range outdatedKeys {
|
for _, key := range outdatedKeys {
|
||||||
|
ps.state[key].delete()
|
||||||
delete(ps.state, key)
|
delete(ps.state, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCollector(metricName string, lnvs labelNamesValues, c stdprometheus.Collector) *collector {
|
// isOutdated checks whether the passed collector has labels that mark
|
||||||
maxAge := generationAgeDefault
|
// it as belonging to an outdated configuration of Traefik.
|
||||||
|
func (ps *prometheusState) isOutdated(collector *collector) bool {
|
||||||
|
labels := collector.labels
|
||||||
|
|
||||||
// metrics without labels should never become outdated
|
if entrypointName, ok := labels["entrypoint"]; ok && !ps.dynamicConfig.hasEntrypoint(entrypointName) {
|
||||||
if len(lnvs) == 0 {
|
return true
|
||||||
maxAge = generationAgeForever
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if backendName, ok := labels["backend"]; ok {
|
||||||
|
if !ps.dynamicConfig.hasBackend(backendName) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if url, ok := labels["url"]; ok && !ps.dynamicConfig.hasServerURL(backendName, url) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDynamicConfig() *dynamicConfig {
|
||||||
|
return &dynamicConfig{
|
||||||
|
entrypoints: make(map[string]bool),
|
||||||
|
backends: make(map[string]map[string]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dynamicConfig holds the current configuration for entrypoints, backends,
|
||||||
|
// and server URLs in an optimized way to check for existence. This provides
|
||||||
|
// a performant way to check whether the collected metrics belong to the
|
||||||
|
// current configuration or to an outdated one.
|
||||||
|
type dynamicConfig struct {
|
||||||
|
entrypoints map[string]bool
|
||||||
|
backends map[string]map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dynamicConfig) hasEntrypoint(entrypointName string) bool {
|
||||||
|
_, ok := d.entrypoints[entrypointName]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dynamicConfig) hasBackend(backendName string) bool {
|
||||||
|
_, ok := d.backends[backendName]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dynamicConfig) hasServerURL(backendName, serverURL string) bool {
|
||||||
|
if backend, hasBackend := d.backends[backendName]; hasBackend {
|
||||||
|
_, ok := backend[serverURL]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCollector(metricName string, labels stdprometheus.Labels, c stdprometheus.Collector, delete func()) *collector {
|
||||||
return &collector{
|
return &collector{
|
||||||
id: buildMetricID(metricName, lnvs),
|
id: buildMetricID(metricName, labels),
|
||||||
maxAge: maxAge,
|
labels: labels,
|
||||||
collector: c,
|
collector: c,
|
||||||
|
delete: delete,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,15 +322,18 @@ func newCollector(metricName string, lnvs labelNamesValues, c stdprometheus.Coll
|
||||||
// in the /metrics output, relatived to the time it was last tracked.
|
// in the /metrics output, relatived to the time it was last tracked.
|
||||||
type collector struct {
|
type collector struct {
|
||||||
id string
|
id string
|
||||||
|
labels stdprometheus.Labels
|
||||||
collector stdprometheus.Collector
|
collector stdprometheus.Collector
|
||||||
lastTrackedGeneration int
|
delete func()
|
||||||
maxAge int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildMetricID(metricName string, lnvs labelNamesValues) string {
|
func buildMetricID(metricName string, labels stdprometheus.Labels) string {
|
||||||
newLnvs := append([]string{}, lnvs...)
|
var labelNamesValues []string
|
||||||
sort.Strings(newLnvs)
|
for name, value := range labels {
|
||||||
return metricName + ":" + strings.Join(newLnvs, "|")
|
labelNamesValues = append(labelNamesValues, name, value)
|
||||||
|
}
|
||||||
|
sort.Strings(labelNamesValues)
|
||||||
|
return metricName + ":" + strings.Join(labelNamesValues, "|")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCounterFrom(collectors chan<- *collector, opts stdprometheus.CounterOpts, labelNames []string) *counter {
|
func newCounterFrom(collectors chan<- *collector, opts stdprometheus.CounterOpts, labelNames []string) *counter {
|
||||||
|
@ -297,9 +366,12 @@ func (c *counter) With(labelValues ...string) metrics.Counter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *counter) Add(delta float64) {
|
func (c *counter) Add(delta float64) {
|
||||||
collector := c.cv.With(c.labelNamesValues.ToLabels())
|
labels := c.labelNamesValues.ToLabels()
|
||||||
|
collector := c.cv.With(labels)
|
||||||
collector.Add(delta)
|
collector.Add(delta)
|
||||||
c.collectors <- newCollector(c.name, c.labelNamesValues, collector)
|
c.collectors <- newCollector(c.name, labels, collector, func() {
|
||||||
|
c.cv.Delete(labels)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *counter) Describe(ch chan<- *stdprometheus.Desc) {
|
func (c *counter) Describe(ch chan<- *stdprometheus.Desc) {
|
||||||
|
@ -336,15 +408,21 @@ func (g *gauge) With(labelValues ...string) metrics.Gauge {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gauge) Add(delta float64) {
|
func (g *gauge) Add(delta float64) {
|
||||||
collector := g.gv.With(g.labelNamesValues.ToLabels())
|
labels := g.labelNamesValues.ToLabels()
|
||||||
|
collector := g.gv.With(labels)
|
||||||
collector.Add(delta)
|
collector.Add(delta)
|
||||||
g.collectors <- newCollector(g.name, g.labelNamesValues, collector)
|
g.collectors <- newCollector(g.name, labels, collector, func() {
|
||||||
|
g.gv.Delete(labels)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gauge) Set(value float64) {
|
func (g *gauge) Set(value float64) {
|
||||||
collector := g.gv.With(g.labelNamesValues.ToLabels())
|
labels := g.labelNamesValues.ToLabels()
|
||||||
|
collector := g.gv.With(labels)
|
||||||
collector.Set(value)
|
collector.Set(value)
|
||||||
g.collectors <- newCollector(g.name, g.labelNamesValues, collector)
|
g.collectors <- newCollector(g.name, labels, collector, func() {
|
||||||
|
g.gv.Delete(labels)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gauge) Describe(ch chan<- *stdprometheus.Desc) {
|
func (g *gauge) Describe(ch chan<- *stdprometheus.Desc) {
|
||||||
|
@ -377,9 +455,12 @@ func (h *histogram) With(labelValues ...string) metrics.Histogram {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *histogram) Observe(value float64) {
|
func (h *histogram) Observe(value float64) {
|
||||||
collector := h.hv.With(h.labelNamesValues.ToLabels())
|
labels := h.labelNamesValues.ToLabels()
|
||||||
|
collector := h.hv.With(labels)
|
||||||
collector.Observe(value)
|
collector.Observe(value)
|
||||||
h.collectors <- newCollector(h.name, h.labelNamesValues, collector)
|
h.collectors <- newCollector(h.name, labels, collector, func() {
|
||||||
|
h.hv.Delete(labels)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *histogram) Describe(ch chan<- *stdprometheus.Desc) {
|
func (h *histogram) Describe(ch chan<- *stdprometheus.Desc) {
|
||||||
|
|
|
@ -7,12 +7,16 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
th "github.com/containous/traefik/testhelpers"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrometheus(t *testing.T) {
|
func TestPrometheus(t *testing.T) {
|
||||||
|
// Reset state of global promState.
|
||||||
|
defer promState.reset()
|
||||||
|
|
||||||
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
||||||
defer prometheus.Unregister(promState)
|
defer prometheus.Unregister(promState)
|
||||||
|
|
||||||
|
@ -177,56 +181,94 @@ func TestPrometheus(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrometheusGenerationLogicForMetricWithLabel(t *testing.T) {
|
func TestPrometheusMetricRemoval(t *testing.T) {
|
||||||
|
// Reset state of global promState.
|
||||||
|
defer promState.reset()
|
||||||
|
|
||||||
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
||||||
defer prometheus.Unregister(promState)
|
defer prometheus.Unregister(promState)
|
||||||
|
|
||||||
// Metrics with labels belonging to a specific configuration in Traefik
|
configurations := make(types.Configurations)
|
||||||
// should be removed when the generationMaxAge is exceeded. As example
|
configurations["providerName"] = th.BuildConfiguration(
|
||||||
// we use the traefik_backend_requests_total metric.
|
th.WithFrontends(
|
||||||
|
th.WithFrontend("backend1", th.WithEntryPoints("entrypoint1")),
|
||||||
|
),
|
||||||
|
th.WithBackends(
|
||||||
|
th.WithBackendNew("backend1", th.WithServersNew(th.WithServerNew("http://localhost:9000"))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OnConfigurationUpdate(configurations)
|
||||||
|
|
||||||
|
// Register some metrics manually that are not part of the active configuration.
|
||||||
|
// Those metrics should be part of the /metrics output on the first scrape but
|
||||||
|
// should be removed after that scrape.
|
||||||
|
prometheusRegistry.
|
||||||
|
EntrypointReqsCounter().
|
||||||
|
With("entrypoint", "entrypoint2", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||||
|
Add(1)
|
||||||
prometheusRegistry.
|
prometheusRegistry.
|
||||||
BackendReqsCounter().
|
BackendReqsCounter().
|
||||||
With("backend", "backend1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
With("backend", "backend2", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||||
|
Add(1)
|
||||||
|
prometheusRegistry.
|
||||||
|
BackendServerUpGauge().
|
||||||
|
With("backend", "backend1", "url", "http://localhost:9999").
|
||||||
|
Set(1)
|
||||||
|
|
||||||
|
delayForTrackingCompletion()
|
||||||
|
|
||||||
|
assertMetricsExist(t, mustScrape(), entrypointReqsTotalName, backendReqsTotalName, backendServerUpName)
|
||||||
|
assertMetricsAbsent(t, mustScrape(), entrypointReqsTotalName, backendReqsTotalName, backendServerUpName)
|
||||||
|
|
||||||
|
// To verify that metrics belonging to active configurations are not removed
|
||||||
|
// here the counter examples.
|
||||||
|
prometheusRegistry.
|
||||||
|
EntrypointReqsCounter().
|
||||||
|
With("entrypoint", "entrypoint1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet, "protocol", "http").
|
||||||
Add(1)
|
Add(1)
|
||||||
|
|
||||||
delayForTrackingCompletion()
|
delayForTrackingCompletion()
|
||||||
|
|
||||||
assertMetricExists(t, backendReqsTotalName, mustScrape())
|
assertMetricsExist(t, mustScrape(), entrypointReqsTotalName)
|
||||||
|
assertMetricsExist(t, mustScrape(), entrypointReqsTotalName)
|
||||||
// Increase the config generation one more than the max age of a metric.
|
|
||||||
for i := 0; i < generationAgeDefault+1; i++ {
|
|
||||||
OnConfigurationUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// On the next scrape the metric still exists and will be removed
|
|
||||||
// after the scrape completed.
|
|
||||||
assertMetricExists(t, backendReqsTotalName, mustScrape())
|
|
||||||
|
|
||||||
// Now the metric should be absent.
|
|
||||||
assertMetricAbsent(t, backendReqsTotalName, mustScrape())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrometheusGenerationLogicForMetricWithoutLabel(t *testing.T) {
|
func TestPrometheusRemovedMetricsReset(t *testing.T) {
|
||||||
|
// Reset state of global promState.
|
||||||
|
defer promState.reset()
|
||||||
|
|
||||||
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
prometheusRegistry := RegisterPrometheus(&types.Prometheus{})
|
||||||
defer prometheus.Unregister(promState)
|
defer prometheus.Unregister(promState)
|
||||||
|
|
||||||
// Metrics without labels like traefik_config_reloads_total should live forever
|
labelNamesValues := []string{
|
||||||
// and never get removed.
|
"backend", "backend",
|
||||||
prometheusRegistry.ConfigReloadsCounter().Add(1)
|
"code", strconv.Itoa(http.StatusOK),
|
||||||
|
"method", http.MethodGet,
|
||||||
|
"protocol", "http",
|
||||||
|
}
|
||||||
|
prometheusRegistry.
|
||||||
|
BackendReqsCounter().
|
||||||
|
With(labelNamesValues...).
|
||||||
|
Add(3)
|
||||||
|
|
||||||
delayForTrackingCompletion()
|
delayForTrackingCompletion()
|
||||||
|
|
||||||
assertMetricExists(t, configReloadsTotalName, mustScrape())
|
metricsFamilies := mustScrape()
|
||||||
|
assertCounterValue(t, 3, findMetricFamily(backendReqsTotalName, metricsFamilies), labelNamesValues...)
|
||||||
|
|
||||||
// Increase the config generation one more than the max age of a metric.
|
// There is no dynamic configuration and so this metric will be deleted
|
||||||
for i := 0; i < generationAgeDefault+100; i++ {
|
// after the first scrape.
|
||||||
OnConfigurationUpdate()
|
assertMetricsAbsent(t, mustScrape(), backendReqsTotalName)
|
||||||
}
|
|
||||||
|
|
||||||
// Scrape two times in order to verify, that it is not removed after the
|
prometheusRegistry.
|
||||||
// first scrape completed.
|
BackendReqsCounter().
|
||||||
assertMetricExists(t, configReloadsTotalName, mustScrape())
|
With(labelNamesValues...).
|
||||||
assertMetricExists(t, configReloadsTotalName, mustScrape())
|
Add(1)
|
||||||
|
|
||||||
|
delayForTrackingCompletion()
|
||||||
|
|
||||||
|
metricsFamilies = mustScrape()
|
||||||
|
assertCounterValue(t, 1, findMetricFamily(backendReqsTotalName, metricsFamilies), labelNamesValues...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracking and gathering the metrics happens concurrently.
|
// Tracking and gathering the metrics happens concurrently.
|
||||||
|
@ -247,17 +289,23 @@ func mustScrape() []*dto.MetricFamily {
|
||||||
return families
|
return families
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertMetricExists(t *testing.T, name string, families []*dto.MetricFamily) {
|
func assertMetricsExist(t *testing.T, families []*dto.MetricFamily, metricNames ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if findMetricFamily(name, families) == nil {
|
|
||||||
t.Errorf("gathered metrics do not contain %q", name)
|
for _, metricName := range metricNames {
|
||||||
|
if findMetricFamily(metricName, families) == nil {
|
||||||
|
t.Errorf("gathered metrics should contain %q", metricName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertMetricAbsent(t *testing.T, name string, families []*dto.MetricFamily) {
|
func assertMetricsAbsent(t *testing.T, families []*dto.MetricFamily, metricNames ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if findMetricFamily(name, families) != nil {
|
|
||||||
t.Errorf("gathered metrics contain %q, but should not", name)
|
for _, metricName := range metricNames {
|
||||||
|
if findMetricFamily(metricName, families) != nil {
|
||||||
|
t.Errorf("gathered metrics should not contain %q", metricName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,6 +318,58 @@ func findMetricFamily(name string, families []*dto.MetricFamily) *dto.MetricFami
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findMetricByLabelNamesValues(family *dto.MetricFamily, labelNamesValues ...string) *dto.Metric {
|
||||||
|
if family == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metric := range family.Metric {
|
||||||
|
if hasMetricAllLabelPairs(metric, labelNamesValues...) {
|
||||||
|
return metric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasMetricAllLabelPairs(metric *dto.Metric, labelNamesValues ...string) bool {
|
||||||
|
for i := 0; i < len(labelNamesValues); i += 2 {
|
||||||
|
name, val := labelNamesValues[i], labelNamesValues[i+1]
|
||||||
|
if !hasMetricLabelPair(metric, name, val) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasMetricLabelPair(metric *dto.Metric, labelName, labelValue string) bool {
|
||||||
|
for _, labelPair := range metric.Label {
|
||||||
|
if labelPair.GetName() == labelName && labelPair.GetValue() == labelValue {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCounterValue(t *testing.T, want float64, family *dto.MetricFamily, labelNamesValues ...string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
metric := findMetricByLabelNamesValues(family, labelNamesValues...)
|
||||||
|
|
||||||
|
if metric == nil {
|
||||||
|
t.Error("metric must not be nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if metric.Counter == nil {
|
||||||
|
t.Errorf("metric %s must be a counter", family.GetName())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cv := metric.Counter.GetValue(); cv != want {
|
||||||
|
t.Errorf("metric %s has value %v, want %v", family.GetName(), cv, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func buildCounterAssert(t *testing.T, metricName string, expectedValue int) func(family *dto.MetricFamily) {
|
func buildCounterAssert(t *testing.T, metricName string, expectedValue int) func(family *dto.MetricFamily) {
|
||||||
return func(family *dto.MetricFamily) {
|
return func(family *dto.MetricFamily) {
|
||||||
if cv := int(family.Metric[0].Counter.GetValue()); cv != expectedValue {
|
if cv := int(family.Metric[0].Counter.GetValue()); cv != expectedValue {
|
||||||
|
|
|
@ -3,15 +3,14 @@ package accesslog
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/negroni"
|
"github.com/urfave/negroni"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SaveBackend sends the backend name to the logger. These are always used with a corresponding
|
// SaveBackend sends the backend name to the logger.
|
||||||
// SaveFrontend handler.
|
// These are always used with a corresponding SaveFrontend handler.
|
||||||
type SaveBackend struct {
|
type SaveBackend struct {
|
||||||
next http.Handler
|
next http.Handler
|
||||||
backendName string
|
backendName string
|
||||||
|
@ -23,61 +22,9 @@ func NewSaveBackend(next http.Handler, backendName string) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
func (sb *SaveBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
table := GetLogDataTable(r)
|
serveSaveBackend(rw, r, sb.backendName, func(crw *captureResponseWriter) {
|
||||||
table.Core[BackendName] = sb.backendName
|
|
||||||
table.Core[BackendURL] = r.URL // note that this is *not* the original incoming URL
|
|
||||||
table.Core[BackendAddr] = r.URL.Host
|
|
||||||
|
|
||||||
crw := &captureResponseWriter{rw: rw}
|
|
||||||
start := time.Now().UTC()
|
|
||||||
|
|
||||||
sb.next.ServeHTTP(crw, r)
|
sb.next.ServeHTTP(crw, r)
|
||||||
|
})
|
||||||
// use UTC to handle switchover of daylight saving correctly
|
|
||||||
table.Core[OriginDuration] = time.Now().UTC().Sub(start)
|
|
||||||
table.Core[OriginStatus] = crw.Status()
|
|
||||||
table.Core[OriginStatusLine] = fmt.Sprintf("%03d %s", crw.Status(), http.StatusText(crw.Status()))
|
|
||||||
// make copy of headers so we can ensure there is no subsequent mutation during response processing
|
|
||||||
table.OriginResponse = make(http.Header)
|
|
||||||
utils.CopyHeaders(table.OriginResponse, crw.Header())
|
|
||||||
table.Core[OriginContentSize] = crw.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveFrontend sends the frontend name to the logger. These are sometimes used with a corresponding
|
|
||||||
// SaveBackend handler, but not always. For example, redirected requests don't reach a backend.
|
|
||||||
type SaveFrontend struct {
|
|
||||||
next http.Handler
|
|
||||||
frontendName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSaveFrontend creates a SaveFrontend handler.
|
|
||||||
func NewSaveFrontend(next http.Handler, frontendName string) http.Handler {
|
|
||||||
return &SaveFrontend{next, frontendName}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb *SaveFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|
||||||
table := GetLogDataTable(r)
|
|
||||||
table.Core[FrontendName] = strings.TrimPrefix(sb.frontendName, "frontend-")
|
|
||||||
|
|
||||||
sb.next.ServeHTTP(rw, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveNegroniFrontend sends the frontend name to the logger.
|
|
||||||
type SaveNegroniFrontend struct {
|
|
||||||
next negroni.Handler
|
|
||||||
frontendName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSaveNegroniFrontend creates a SaveNegroniFrontend handler.
|
|
||||||
func NewSaveNegroniFrontend(next negroni.Handler, frontendName string) negroni.Handler {
|
|
||||||
return &SaveNegroniFrontend{next, frontendName}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb *SaveNegroniFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
|
||||||
table := GetLogDataTable(r)
|
|
||||||
table.Core[FrontendName] = strings.TrimPrefix(sb.frontendName, "frontend-")
|
|
||||||
|
|
||||||
sb.next.ServeHTTP(rw, r, next)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveNegroniBackend sends the backend name to the logger.
|
// SaveNegroniBackend sends the backend name to the logger.
|
||||||
|
@ -92,13 +39,21 @@ func NewSaveNegroniBackend(next negroni.Handler, backendName string) negroni.Han
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *SaveNegroniBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
func (sb *SaveNegroniBackend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
serveSaveBackend(rw, r, sb.backendName, func(crw *captureResponseWriter) {
|
||||||
|
sb.next.ServeHTTP(crw, r, next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveSaveBackend(rw http.ResponseWriter, r *http.Request, backendName string, apply func(*captureResponseWriter)) {
|
||||||
table := GetLogDataTable(r)
|
table := GetLogDataTable(r)
|
||||||
table.Core[BackendName] = sb.backendName
|
table.Core[BackendName] = backendName
|
||||||
|
table.Core[BackendURL] = r.URL // note that this is *not* the original incoming URL
|
||||||
|
table.Core[BackendAddr] = r.URL.Host
|
||||||
|
|
||||||
crw := &captureResponseWriter{rw: rw}
|
crw := &captureResponseWriter{rw: rw}
|
||||||
start := time.Now().UTC()
|
start := time.Now().UTC()
|
||||||
|
|
||||||
sb.next.ServeHTTP(crw, r, next)
|
apply(crw)
|
||||||
|
|
||||||
// use UTC to handle switchover of daylight saving correctly
|
// use UTC to handle switchover of daylight saving correctly
|
||||||
table.Core[OriginDuration] = time.Now().UTC().Sub(start)
|
table.Core[OriginDuration] = time.Now().UTC().Sub(start)
|
||||||
|
|
51
middlewares/accesslog/save_frontend.go
Normal file
51
middlewares/accesslog/save_frontend.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package accesslog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/urfave/negroni"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SaveFrontend sends the frontend name to the logger.
|
||||||
|
// These are sometimes used with a corresponding SaveBackend handler, but not always.
|
||||||
|
// For example, redirected requests don't reach a backend.
|
||||||
|
type SaveFrontend struct {
|
||||||
|
next http.Handler
|
||||||
|
frontendName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSaveFrontend creates a SaveFrontend handler.
|
||||||
|
func NewSaveFrontend(next http.Handler, frontendName string) http.Handler {
|
||||||
|
return &SaveFrontend{next, frontendName}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *SaveFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
serveSaveFrontend(r, sf.frontendName, func() {
|
||||||
|
sf.next.ServeHTTP(rw, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveNegroniFrontend sends the frontend name to the logger.
|
||||||
|
type SaveNegroniFrontend struct {
|
||||||
|
next negroni.Handler
|
||||||
|
frontendName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSaveNegroniFrontend creates a SaveNegroniFrontend handler.
|
||||||
|
func NewSaveNegroniFrontend(next negroni.Handler, frontendName string) negroni.Handler {
|
||||||
|
return &SaveNegroniFrontend{next, frontendName}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sf *SaveNegroniFrontend) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||||
|
serveSaveFrontend(r, sf.frontendName, func() {
|
||||||
|
sf.next.ServeHTTP(rw, r, next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveSaveFrontend(r *http.Request, frontendName string, apply func()) {
|
||||||
|
table := GetLogDataTable(r)
|
||||||
|
table.Core[FrontendName] = strings.TrimPrefix(frontendName, "frontend-")
|
||||||
|
|
||||||
|
apply()
|
||||||
|
}
|
|
@ -101,12 +101,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request, next http.
|
||||||
|
|
||||||
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
|
h.backendHandler.ServeHTTP(recorderErrorPage, pageReq.WithContext(req.Context()))
|
||||||
|
|
||||||
utils.CopyHeaders(w.Header(), recorder.Header())
|
|
||||||
for key := range recorderErrorPage.Header() {
|
|
||||||
w.Header().Del(key)
|
|
||||||
}
|
|
||||||
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
utils.CopyHeaders(w.Header(), recorderErrorPage.Header())
|
||||||
|
|
||||||
w.WriteHeader(recorder.GetCode())
|
w.WriteHeader(recorder.GetCode())
|
||||||
w.Write(recorderErrorPage.GetBody().Bytes())
|
w.Write(recorderErrorPage.GetBody().Bytes())
|
||||||
return
|
return
|
||||||
|
@ -174,64 +169,65 @@ type responseRecorderWithCloseNotify struct {
|
||||||
|
|
||||||
// CloseNotify returns a channel that receives at most a
|
// CloseNotify returns a channel that receives at most a
|
||||||
// single value (true) when the client connection has gone away.
|
// single value (true) when the client connection has gone away.
|
||||||
func (rw *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
|
func (r *responseRecorderWithCloseNotify) CloseNotify() <-chan bool {
|
||||||
return rw.responseWriter.(http.CloseNotifier).CloseNotify()
|
return r.responseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header returns the response headers.
|
// Header returns the response headers.
|
||||||
func (rw *responseRecorderWithoutCloseNotify) Header() http.Header {
|
func (r *responseRecorderWithoutCloseNotify) Header() http.Header {
|
||||||
if rw.HeaderMap == nil {
|
if r.HeaderMap == nil {
|
||||||
rw.HeaderMap = make(http.Header)
|
r.HeaderMap = make(http.Header)
|
||||||
}
|
}
|
||||||
return rw.HeaderMap
|
|
||||||
|
return r.HeaderMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *responseRecorderWithoutCloseNotify) GetCode() int {
|
func (r *responseRecorderWithoutCloseNotify) GetCode() int {
|
||||||
return rw.Code
|
return r.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
|
func (r *responseRecorderWithoutCloseNotify) GetBody() *bytes.Buffer {
|
||||||
return rw.Body
|
return r.Body
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
|
func (r *responseRecorderWithoutCloseNotify) IsStreamingResponseStarted() bool {
|
||||||
return rw.streamingResponseStarted
|
return r.streamingResponseStarted
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write always succeeds and writes to rw.Body, if not nil.
|
// Write always succeeds and writes to rw.Body, if not nil.
|
||||||
func (rw *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
|
func (r *responseRecorderWithoutCloseNotify) Write(buf []byte) (int, error) {
|
||||||
if rw.err != nil {
|
if r.err != nil {
|
||||||
return 0, rw.err
|
return 0, r.err
|
||||||
}
|
}
|
||||||
return rw.Body.Write(buf)
|
return r.Body.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteHeader sets rw.Code.
|
// WriteHeader sets rw.Code.
|
||||||
func (rw *responseRecorderWithoutCloseNotify) WriteHeader(code int) {
|
func (r *responseRecorderWithoutCloseNotify) WriteHeader(code int) {
|
||||||
rw.Code = code
|
r.Code = code
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hijack hijacks the connection
|
// Hijack hijacks the connection
|
||||||
func (rw *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (r *responseRecorderWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
return rw.responseWriter.(http.Hijacker).Hijack()
|
return r.responseWriter.(http.Hijacker).Hijack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush sends any buffered data to the client.
|
// Flush sends any buffered data to the client.
|
||||||
func (rw *responseRecorderWithoutCloseNotify) Flush() {
|
func (r *responseRecorderWithoutCloseNotify) Flush() {
|
||||||
if !rw.streamingResponseStarted {
|
if !r.streamingResponseStarted {
|
||||||
utils.CopyHeaders(rw.responseWriter.Header(), rw.Header())
|
utils.CopyHeaders(r.responseWriter.Header(), r.Header())
|
||||||
rw.responseWriter.WriteHeader(rw.Code)
|
r.responseWriter.WriteHeader(r.Code)
|
||||||
rw.streamingResponseStarted = true
|
r.streamingResponseStarted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := rw.responseWriter.Write(rw.Body.Bytes())
|
_, err := r.responseWriter.Write(r.Body.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error writing response in responseRecorder: %s", err)
|
log.Errorf("Error writing response in responseRecorder: %v", err)
|
||||||
rw.err = err
|
r.err = err
|
||||||
}
|
}
|
||||||
rw.Body.Reset()
|
r.Body.Reset()
|
||||||
|
|
||||||
if flusher, ok := rw.responseWriter.(http.Flusher); ok {
|
if flusher, ok := r.responseWriter.(http.Flusher); ok {
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,7 +318,6 @@ func TestHandlerOldWayIntegration(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-Foo", "bar")
|
|
||||||
w.WriteHeader(test.backendCode)
|
w.WriteHeader(test.backendCode)
|
||||||
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
fmt.Fprintln(w, http.StatusText(test.backendCode))
|
||||||
})
|
})
|
||||||
|
@ -331,7 +330,6 @@ func TestHandlerOldWayIntegration(t *testing.T) {
|
||||||
n.ServeHTTP(recorder, req)
|
n.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
test.validate(t, recorder)
|
test.validate(t, recorder)
|
||||||
assert.Equal(t, "bar", recorder.Header().Get("X-Foo"), "missing header")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -60,6 +60,7 @@ func (s *LocalStore) get() (*StoredData, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if isOldRegistration {
|
if isOldRegistration {
|
||||||
|
log.Debug("Reset ACME account.")
|
||||||
s.storedData.Account = nil
|
s.storedData.Account = nil
|
||||||
s.SaveDataChan <- s.storedData
|
s.SaveDataChan <- s.storedData
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,10 @@ import (
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
traefiktls "github.com/containous/traefik/tls"
|
traefiktls "github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
|
"github.com/containous/traefik/version"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,10 +88,11 @@ func (p *Provider) SetConfigListenerChan(configFromListenerChan chan types.Confi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) init() error {
|
func (p *Provider) init() error {
|
||||||
|
acme.UserAgent = fmt.Sprintf("containous-traefik/%s", version.Version)
|
||||||
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
|
||||||
|
@ -103,6 +106,11 @@ func (p *Provider) init() error {
|
||||||
return fmt.Errorf("unable to get ACME account : %v", err)
|
return fmt.Errorf("unable to get ACME account : %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset Account if caServer changed, thus registration URI can be updated
|
||||||
|
if p.account != nil && p.account.Registration != nil && !strings.HasPrefix(p.account.Registration.URI, p.CAServer) {
|
||||||
|
p.account = nil
|
||||||
|
}
|
||||||
|
|
||||||
p.certificates, err = p.Store.GetCertificates()
|
p.certificates, err = p.Store.GetCertificates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to get ACME certificates : %v", err)
|
return fmt.Errorf("unable to get ACME certificates : %v", err)
|
||||||
|
@ -227,7 +235,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) {
|
||||||
|
@ -299,7 +307,6 @@ func (p *Provider) getClient() (*acme.Client, error) {
|
||||||
}
|
}
|
||||||
p.client = client
|
p.client = client
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.client, nil
|
return p.client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package consulcatalog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -255,7 +256,8 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s
|
||||||
|
|
||||||
safe.Go(func() {
|
safe.Go(func() {
|
||||||
// variable to hold previous state
|
// variable to hold previous state
|
||||||
var flashback []string
|
var flashback map[string][]string
|
||||||
|
var flashbackMaintenance []string
|
||||||
|
|
||||||
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
options := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
|
||||||
|
|
||||||
|
@ -267,19 +269,31 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listening to changes that leads to `passing` state or degrades from it.
|
// Listening to changes that leads to `passing` state or degrades from it.
|
||||||
healthyState, meta, err := health.State("passing", options)
|
healthyState, meta, err := health.State("any", options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Failed to retrieve health checks")
|
log.WithError(err).Error("Failed to retrieve health checks")
|
||||||
notifyError(err)
|
notifyError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var current []string
|
var current = make(map[string][]string)
|
||||||
|
var currentFailing = make(map[string]*api.HealthCheck)
|
||||||
|
var maintenance []string
|
||||||
if healthyState != nil {
|
if healthyState != nil {
|
||||||
for _, healthy := range healthyState {
|
for _, healthy := range healthyState {
|
||||||
current = append(current, healthy.ServiceID)
|
key := fmt.Sprintf("%s-%s", healthy.Node, healthy.ServiceID)
|
||||||
|
_, failing := currentFailing[key]
|
||||||
|
if healthy.Status == "passing" && !failing {
|
||||||
|
current[key] = append(current[key], healthy.Node)
|
||||||
|
} else if strings.HasPrefix(healthy.CheckID, "_service_maintenance") || strings.HasPrefix(healthy.CheckID, "_node_maintenance") {
|
||||||
|
maintenance = append(maintenance, healthy.CheckID)
|
||||||
|
} else {
|
||||||
|
currentFailing[key] = healthy
|
||||||
|
if _, ok := current[key]; ok {
|
||||||
|
delete(current, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If LastIndex didn't change then it means `Get` returned
|
// If LastIndex didn't change then it means `Get` returned
|
||||||
|
@ -302,18 +316,26 @@ func (p *Provider) watchHealthState(stopCh <-chan struct{}, watchCh chan<- map[s
|
||||||
// A critical note is that the return of a blocking request is no guarantee of a change.
|
// A critical note is that the return of a blocking request is no guarantee of a change.
|
||||||
// It is possible that there was an idempotent write that does not affect the result of the query.
|
// It is possible that there was an idempotent write that does not affect the result of the query.
|
||||||
// Thus it is required to do extra check for changes...
|
// Thus it is required to do extra check for changes...
|
||||||
addedKeys, removedKeys := getChangedStringKeys(current, flashback)
|
addedKeys, removedKeys, changedKeys := getChangedHealth(current, flashback)
|
||||||
|
|
||||||
|
if len(addedKeys) > 0 || len(removedKeys) > 0 || len(changedKeys) > 0 {
|
||||||
|
log.WithField("DiscoveredServices", addedKeys).
|
||||||
|
WithField("MissingServices", removedKeys).
|
||||||
|
WithField("ChangedServices", changedKeys).
|
||||||
|
Debug("Health State change detected.")
|
||||||
|
|
||||||
if len(addedKeys) > 0 {
|
|
||||||
log.WithField("DiscoveredServices", addedKeys).Debug("Health State change detected.")
|
|
||||||
watchCh <- data
|
watchCh <- data
|
||||||
flashback = current
|
flashback = current
|
||||||
|
flashbackMaintenance = maintenance
|
||||||
|
} else {
|
||||||
|
addedKeysMaintenance, removedMaintenance := getChangedStringKeys(maintenance, flashbackMaintenance)
|
||||||
|
|
||||||
|
if len(addedKeysMaintenance) > 0 || len(removedMaintenance) > 0 {
|
||||||
|
log.WithField("MaintenanceMode", maintenance).Debug("Maintenance change detected.")
|
||||||
|
watchCh <- data
|
||||||
|
flashback = current
|
||||||
|
flashbackMaintenance = maintenance
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(removedKeys) > 0 {
|
|
||||||
log.WithField("MissingServices", removedKeys).Debug("Health State change detected.")
|
|
||||||
watchCh <- data
|
|
||||||
flashback = current
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,6 +416,27 @@ func getChangedStringKeys(currState []string, prevState []string) ([]string, []s
|
||||||
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
|
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getChangedHealth(current map[string][]string, previous map[string][]string) ([]string, []string, []string) {
|
||||||
|
currKeySet := fun.Set(fun.Keys(current).([]string)).(map[string]bool)
|
||||||
|
prevKeySet := fun.Set(fun.Keys(previous).([]string)).(map[string]bool)
|
||||||
|
|
||||||
|
addedKeys := fun.Difference(currKeySet, prevKeySet).(map[string]bool)
|
||||||
|
removedKeys := fun.Difference(prevKeySet, currKeySet).(map[string]bool)
|
||||||
|
|
||||||
|
var changedKeys []string
|
||||||
|
|
||||||
|
for key, value := range current {
|
||||||
|
if prevValue, ok := previous[key]; ok {
|
||||||
|
addedNodesKeys, removedNodesKeys := getChangedStringKeys(value, prevValue)
|
||||||
|
if len(addedNodesKeys) > 0 || len(removedNodesKeys) > 0 {
|
||||||
|
changedKeys = append(changedKeys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fun.Keys(addedKeys).([]string), fun.Keys(removedKeys).([]string), changedKeys
|
||||||
|
}
|
||||||
|
|
||||||
func getChangedIntKeys(currState []int, prevState []int) ([]int, []int) {
|
func getChangedIntKeys(currState []int, prevState []int) ([]int, []int) {
|
||||||
currKeySet := fun.Set(currState).(map[int]bool)
|
currKeySet := fun.Set(currState).(map[int]bool)
|
||||||
prevKeySet := fun.Set(prevState).(map[int]bool)
|
prevKeySet := fun.Set(prevState).(map[int]bool)
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
@ -319,7 +321,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, address := range subset.Addresses {
|
for _, address := range subset.Addresses {
|
||||||
url := fmt.Sprintf("%s://%s:%d", protocol, address.IP, endpointPort)
|
url := protocol + "://" + net.JoinHostPort(address.IP, strconv.FormatInt(int64(endpointPort), 10))
|
||||||
name := url
|
name := url
|
||||||
if address.TargetRef != nil && address.TargetRef.Name != "" {
|
if address.TargetRef != nil && address.TargetRef.Name != "" {
|
||||||
name = address.TargetRef.Name
|
name = address.TargetRef.Name
|
||||||
|
|
|
@ -44,7 +44,7 @@ func (h *headerRewriter) Rewrite(req *http.Request) {
|
||||||
|
|
||||||
err := h.ips.IsAuthorized(req)
|
err := h.ips.IsAuthorized(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Debug(err)
|
||||||
h.secureRewriter.Rewrite(req)
|
h.secureRewriter.Rewrite(req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -557,7 +557,10 @@ func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) postLoadConfiguration() {
|
func (s *Server) postLoadConfiguration() {
|
||||||
metrics.OnConfigurationUpdate()
|
if s.metricsRegistry.IsEnabled() {
|
||||||
|
activeConfig := s.currentConfigurations.Get().(types.Configurations)
|
||||||
|
metrics.OnConfigurationUpdate(activeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
if s.globalConfiguration.ACME == nil || s.leadership == nil || !s.leadership.IsLeader() {
|
if s.globalConfiguration.ACME == nil || s.leadership == nil || !s.leadership.IsLeader() {
|
||||||
return
|
return
|
||||||
|
|
|
@ -16,9 +16,8 @@ import (
|
||||||
"github.com/containous/traefik/healthcheck"
|
"github.com/containous/traefik/healthcheck"
|
||||||
"github.com/containous/traefik/metrics"
|
"github.com/containous/traefik/metrics"
|
||||||
"github.com/containous/traefik/middlewares"
|
"github.com/containous/traefik/middlewares"
|
||||||
"github.com/containous/traefik/provider/label"
|
|
||||||
"github.com/containous/traefik/rules"
|
"github.com/containous/traefik/rules"
|
||||||
"github.com/containous/traefik/testhelpers"
|
th "github.com/containous/traefik/testhelpers"
|
||||||
"github.com/containous/traefik/tls"
|
"github.com/containous/traefik/tls"
|
||||||
"github.com/containous/traefik/types"
|
"github.com/containous/traefik/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -202,9 +201,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
config := buildDynamicConfig(
|
config := th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend()),
|
th.WithFrontends(th.WithFrontend("backend")),
|
||||||
withBackend("backend", buildBackend()),
|
th.WithBackends(th.WithBackendNew("backend")),
|
||||||
)
|
)
|
||||||
|
|
||||||
// provide a configuration
|
// provide a configuration
|
||||||
|
@ -243,9 +242,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
config := buildDynamicConfig(
|
config := th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend()),
|
th.WithFrontends(th.WithFrontend("backend")),
|
||||||
withBackend("backend", buildBackend()),
|
th.WithBackends(th.WithBackendNew("backend")),
|
||||||
)
|
)
|
||||||
server.configurationChan <- types.ConfigMessage{ProviderName: "kubernetes", Configuration: config}
|
server.configurationChan <- types.ConfigMessage{ProviderName: "kubernetes", Configuration: config}
|
||||||
server.configurationChan <- types.ConfigMessage{ProviderName: "marathon", Configuration: config}
|
server.configurationChan <- types.ConfigMessage{ProviderName: "marathon", Configuration: config}
|
||||||
|
@ -398,7 +397,7 @@ func TestServerMultipleFrontendRules(t *testing.T) {
|
||||||
t.Fatalf("Error while building route for %s: %+v", expression, err)
|
t.Fatalf("Error while building route for %s: %+v", expression, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
request := testhelpers.MustNewRequest(http.MethodGet, test.requestURL, nil)
|
request := th.MustNewRequest(http.MethodGet, test.requestURL, nil)
|
||||||
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
routeMatch := routeResult.Match(request, &mux.RouteMatch{Route: routeResult})
|
||||||
|
|
||||||
if !routeMatch {
|
if !routeMatch {
|
||||||
|
@ -479,7 +478,7 @@ func TestServerLoadConfigHealthCheckOptions(t *testing.T) {
|
||||||
if healthCheck != nil {
|
if healthCheck != nil {
|
||||||
expectedNumHealthCheckBackends = 1
|
expectedNumHealthCheckBackends = 1
|
||||||
}
|
}
|
||||||
assert.Len(t, healthcheck.GetHealthCheck(testhelpers.NewCollectingHealthCheckMetrics()).Backends, expectedNumHealthCheckBackends, "health check backends")
|
assert.Len(t, healthcheck.GetHealthCheck(th.NewCollectingHealthCheckMetrics()).Backends, expectedNumHealthCheckBackends, "health check backends")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -842,62 +841,88 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
dynamicConfig func(testServerURL string) *types.Configuration
|
config func(testServerURL string) *types.Configuration
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "Ok",
|
desc: "Ok",
|
||||||
dynamicConfig: func(testServerURL string) *types.Configuration {
|
config: func(testServerURL string) *types.Configuration {
|
||||||
return buildDynamicConfig(
|
return th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))),
|
th.WithFrontends(th.WithFrontend("backend",
|
||||||
withBackend("backend", buildBackend(withServer("testServer", testServerURL))),
|
th.WithEntryPoints("http"),
|
||||||
|
th.WithRoutes(th.WithRoute(requestPath, routeRule))),
|
||||||
|
),
|
||||||
|
th.WithBackends(th.WithBackendNew("backend",
|
||||||
|
th.WithLBMethod("wrr"),
|
||||||
|
th.WithServersNew(th.WithServerNew(testServerURL))),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "No Frontend",
|
desc: "No Frontend",
|
||||||
dynamicConfig: func(testServerURL string) *types.Configuration {
|
config: func(testServerURL string) *types.Configuration {
|
||||||
return buildDynamicConfig()
|
return th.BuildConfiguration()
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusNotFound,
|
expectedStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Empty Backend LB-Drr",
|
desc: "Empty Backend LB-Drr",
|
||||||
dynamicConfig: func(testServerURL string) *types.Configuration {
|
config: func(testServerURL string) *types.Configuration {
|
||||||
return buildDynamicConfig(
|
return th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))),
|
th.WithFrontends(th.WithFrontend("backend",
|
||||||
withBackend("backend", buildBackend(withLoadBalancer("Drr", false))),
|
th.WithEntryPoints("http"),
|
||||||
|
th.WithRoutes(th.WithRoute(requestPath, routeRule))),
|
||||||
|
),
|
||||||
|
th.WithBackends(th.WithBackendNew("backend",
|
||||||
|
th.WithLBMethod("drr")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusServiceUnavailable,
|
expectedStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Empty Backend LB-Drr Sticky",
|
desc: "Empty Backend LB-Drr Sticky",
|
||||||
dynamicConfig: func(testServerURL string) *types.Configuration {
|
config: func(testServerURL string) *types.Configuration {
|
||||||
return buildDynamicConfig(
|
return th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))),
|
th.WithFrontends(th.WithFrontend("backend",
|
||||||
withBackend("backend", buildBackend(withLoadBalancer("Drr", true))),
|
th.WithEntryPoints("http"),
|
||||||
|
th.WithRoutes(th.WithRoute(requestPath, routeRule))),
|
||||||
|
),
|
||||||
|
th.WithBackends(th.WithBackendNew("backend",
|
||||||
|
th.WithLBMethod("drr"), th.WithLBSticky("test")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusServiceUnavailable,
|
expectedStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Empty Backend LB-Wrr",
|
desc: "Empty Backend LB-Wrr",
|
||||||
dynamicConfig: func(testServerURL string) *types.Configuration {
|
config: func(testServerURL string) *types.Configuration {
|
||||||
return buildDynamicConfig(
|
return th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))),
|
th.WithFrontends(th.WithFrontend("backend",
|
||||||
withBackend("backend", buildBackend(withLoadBalancer("Wrr", false))),
|
th.WithEntryPoints("http"),
|
||||||
|
th.WithRoutes(th.WithRoute(requestPath, routeRule))),
|
||||||
|
),
|
||||||
|
th.WithBackends(th.WithBackendNew("backend",
|
||||||
|
th.WithLBMethod("wrr")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusServiceUnavailable,
|
expectedStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Empty Backend LB-Wrr Sticky",
|
desc: "Empty Backend LB-Wrr Sticky",
|
||||||
dynamicConfig: func(testServerURL string) *types.Configuration {
|
config: func(testServerURL string) *types.Configuration {
|
||||||
return buildDynamicConfig(
|
return th.BuildConfiguration(
|
||||||
withFrontend("frontend", buildFrontend(withRoute(requestPath, routeRule))),
|
th.WithFrontends(th.WithFrontend("backend",
|
||||||
withBackend("backend", buildBackend(withLoadBalancer("Wrr", true))),
|
th.WithEntryPoints("http"),
|
||||||
|
th.WithRoutes(th.WithRoute(requestPath, routeRule))),
|
||||||
|
),
|
||||||
|
th.WithBackends(th.WithBackendNew("backend",
|
||||||
|
th.WithLBMethod("wrr"), th.WithLBSticky("test")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expectedStatusCode: http.StatusServiceUnavailable,
|
expectedStatusCode: http.StatusServiceUnavailable,
|
||||||
|
@ -919,7 +944,7 @@ func TestServerResponseEmptyBackend(t *testing.T) {
|
||||||
entryPointsConfig := map[string]EntryPoint{
|
entryPointsConfig := map[string]EntryPoint{
|
||||||
"http": {Configuration: &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}}},
|
"http": {Configuration: &configuration.EntryPoint{ForwardedHeaders: &configuration.ForwardedHeaders{Insecure: true}}},
|
||||||
}
|
}
|
||||||
dynamicConfigs := types.Configurations{"config": test.dynamicConfig(testServer.URL)}
|
dynamicConfigs := types.Configurations{"config": test.config(testServer.URL)}
|
||||||
|
|
||||||
srv := NewServer(globalConfig, nil, entryPointsConfig)
|
srv := NewServer(globalConfig, nil, entryPointsConfig)
|
||||||
entryPoints, err := srv.loadConfig(dynamicConfigs, globalConfig)
|
entryPoints, err := srv.loadConfig(dynamicConfigs, globalConfig)
|
||||||
|
@ -1015,7 +1040,7 @@ func TestBuildRedirectHandler(t *testing.T) {
|
||||||
rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect)
|
rewrite, err := srv.buildRedirectHandler(test.srcEntryPointName, test.redirect)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req := testhelpers.MustNewRequest(http.MethodGet, test.url, nil)
|
req := th.MustNewRequest(http.MethodGet, test.url, nil)
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|
||||||
rewrite.ServeHTTP(recorder, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
rewrite.ServeHTTP(recorder, req, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -1145,71 +1170,3 @@ func TestNewServerWithResponseModifiers(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDynamicConfig(dynamicConfigBuilders ...func(*types.Configuration)) *types.Configuration {
|
|
||||||
config := &types.Configuration{
|
|
||||||
Frontends: make(map[string]*types.Frontend),
|
|
||||||
Backends: make(map[string]*types.Backend),
|
|
||||||
}
|
|
||||||
for _, build := range dynamicConfigBuilders {
|
|
||||||
build(config)
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func withFrontend(frontendName string, frontend *types.Frontend) func(*types.Configuration) {
|
|
||||||
return func(config *types.Configuration) {
|
|
||||||
config.Frontends[frontendName] = frontend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withBackend(backendName string, backend *types.Backend) func(*types.Configuration) {
|
|
||||||
return func(config *types.Configuration) {
|
|
||||||
config.Backends[backendName] = backend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildFrontend(frontendBuilders ...func(*types.Frontend)) *types.Frontend {
|
|
||||||
fe := &types.Frontend{
|
|
||||||
EntryPoints: []string{"http"},
|
|
||||||
Backend: "backend",
|
|
||||||
Routes: make(map[string]types.Route),
|
|
||||||
}
|
|
||||||
for _, build := range frontendBuilders {
|
|
||||||
build(fe)
|
|
||||||
}
|
|
||||||
return fe
|
|
||||||
}
|
|
||||||
|
|
||||||
func withRoute(routeName, rule string) func(*types.Frontend) {
|
|
||||||
return func(fe *types.Frontend) {
|
|
||||||
fe.Routes[routeName] = types.Route{Rule: rule}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBackend(backendBuilders ...func(*types.Backend)) *types.Backend {
|
|
||||||
be := &types.Backend{
|
|
||||||
Servers: make(map[string]types.Server),
|
|
||||||
LoadBalancer: &types.LoadBalancer{Method: "Wrr"},
|
|
||||||
}
|
|
||||||
for _, build := range backendBuilders {
|
|
||||||
build(be)
|
|
||||||
}
|
|
||||||
return be
|
|
||||||
}
|
|
||||||
|
|
||||||
func withServer(name, url string) func(backend *types.Backend) {
|
|
||||||
return func(be *types.Backend) {
|
|
||||||
be.Servers[name] = types.Server{URL: url, Weight: label.DefaultWeight}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func withLoadBalancer(method string, sticky bool) func(*types.Backend) {
|
|
||||||
return func(be *types.Backend) {
|
|
||||||
if sticky {
|
|
||||||
be.LoadBalancer = &types.LoadBalancer{Method: method, Stickiness: &types.Stickiness{CookieName: "test"}}
|
|
||||||
} else {
|
|
||||||
be.LoadBalancer = &types.LoadBalancer{Method: method}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
134
testhelpers/config.go
Normal file
134
testhelpers/config.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package testhelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containous/traefik/provider"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildConfiguration is a helper to create a configuration.
|
||||||
|
func BuildConfiguration(dynamicConfigBuilders ...func(*types.Configuration)) *types.Configuration {
|
||||||
|
config := &types.Configuration{}
|
||||||
|
for _, build := range dynamicConfigBuilders {
|
||||||
|
build(config)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Backend
|
||||||
|
|
||||||
|
// WithBackends is a helper to create a configuration
|
||||||
|
func WithBackends(opts ...func(*types.Backend) string) func(*types.Configuration) {
|
||||||
|
return func(c *types.Configuration) {
|
||||||
|
c.Backends = make(map[string]*types.Backend)
|
||||||
|
for _, opt := range opts {
|
||||||
|
b := &types.Backend{}
|
||||||
|
name := opt(b)
|
||||||
|
c.Backends[name] = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBackendNew is a helper to create a configuration
|
||||||
|
func WithBackendNew(name string, opts ...func(*types.Backend)) func(*types.Backend) string {
|
||||||
|
return func(b *types.Backend) string {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(b)
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServersNew is a helper to create a configuration
|
||||||
|
func WithServersNew(opts ...func(*types.Server) string) func(*types.Backend) {
|
||||||
|
return func(b *types.Backend) {
|
||||||
|
b.Servers = make(map[string]types.Server)
|
||||||
|
for _, opt := range opts {
|
||||||
|
s := &types.Server{Weight: 1}
|
||||||
|
name := opt(s)
|
||||||
|
b.Servers[name] = *s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithServerNew is a helper to create a configuration
|
||||||
|
func WithServerNew(url string, opts ...func(*types.Server)) func(*types.Server) string {
|
||||||
|
return func(s *types.Server) string {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(s)
|
||||||
|
}
|
||||||
|
s.URL = url
|
||||||
|
return provider.Normalize(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLBMethod is a helper to create a configuration
|
||||||
|
func WithLBMethod(method string) func(*types.Backend) {
|
||||||
|
return func(b *types.Backend) {
|
||||||
|
if b.LoadBalancer == nil {
|
||||||
|
b.LoadBalancer = &types.LoadBalancer{}
|
||||||
|
}
|
||||||
|
b.LoadBalancer.Method = method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Frontend
|
||||||
|
|
||||||
|
// WithFrontends is a helper to create a configuration
|
||||||
|
func WithFrontends(opts ...func(*types.Frontend) string) func(*types.Configuration) {
|
||||||
|
return func(c *types.Configuration) {
|
||||||
|
c.Frontends = make(map[string]*types.Frontend)
|
||||||
|
for _, opt := range opts {
|
||||||
|
f := &types.Frontend{}
|
||||||
|
name := opt(f)
|
||||||
|
c.Frontends[name] = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFrontend is a helper to create a configuration
|
||||||
|
func WithFrontend(backend string, opts ...func(*types.Frontend)) func(*types.Frontend) string {
|
||||||
|
return func(f *types.Frontend) string {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(f)
|
||||||
|
}
|
||||||
|
f.Backend = backend
|
||||||
|
return backend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEntryPoints is a helper to create a configuration
|
||||||
|
func WithEntryPoints(eps ...string) func(*types.Frontend) {
|
||||||
|
return func(f *types.Frontend) {
|
||||||
|
f.EntryPoints = eps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRoutes is a helper to create a configuration
|
||||||
|
func WithRoutes(opts ...func(*types.Route) string) func(*types.Frontend) {
|
||||||
|
return func(f *types.Frontend) {
|
||||||
|
f.Routes = make(map[string]types.Route)
|
||||||
|
for _, opt := range opts {
|
||||||
|
s := &types.Route{}
|
||||||
|
name := opt(s)
|
||||||
|
f.Routes[name] = *s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRoute is a helper to create a configuration
|
||||||
|
func WithRoute(name string, rule string) func(*types.Route) string {
|
||||||
|
return func(r *types.Route) string {
|
||||||
|
r.Rule = rule
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLBSticky is a helper to create a configuration
|
||||||
|
func WithLBSticky(cookieName string) func(*types.Backend) {
|
||||||
|
return func(b *types.Backend) {
|
||||||
|
if b.LoadBalancer == nil {
|
||||||
|
b.LoadBalancer = &types.LoadBalancer{}
|
||||||
|
}
|
||||||
|
b.LoadBalancer.Stickiness = &types.Stickiness{CookieName: cookieName}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,16 +7,6 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Intp returns a pointer to the given integer value.
|
|
||||||
func Intp(i int) *int {
|
|
||||||
return &i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stringp returns a pointer to the given string value.
|
|
||||||
func Stringp(s string) *string {
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustNewRequest creates a new http get request or panics if it can't
|
// MustNewRequest creates a new http get request or panics if it can't
|
||||||
func MustNewRequest(method, urlStr string, body io.Reader) *http.Request {
|
func MustNewRequest(method, urlStr string, body io.Reader) *http.Request {
|
||||||
request, err := http.NewRequest(method, urlStr, body)
|
request, err := http.NewRequest(method, urlStr, body)
|
||||||
|
|
|
@ -46,12 +46,12 @@ type CollectingHealthCheckMetrics struct {
|
||||||
Gauge *CollectingGauge
|
Gauge *CollectingGauge
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCollectingHealthCheckMetrics creates a new CollectingHealthCheckMetrics instance.
|
|
||||||
func NewCollectingHealthCheckMetrics() *CollectingHealthCheckMetrics {
|
|
||||||
return &CollectingHealthCheckMetrics{&CollectingGauge{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackendServerUpGauge is there to satisfy the healthcheck.metricsRegistry interface.
|
// BackendServerUpGauge is there to satisfy the healthcheck.metricsRegistry interface.
|
||||||
func (m *CollectingHealthCheckMetrics) BackendServerUpGauge() metrics.Gauge {
|
func (m *CollectingHealthCheckMetrics) BackendServerUpGauge() metrics.Gauge {
|
||||||
return m.Gauge
|
return m.Gauge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCollectingHealthCheckMetrics creates a new CollectingHealthCheckMetrics instance.
|
||||||
|
func NewCollectingHealthCheckMetrics() *CollectingHealthCheckMetrics {
|
||||||
|
return &CollectingHealthCheckMetrics{&CollectingGauge{}}
|
||||||
|
}
|
||||||
|
|
2
vendor/github.com/dnsimple/dnsimple-go/LICENSE.txt
generated
vendored
2
vendor/github.com/dnsimple/dnsimple-go/LICENSE.txt
generated
vendored
|
@ -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
|
||||||
|
|
5
vendor/github.com/dnsimple/dnsimple-go/dnsimple/accounts.go
generated
vendored
5
vendor/github.com/dnsimple/dnsimple-go/dnsimple/accounts.go
generated
vendored
|
@ -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"`
|
||||||
|
|
159
vendor/github.com/dnsimple/dnsimple-go/dnsimple/certificates.go
generated
vendored
159
vendor/github.com/dnsimple/dnsimple-go/dnsimple/certificates.go
generated
vendored
|
@ -2,25 +2,27 @@ 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"`
|
||||||
|
ContactID int64 `json:"contact_id,omitempty"`
|
||||||
CommonName string `json:"common_name,omitempty"`
|
CommonName string `json:"common_name,omitempty"`
|
||||||
|
AlternateNames []string `json:"alternate_names,omitempty"`
|
||||||
Years int `json:"years,omitempty"`
|
Years int `json:"years,omitempty"`
|
||||||
State string `json:"state,omitempty"`
|
State string `json:"state,omitempty"`
|
||||||
AuthorityIdentifier string `json:"authority_identifier,omitempty"`
|
AuthorityIdentifier string `json:"authority_identifier,omitempty"`
|
||||||
|
AutoRenew bool `json:"auto_renew"`
|
||||||
CreatedAt string `json:"created_at,omitempty"`
|
CreatedAt string `json:"created_at,omitempty"`
|
||||||
UpdatedAt string `json:"updated_at,omitempty"`
|
UpdatedAt string `json:"updated_at,omitempty"`
|
||||||
ExpiresOn string `json:"expires_on,omitempty"`
|
ExpiresOn string `json:"expires_on,omitempty"`
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
12
vendor/github.com/dnsimple/dnsimple-go/dnsimple/contacts.go
generated
vendored
12
vendor/github.com/dnsimple/dnsimple-go/dnsimple/contacts.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
2
vendor/github.com/dnsimple/dnsimple-go/dnsimple/dnsimple.go
generated
vendored
2
vendor/github.com/dnsimple/dnsimple-go/dnsimple/dnsimple.go
generated
vendored
|
@ -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"
|
||||||
|
|
6
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains.go
generated
vendored
6
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains.go
generated
vendored
|
@ -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"`
|
||||||
|
|
18
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_collaborators.go
generated
vendored
18
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_collaborators.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
12
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_delegation_signer_records.go
generated
vendored
12
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_delegation_signer_records.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_dnssec.go
generated
vendored
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_dnssec.go
generated
vendored
|
@ -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 {
|
||||||
|
|
14
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_email_forwards.go
generated
vendored
14
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_email_forwards.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
20
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_pushes.go
generated
vendored
20
vendor/github.com/dnsimple/dnsimple-go/dnsimple/domains_pushes.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
13
vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar.go
generated
vendored
13
vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar.go
generated
vendored
|
@ -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.
|
||||||
|
@ -223,7 +225,6 @@ type DomainRenewal struct {
|
||||||
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"`
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
|
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar_whois_privacy.go
generated
vendored
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/registrar_whois_privacy.go
generated
vendored
|
@ -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"`
|
||||||
|
|
8
vendor/github.com/dnsimple/dnsimple-go/dnsimple/services.go
generated
vendored
8
vendor/github.com/dnsimple/dnsimple-go/dnsimple/services.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
18
vendor/github.com/dnsimple/dnsimple-go/dnsimple/services_domains.go
generated
vendored
18
vendor/github.com/dnsimple/dnsimple-go/dnsimple/services_domains.go
generated
vendored
|
@ -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)
|
||||||
|
|
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates.go
generated
vendored
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates.go
generated
vendored
|
@ -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"`
|
||||||
|
|
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_domains.go
generated
vendored
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_domains.go
generated
vendored
|
@ -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)
|
||||||
|
|
16
vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_records.go
generated
vendored
16
vendor/github.com/dnsimple/dnsimple-go/dnsimple/templates_records.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
2
vendor/github.com/dnsimple/dnsimple-go/dnsimple/users.go
generated
vendored
2
vendor/github.com/dnsimple/dnsimple-go/dnsimple/users.go
generated
vendored
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
14
vendor/github.com/dnsimple/dnsimple-go/dnsimple/vanity_name_server.go
generated
vendored
14
vendor/github.com/dnsimple/dnsimple-go/dnsimple/vanity_name_server.go
generated
vendored
|
@ -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)
|
||||||
|
|
8
vendor/github.com/dnsimple/dnsimple-go/dnsimple/webhooks.go
generated
vendored
8
vendor/github.com/dnsimple/dnsimple-go/dnsimple/webhooks.go
generated
vendored
|
@ -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{}
|
||||||
|
|
||||||
|
|
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones.go
generated
vendored
4
vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones.go
generated
vendored
|
@ -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"`
|
||||||
|
|
42
vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones_records.go
generated
vendored
42
vendor/github.com/dnsimple/dnsimple-go/dnsimple/zones_records.go
generated
vendored
|
@ -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)
|
||||||
|
|
17
vendor/github.com/ovh/go-ovh/ovh/configuration.go
generated
vendored
17
vendor/github.com/ovh/go-ovh/ovh/configuration.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
return usr.HomeDir, nil
|
} else {
|
||||||
|
userHome = usr.HomeDir
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
131
vendor/github.com/ovh/go-ovh/ovh/ovh.go
generated
vendored
131
vendor/github.com/ovh/go-ovh/ovh/ovh.go
generated
vendored
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/buffer/buffer.go
generated
vendored
2
vendor/github.com/vulcand/oxy/buffer/buffer.go
generated
vendored
|
@ -199,7 +199,7 @@ func (b *Buffer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/buffer: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/buffer: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/buffer: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/buffer: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := b.checkLimit(req); err != nil {
|
if err := b.checkLimit(req); err != nil {
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go
generated
vendored
2
vendor/github.com/vulcand/oxy/cbreaker/cbreaker.go
generated
vendored
|
@ -103,7 +103,7 @@ func (c *CircuitBreaker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/circuitbreaker: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/circuitbreaker: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/circuitbreaker: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/circuitbreaker: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
if c.activateFallback(w, req) {
|
if c.activateFallback(w, req) {
|
||||||
c.fallback.ServeHTTP(w, req)
|
c.fallback.ServeHTTP(w, req)
|
||||||
|
|
4
vendor/github.com/vulcand/oxy/cbreaker/fallback.go
generated
vendored
4
vendor/github.com/vulcand/oxy/cbreaker/fallback.go
generated
vendored
|
@ -31,7 +31,7 @@ func (f *ResponseFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/fallback/response: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/fallback/response: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/fallback/response: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/fallback/response: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.r.ContentType != "" {
|
if f.r.ContentType != "" {
|
||||||
|
@ -67,7 +67,7 @@ func (f *RedirectFallback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/fallback/redirect: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/fallback/redirect: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/fallback/redirect: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/fallback/redirect: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
location := f.u.String()
|
location := f.u.String()
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/connlimit/connlimit.go
generated
vendored
2
vendor/github.com/vulcand/oxy/connlimit/connlimit.go
generated
vendored
|
@ -110,7 +110,7 @@ func (e *ConnErrHandler) ServeHTTP(w http.ResponseWriter, req *http.Request, err
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/connlimit: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/connlimit: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/connlimit: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/connlimit: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := err.(*MaxConnError); ok {
|
if _, ok := err.(*MaxConnError); ok {
|
||||||
|
|
12
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
12
vendor/github.com/vulcand/oxy/forward/fwd.go
generated
vendored
|
@ -302,7 +302,7 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
|
||||||
if f.log.GetLevel() >= log.DebugLevel {
|
if f.log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := f.log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/forward/websocket: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/forward/websocket: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/forward/websocket: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/forward/websocket: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
outReq := f.copyWebSocketRequest(req)
|
outReq := f.copyWebSocketRequest(req)
|
||||||
|
@ -351,6 +351,7 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
|
utils.RemoveHeaders(resp.Header, WebsocketUpgradeHeaders...)
|
||||||
|
utils.CopyHeaders(resp.Header, w.Header())
|
||||||
|
|
||||||
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
|
underlyingConn, err := upgrader.Upgrade(w, req, resp.Header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -370,11 +371,20 @@ func (f *httpForwarder) serveWebSocket(w http.ResponseWriter, req *http.Request,
|
||||||
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
|
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
|
||||||
if e, ok := err.(*websocket.CloseError); ok {
|
if e, ok := err.(*websocket.CloseError); ok {
|
||||||
if e.Code != websocket.CloseNoStatusReceived {
|
if e.Code != websocket.CloseNoStatusReceived {
|
||||||
|
m = nil
|
||||||
|
// Following codes are not valid on the wire so just close the
|
||||||
|
// underlying TCP connection without sending a close frame.
|
||||||
|
if e.Code != websocket.CloseAbnormalClosure &&
|
||||||
|
e.Code != websocket.CloseTLSHandshake {
|
||||||
|
|
||||||
m = websocket.FormatCloseMessage(e.Code, e.Text)
|
m = websocket.FormatCloseMessage(e.Code, e.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
errc <- err
|
errc <- err
|
||||||
|
if m != nil {
|
||||||
dst.WriteMessage(websocket.CloseMessage, m)
|
dst.WriteMessage(websocket.CloseMessage, m)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = dst.WriteMessage(msgType, msg)
|
err = dst.WriteMessage(msgType, msg)
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go
generated
vendored
2
vendor/github.com/vulcand/oxy/roundrobin/rebalancer.go
generated
vendored
|
@ -145,7 +145,7 @@ func (rb *Rebalancer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/roundrobin/rebalancer: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw := utils.NewSimpleProxyWriter(w)
|
pw := utils.NewSimpleProxyWriter(w)
|
||||||
|
|
2
vendor/github.com/vulcand/oxy/roundrobin/rr.go
generated
vendored
2
vendor/github.com/vulcand/oxy/roundrobin/rr.go
generated
vendored
|
@ -84,7 +84,7 @@ func (r *RoundRobin) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if log.GetLevel() >= log.DebugLevel {
|
if log.GetLevel() >= log.DebugLevel {
|
||||||
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
logEntry := log.WithField("Request", utils.DumpHttpRequest(req))
|
||||||
logEntry.Debug("vulcand/oxy/roundrobin/rr: begin ServeHttp on request")
|
logEntry.Debug("vulcand/oxy/roundrobin/rr: begin ServeHttp on request")
|
||||||
defer logEntry.Debug("vulcand/oxy/roundrobin/rr: competed ServeHttp on request")
|
defer logEntry.Debug("vulcand/oxy/roundrobin/rr: completed ServeHttp on request")
|
||||||
}
|
}
|
||||||
|
|
||||||
// make shallow copy of request before chaning anything to avoid side effects
|
// make shallow copy of request before chaning anything to avoid side effects
|
||||||
|
|
|
@ -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")
|
||||||
)
|
)
|
|
@ -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,14 @@ 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 "processing":
|
||||||
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"))
|
|
@ -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))}
|
||||||
}
|
}
|
|
@ -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) {
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package acmev2
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -155,6 +155,6 @@ func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, e
|
||||||
|
|
||||||
// userAgent builds and returns the User-Agent string to use in requests.
|
// userAgent builds and returns the User-Agent string to use in requests.
|
||||||
func userAgent() string {
|
func userAgent() string {
|
||||||
ua := fmt.Sprintf("%s (%s; %s) %s %s", defaultGoUserAgent, runtime.GOOS, runtime.GOARCH, ourUserAgent, UserAgent)
|
ua := fmt.Sprintf("%s %s (%s; %s) %s", UserAgent, ourUserAgent, runtime.GOOS, runtime.GOARCH, defaultGoUserAgent)
|
||||||
return strings.TrimSpace(ua)
|
return strings.TrimSpace(ua)
|
||||||
}
|
}
|
|
@ -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)
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
})
|
})
|
43
vendor/github.com/xenolf/lego/acmev2/jws.go → vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
43
vendor/github.com/xenolf/lego/acmev2/jws.go → vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
|
@ -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
|
|
@ -1,6 +1,7 @@
|
||||||
package acmev2
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ type accountMessage struct {
|
||||||
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"`
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package acmev2
|
package acme
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package acmev2
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
1
vendor/github.com/xenolf/lego/acmev2/pop_challenge.go
generated
vendored
1
vendor/github.com/xenolf/lego/acmev2/pop_challenge.go
generated
vendored
|
@ -1 +0,0 @@
|
||||||
package acmev2
|
|
59
vendor/github.com/xenolf/lego/log/logger.go
generated
vendored
Normal file
59
vendor/github.com/xenolf/lego/log/logger.go
generated
vendored
Normal 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...)
|
||||||
|
}
|
25
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
25
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
|
@ -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 {
|
||||||
|
|
86
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
86
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
|
@ -15,17 +15,16 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,26 +32,32 @@ type DNSProvider struct {
|
||||||
// Credentials must be passed in the environment variables: AZURE_CLIENT_ID,
|
// Credentials must be passed in the environment variables: AZURE_CLIENT_ID,
|
||||||
// AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
|
// AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
|
||||||
func NewDNSProvider() (*DNSProvider, error) {
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
clientId := os.Getenv("AZURE_CLIENT_ID")
|
clientID := os.Getenv("AZURE_CLIENT_ID")
|
||||||
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
clientSecret := os.Getenv("AZURE_CLIENT_SECRET")
|
||||||
subscriptionId := os.Getenv("AZURE_SUBSCRIPTION_ID")
|
subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID")
|
||||||
tenantId := os.Getenv("AZURE_TENANT_ID")
|
tenantID := os.Getenv("AZURE_TENANT_ID")
|
||||||
resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP")
|
resourceGroup := os.Getenv("AZURE_RESOURCE_GROUP")
|
||||||
return NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup)
|
return NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||||
// DNSProvider instance configured for azure.
|
// DNSProvider instance configured for azure.
|
||||||
func NewDNSProviderCredentials(clientId, clientSecret, subscriptionId, tenantId, resourceGroup string) (*DNSProvider, error) {
|
func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup string) (*DNSProvider, error) {
|
||||||
if clientId == "" || clientSecret == "" || subscriptionId == "" || tenantId == "" || resourceGroup == "" {
|
if clientID == "" || clientSecret == "" || subscriptionID == "" || tenantID == "" || resourceGroup == "" {
|
||||||
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, "", "")
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
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)
|
||||||
}
|
}
|
||||||
|
|
98
vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go
generated
vendored
98
vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go
generated
vendored
|
@ -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,7 +160,8 @@ 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)
|
authBytes, _ := ioutil.ReadAll(resp.Body)
|
||||||
authResp := string(authBytes)
|
authResp := string(authBytes)
|
||||||
|
|
||||||
|
@ -168,7 +169,6 @@ func (d *DNSProvider) logout() error {
|
||||||
msg := strings.Trim(authResp, "\"")
|
msg := strings.Trim(authResp, "\"")
|
||||||
return fmt.Errorf("Bluecat API request failed to delete session: %s", msg)
|
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"`
|
||||||
|
|
46
vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
46
vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
|
@ -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
|
||||||
|
|
24
vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
24
vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
64
vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
64
vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
16
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
16
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
31
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
31
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
18
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
18
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
|
@ -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 {
|
||||||
|
|
14
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
14
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
9
vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go
generated
vendored
9
vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go
generated
vendored
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
39
vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go
generated
vendored
39
vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go
generated
vendored
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
6
vendor/github.com/xenolf/lego/providers/dns/exec/exec.go
generated
vendored
6
vendor/github.com/xenolf/lego/providers/dns/exec/exec.go
generated
vendored
|
@ -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
|
||||||
|
|
28
vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
generated
vendored
28
vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
generated
vendored
|
@ -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
|
||||||
|
|
14
vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
generated
vendored
14
vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
generated
vendored
|
@ -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
|
||||||
|
|
53
vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go
generated
vendored
53
vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go
generated
vendored
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
33
vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
33
vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
60
vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go
generated
vendored
60
vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go
generated
vendored
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
21
vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go
generated
vendored
21
vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go
generated
vendored
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue