Migrate to go-acme/lego.
This commit is contained in:
parent
4a68d29ce2
commit
87da7520de
286 changed files with 14021 additions and 2501 deletions
21
vendor/github.com/go-acme/lego/LICENSE
generated
vendored
Normal file
21
vendor/github.com/go-acme/lego/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2017 Sebastian Erhart
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
69
vendor/github.com/go-acme/lego/acme/api/account.go
generated
vendored
Normal file
69
vendor/github.com/go-acme/lego/acme/api/account.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
)
|
||||
|
||||
type AccountService service
|
||||
|
||||
// New Creates a new account.
|
||||
func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
||||
var account acme.Account
|
||||
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
||||
location := getLocation(resp)
|
||||
|
||||
if len(location) > 0 {
|
||||
a.core.jws.SetKid(location)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return acme.ExtendedAccount{Location: location}, err
|
||||
}
|
||||
|
||||
return acme.ExtendedAccount{Account: account, Location: location}, nil
|
||||
}
|
||||
|
||||
// NewEAB Creates a new account with an External Account Binding.
|
||||
func (a *AccountService) NewEAB(accMsg acme.Account, kid string, hmacEncoded string) (acme.ExtendedAccount, error) {
|
||||
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
||||
if err != nil {
|
||||
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %v", err)
|
||||
}
|
||||
|
||||
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
|
||||
if err != nil {
|
||||
return acme.ExtendedAccount{}, fmt.Errorf("acme: error signing eab content: %v", err)
|
||||
}
|
||||
accMsg.ExternalAccountBinding = eabJWS
|
||||
|
||||
return a.New(accMsg)
|
||||
}
|
||||
|
||||
// Get Retrieves an account.
|
||||
func (a *AccountService) Get(accountURL string) (acme.Account, error) {
|
||||
if len(accountURL) == 0 {
|
||||
return acme.Account{}, errors.New("account[get]: empty URL")
|
||||
}
|
||||
|
||||
var account acme.Account
|
||||
_, err := a.core.post(accountURL, acme.Account{}, &account)
|
||||
if err != nil {
|
||||
return acme.Account{}, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Deactivate Deactivates an account.
|
||||
func (a *AccountService) Deactivate(accountURL string) error {
|
||||
if len(accountURL) == 0 {
|
||||
return errors.New("account[deactivate]: empty URL")
|
||||
}
|
||||
|
||||
req := acme.Account{Status: acme.StatusDeactivated}
|
||||
_, err := a.core.post(accountURL, req, nil)
|
||||
return err
|
||||
}
|
166
vendor/github.com/go-acme/lego/acme/api/api.go
generated
vendored
Normal file
166
vendor/github.com/go-acme/lego/acme/api/api.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api/internal/nonces"
|
||||
"github.com/go-acme/lego/acme/api/internal/secure"
|
||||
"github.com/go-acme/lego/acme/api/internal/sender"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// Core ACME/LE core API.
|
||||
type Core struct {
|
||||
doer *sender.Doer
|
||||
nonceManager *nonces.Manager
|
||||
jws *secure.JWS
|
||||
directory acme.Directory
|
||||
HTTPClient *http.Client
|
||||
|
||||
common service // Reuse a single struct instead of allocating one for each service on the heap.
|
||||
Accounts *AccountService
|
||||
Authorizations *AuthorizationService
|
||||
Certificates *CertificateService
|
||||
Challenges *ChallengeService
|
||||
Orders *OrderService
|
||||
}
|
||||
|
||||
// New Creates a new Core.
|
||||
func New(httpClient *http.Client, userAgent string, caDirURL, kid string, privateKey crypto.PrivateKey) (*Core, error) {
|
||||
doer := sender.NewDoer(httpClient, userAgent)
|
||||
|
||||
dir, err := getDirectory(doer, caDirURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonceManager := nonces.NewManager(doer, dir.NewNonceURL)
|
||||
|
||||
jws := secure.NewJWS(privateKey, kid, nonceManager)
|
||||
|
||||
c := &Core{doer: doer, nonceManager: nonceManager, jws: jws, directory: dir, HTTPClient: httpClient}
|
||||
|
||||
c.common.core = c
|
||||
c.Accounts = (*AccountService)(&c.common)
|
||||
c.Authorizations = (*AuthorizationService)(&c.common)
|
||||
c.Certificates = (*CertificateService)(&c.common)
|
||||
c.Challenges = (*ChallengeService)(&c.common)
|
||||
c.Orders = (*OrderService)(&c.common)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// post performs an HTTP POST request and parses the response body as JSON,
|
||||
// into the provided respBody object.
|
||||
func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, error) {
|
||||
content, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to marshal message")
|
||||
}
|
||||
|
||||
return a.retrievablePost(uri, content, response)
|
||||
}
|
||||
|
||||
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.3
|
||||
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
|
||||
return a.retrievablePost(uri, []byte{}, response)
|
||||
}
|
||||
|
||||
func (a *Core) retrievablePost(uri string, content []byte, response interface{}) (*http.Response, error) {
|
||||
// during tests, allow to support ~90% of bad nonce with a minimum of attempts.
|
||||
bo := backoff.NewExponentialBackOff()
|
||||
bo.InitialInterval = 200 * time.Millisecond
|
||||
bo.MaxInterval = 5 * time.Second
|
||||
bo.MaxElapsedTime = 20 * time.Second
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
var resp *http.Response
|
||||
operation := func() error {
|
||||
var err error
|
||||
resp, err = a.signedPost(uri, content, response)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
// Retry if the nonce was invalidated
|
||||
case *acme.NonceError:
|
||||
log.Infof("nonce error retry: %s", err)
|
||||
return err
|
||||
default:
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) {
|
||||
signedContent, err := a.jws.SignContent(uri, content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to post JWS message -> failed to sign content -> %v", err)
|
||||
}
|
||||
|
||||
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
||||
|
||||
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
|
||||
|
||||
// nonceErr is ignored to keep the root error.
|
||||
nonce, nonceErr := nonces.GetFromResponse(resp)
|
||||
if nonceErr == nil {
|
||||
a.nonceManager.Push(nonce)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (a *Core) signEABContent(newAccountURL, kid string, hmac []byte) ([]byte, error) {
|
||||
eabJWS, err := a.jws.SignEABContent(newAccountURL, kid, hmac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(eabJWS.FullSerialize()), nil
|
||||
}
|
||||
|
||||
// GetKeyAuthorization Gets the key authorization
|
||||
func (a *Core) GetKeyAuthorization(token string) (string, error) {
|
||||
return a.jws.GetKeyAuthorization(token)
|
||||
}
|
||||
|
||||
func (a *Core) GetDirectory() acme.Directory {
|
||||
return a.directory
|
||||
}
|
||||
|
||||
func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) {
|
||||
var dir acme.Directory
|
||||
if _, err := do.Get(caDirURL, &dir); err != nil {
|
||||
return dir, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||
}
|
||||
|
||||
if dir.NewAccountURL == "" {
|
||||
return dir, errors.New("directory missing new registration URL")
|
||||
}
|
||||
if dir.NewOrderURL == "" {
|
||||
return dir, errors.New("directory missing new order URL")
|
||||
}
|
||||
|
||||
return dir, nil
|
||||
}
|
34
vendor/github.com/go-acme/lego/acme/api/authorization.go
generated
vendored
Normal file
34
vendor/github.com/go-acme/lego/acme/api/authorization.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
)
|
||||
|
||||
type AuthorizationService service
|
||||
|
||||
// Get Gets an authorization.
|
||||
func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error) {
|
||||
if len(authzURL) == 0 {
|
||||
return acme.Authorization{}, errors.New("authorization[get]: empty URL")
|
||||
}
|
||||
|
||||
var authz acme.Authorization
|
||||
_, err := c.core.postAsGet(authzURL, &authz)
|
||||
if err != nil {
|
||||
return acme.Authorization{}, err
|
||||
}
|
||||
return authz, nil
|
||||
}
|
||||
|
||||
// Deactivate Deactivates an authorization.
|
||||
func (c *AuthorizationService) Deactivate(authzURL string) error {
|
||||
if len(authzURL) == 0 {
|
||||
return errors.New("authorization[deactivate]: empty URL")
|
||||
}
|
||||
|
||||
var disabledAuth acme.Authorization
|
||||
_, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth)
|
||||
return err
|
||||
}
|
99
vendor/github.com/go-acme/lego/acme/api/certificate.go
generated
vendored
Normal file
99
vendor/github.com/go-acme/lego/acme/api/certificate.go
generated
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// maxBodySize is the maximum size of body that we will read.
|
||||
const maxBodySize = 1024 * 1024
|
||||
|
||||
type CertificateService service
|
||||
|
||||
// Get Returns the certificate and the issuer certificate.
|
||||
// 'bundle' is only applied if the issuer is provided by the 'up' link.
|
||||
func (c *CertificateService) Get(certURL string, bundle bool) ([]byte, []byte, error) {
|
||||
cert, up, err := c.get(certURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Get issuerCert from bundled response from Let's Encrypt
|
||||
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
||||
_, issuer := pem.Decode(cert)
|
||||
if issuer != nil {
|
||||
return cert, issuer, nil
|
||||
}
|
||||
|
||||
issuer, err = c.getIssuerFromLink(up)
|
||||
if err != nil {
|
||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||
log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err)
|
||||
} else if len(issuer) > 0 {
|
||||
// If bundle is true, we want to return a certificate bundle.
|
||||
// To do this, we append the issuer cert to the issued cert.
|
||||
if bundle {
|
||||
cert = append(cert, issuer...)
|
||||
}
|
||||
}
|
||||
|
||||
return cert, issuer, nil
|
||||
}
|
||||
|
||||
// Revoke Revokes a certificate.
|
||||
func (c *CertificateService) Revoke(req acme.RevokeCertMessage) error {
|
||||
_, err := c.core.post(c.core.GetDirectory().RevokeCertURL, req, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// get Returns the certificate and the "up" link.
|
||||
func (c *CertificateService) get(certURL string) ([]byte, string, error) {
|
||||
if len(certURL) == 0 {
|
||||
return nil, "", errors.New("certificate[get]: empty URL")
|
||||
}
|
||||
|
||||
resp, err := c.core.postAsGet(certURL, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
cert, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// The issuer certificate link may be supplied via an "up" link
|
||||
// in the response headers of a new certificate.
|
||||
// See https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4.2
|
||||
up := getLink(resp.Header, "up")
|
||||
|
||||
return cert, up, err
|
||||
}
|
||||
|
||||
// getIssuerFromLink requests the issuer certificate
|
||||
func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) {
|
||||
if len(up) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Infof("acme: Requesting issuer cert from %s", up)
|
||||
|
||||
cert, _, err := c.get(up)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert)), nil
|
||||
}
|
45
vendor/github.com/go-acme/lego/acme/api/challenge.go
generated
vendored
Normal file
45
vendor/github.com/go-acme/lego/acme/api/challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
)
|
||||
|
||||
type ChallengeService service
|
||||
|
||||
// New Creates a challenge.
|
||||
func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||
if len(chlgURL) == 0 {
|
||||
return acme.ExtendedChallenge{}, errors.New("challenge[new]: empty URL")
|
||||
}
|
||||
|
||||
// Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`.
|
||||
// We use an empty struct instance as the postJSON payload here to achieve this result.
|
||||
var chlng acme.ExtendedChallenge
|
||||
resp, err := c.core.post(chlgURL, struct{}{}, &chlng)
|
||||
if err != nil {
|
||||
return acme.ExtendedChallenge{}, err
|
||||
}
|
||||
|
||||
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
||||
chlng.RetryAfter = getRetryAfter(resp)
|
||||
return chlng, nil
|
||||
}
|
||||
|
||||
// Get Gets a challenge.
|
||||
func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||
if len(chlgURL) == 0 {
|
||||
return acme.ExtendedChallenge{}, errors.New("challenge[get]: empty URL")
|
||||
}
|
||||
|
||||
var chlng acme.ExtendedChallenge
|
||||
resp, err := c.core.postAsGet(chlgURL, &chlng)
|
||||
if err != nil {
|
||||
return acme.ExtendedChallenge{}, err
|
||||
}
|
||||
|
||||
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
||||
chlng.RetryAfter = getRetryAfter(resp)
|
||||
return chlng, nil
|
||||
}
|
78
vendor/github.com/go-acme/lego/acme/api/internal/nonces/nonce_manager.go
generated
vendored
Normal file
78
vendor/github.com/go-acme/lego/acme/api/internal/nonces/nonce_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
package nonces
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/go-acme/lego/acme/api/internal/sender"
|
||||
)
|
||||
|
||||
// Manager Manages nonces.
|
||||
type Manager struct {
|
||||
do *sender.Doer
|
||||
nonceURL string
|
||||
nonces []string
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewManager Creates a new Manager.
|
||||
func NewManager(do *sender.Doer, nonceURL string) *Manager {
|
||||
return &Manager{
|
||||
do: do,
|
||||
nonceURL: nonceURL,
|
||||
}
|
||||
}
|
||||
|
||||
// Pop Pops a nonce.
|
||||
func (n *Manager) Pop() (string, bool) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
if len(n.nonces) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
nonce := n.nonces[len(n.nonces)-1]
|
||||
n.nonces = n.nonces[:len(n.nonces)-1]
|
||||
return nonce, true
|
||||
}
|
||||
|
||||
// Push Pushes a nonce.
|
||||
func (n *Manager) Push(nonce string) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
n.nonces = append(n.nonces, nonce)
|
||||
}
|
||||
|
||||
// Nonce implement jose.NonceSource
|
||||
func (n *Manager) Nonce() (string, error) {
|
||||
if nonce, ok := n.Pop(); ok {
|
||||
return nonce, nil
|
||||
}
|
||||
return n.getNonce()
|
||||
}
|
||||
|
||||
func (n *Manager) getNonce() (string, error) {
|
||||
resp, err := n.do.Head(n.nonceURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %v", err)
|
||||
}
|
||||
|
||||
return GetFromResponse(resp)
|
||||
}
|
||||
|
||||
// GetFromResponse Extracts a nonce from a HTTP response.
|
||||
func GetFromResponse(resp *http.Response) (string, error) {
|
||||
if resp == nil {
|
||||
return "", errors.New("nil response")
|
||||
}
|
||||
|
||||
nonce := resp.Header.Get("Replay-Nonce")
|
||||
if nonce == "" {
|
||||
return "", fmt.Errorf("server did not respond with a proper nonce header")
|
||||
}
|
||||
|
||||
return nonce, nil
|
||||
}
|
134
vendor/github.com/go-acme/lego/acme/api/internal/secure/jws.go
generated
vendored
Normal file
134
vendor/github.com/go-acme/lego/acme/api/internal/secure/jws.go
generated
vendored
Normal file
|
@ -0,0 +1,134 @@
|
|||
package secure
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme/api/internal/nonces"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// JWS Represents a JWS.
|
||||
type JWS struct {
|
||||
privKey crypto.PrivateKey
|
||||
kid string // Key identifier
|
||||
nonces *nonces.Manager
|
||||
}
|
||||
|
||||
// NewJWS Create a new JWS.
|
||||
func NewJWS(privateKey crypto.PrivateKey, kid string, nonceManager *nonces.Manager) *JWS {
|
||||
return &JWS{
|
||||
privKey: privateKey,
|
||||
nonces: nonceManager,
|
||||
kid: kid,
|
||||
}
|
||||
}
|
||||
|
||||
// SetKid Sets a key identifier.
|
||||
func (j *JWS) SetKid(kid string) {
|
||||
j.kid = kid
|
||||
}
|
||||
|
||||
// SignContent Signs a content with the JWS.
|
||||
func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
|
||||
var alg jose.SignatureAlgorithm
|
||||
switch k := j.privKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
alg = jose.RS256
|
||||
case *ecdsa.PrivateKey:
|
||||
if k.Curve == elliptic.P256() {
|
||||
alg = jose.ES256
|
||||
} else if k.Curve == elliptic.P384() {
|
||||
alg = jose.ES384
|
||||
}
|
||||
}
|
||||
|
||||
signKey := jose.SigningKey{
|
||||
Algorithm: alg,
|
||||
Key: jose.JSONWebKey{Key: j.privKey, KeyID: j.kid},
|
||||
}
|
||||
|
||||
options := jose.SignerOptions{
|
||||
NonceSource: j.nonces,
|
||||
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||
"url": url,
|
||||
},
|
||||
}
|
||||
|
||||
if j.kid == "" {
|
||||
options.EmbedJWK = true
|
||||
}
|
||||
|
||||
signer, err := jose.NewSigner(signKey, &options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create jose signer -> %v", err)
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign content -> %v", err)
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
// SignEABContent Signs an external account binding content with the JWS.
|
||||
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: %v", err)
|
||||
}
|
||||
|
||||
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 -> %v", err)
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(jwkJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to External Account Binding sign content -> %v", err)
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
// GetKeyAuthorization Gets the key authorization for a token.
|
||||
func (j *JWS) GetKeyAuthorization(token string) (string, error) {
|
||||
var publicKey crypto.PublicKey
|
||||
switch k := j.privKey.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
publicKey = k.Public()
|
||||
case *rsa.PrivateKey:
|
||||
publicKey = k.Public()
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
jwk := &jose.JSONWebKey{Key: publicKey}
|
||||
if jwk == nil {
|
||||
return "", errors.New("could not generate JWK from key")
|
||||
}
|
||||
|
||||
thumbBytes, err := jwk.Thumbprint(crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// unpad the base64URL
|
||||
keyThumb := base64.RawURLEncoding.EncodeToString(thumbBytes)
|
||||
|
||||
return token + "." + keyThumb, nil
|
||||
}
|
146
vendor/github.com/go-acme/lego/acme/api/internal/sender/sender.go
generated
vendored
Normal file
146
vendor/github.com/go-acme/lego/acme/api/internal/sender/sender.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package sender
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
)
|
||||
|
||||
type RequestOption func(*http.Request) error
|
||||
|
||||
func contentType(ct string) RequestOption {
|
||||
return func(req *http.Request) error {
|
||||
req.Header.Set("Content-Type", ct)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type Doer struct {
|
||||
httpClient *http.Client
|
||||
userAgent string
|
||||
}
|
||||
|
||||
// NewDoer Creates a new Doer.
|
||||
func NewDoer(client *http.Client, userAgent string) *Doer {
|
||||
return &Doer{
|
||||
httpClient: client,
|
||||
userAgent: userAgent,
|
||||
}
|
||||
}
|
||||
|
||||
// Get performs a GET request with a proper User-Agent string.
|
||||
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
||||
func (d *Doer) Get(url string, response interface{}) (*http.Response, error) {
|
||||
req, err := d.newRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.do(req, response)
|
||||
}
|
||||
|
||||
// Head performs a HEAD request with a proper User-Agent string.
|
||||
// The response body (resp.Body) is already closed when this function returns.
|
||||
func (d *Doer) Head(url string) (*http.Response, error) {
|
||||
req, err := d.newRequest(http.MethodHead, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.do(req, nil)
|
||||
}
|
||||
|
||||
// Post performs a POST request with a proper User-Agent string.
|
||||
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
||||
func (d *Doer) Post(url string, body io.Reader, bodyType string, response interface{}) (*http.Response, error) {
|
||||
req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d.do(req, response)
|
||||
}
|
||||
|
||||
func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOption) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, uri, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", d.formatUserAgent())
|
||||
|
||||
for _, opt := range opts {
|
||||
err = opt(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, error) {
|
||||
resp, err := d.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkError(req, resp); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if response != nil {
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.Unmarshal(raw, response)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("failed to unmarshal %q to type %T: %v", raw, response, err)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// formatUserAgent builds and returns the User-Agent string to use in requests.
|
||||
func (d *Doer) formatUserAgent() string {
|
||||
ua := fmt.Sprintf("%s %s (%s; %s; %s)", d.userAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
|
||||
return strings.TrimSpace(ua)
|
||||
}
|
||||
|
||||
func checkError(req *http.Request, resp *http.Response) error {
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d :: %s :: %s :: %v", resp.StatusCode, req.Method, req.URL, err)
|
||||
}
|
||||
|
||||
var errorDetails *acme.ProblemDetails
|
||||
err = json.Unmarshal(body, &errorDetails)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d ::%s :: %s :: %v :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
|
||||
}
|
||||
|
||||
errorDetails.Method = req.Method
|
||||
errorDetails.URL = req.URL.String()
|
||||
|
||||
// Check for errors we handle specifically
|
||||
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
|
||||
return &acme.NonceError{ProblemDetails: errorDetails}
|
||||
}
|
||||
|
||||
return errorDetails
|
||||
}
|
||||
return nil
|
||||
}
|
14
vendor/github.com/go-acme/lego/acme/api/internal/sender/useragent.go
generated
vendored
Normal file
14
vendor/github.com/go-acme/lego/acme/api/internal/sender/useragent.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
package sender
|
||||
|
||||
// CODE GENERATED AUTOMATICALLY
|
||||
// THIS FILE MUST NOT BE EDITED BY HAND
|
||||
|
||||
const (
|
||||
// ourUserAgent is the User-Agent of this underlying library package.
|
||||
ourUserAgent = "xenolf-acme/2.3.0"
|
||||
|
||||
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
||||
// values: detach|release
|
||||
// NOTE: Update this with each tagged release.
|
||||
ourUserAgentComment = "detach"
|
||||
)
|
65
vendor/github.com/go-acme/lego/acme/api/order.go
generated
vendored
Normal file
65
vendor/github.com/go-acme/lego/acme/api/order.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
)
|
||||
|
||||
type OrderService service
|
||||
|
||||
// New Creates a new order.
|
||||
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
||||
var identifiers []acme.Identifier
|
||||
for _, domain := range domains {
|
||||
identifiers = append(identifiers, acme.Identifier{Type: "dns", Value: domain})
|
||||
}
|
||||
|
||||
orderReq := acme.Order{Identifiers: identifiers}
|
||||
|
||||
var order acme.Order
|
||||
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
||||
if err != nil {
|
||||
return acme.ExtendedOrder{}, err
|
||||
}
|
||||
|
||||
return acme.ExtendedOrder{
|
||||
Location: resp.Header.Get("Location"),
|
||||
Order: order,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get Gets an order.
|
||||
func (o *OrderService) Get(orderURL string) (acme.Order, error) {
|
||||
if len(orderURL) == 0 {
|
||||
return acme.Order{}, errors.New("order[get]: empty URL")
|
||||
}
|
||||
|
||||
var order acme.Order
|
||||
_, err := o.core.postAsGet(orderURL, &order)
|
||||
if err != nil {
|
||||
return acme.Order{}, err
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// UpdateForCSR Updates an order for a CSR.
|
||||
func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.Order, error) {
|
||||
csrMsg := acme.CSRMessage{
|
||||
Csr: base64.RawURLEncoding.EncodeToString(csr),
|
||||
}
|
||||
|
||||
var order acme.Order
|
||||
_, err := o.core.post(orderURL, csrMsg, &order)
|
||||
if err != nil {
|
||||
return acme.Order{}, err
|
||||
}
|
||||
|
||||
if order.Status == acme.StatusInvalid {
|
||||
return acme.Order{}, order.Error
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
45
vendor/github.com/go-acme/lego/acme/api/service.go
generated
vendored
Normal file
45
vendor/github.com/go-acme/lego/acme/api/service.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
core *Core
|
||||
}
|
||||
|
||||
// getLink get a rel into the Link header
|
||||
func getLink(header http.Header, rel string) string {
|
||||
var linkExpr = regexp.MustCompile(`<(.+?)>;\s*rel="(.+?)"`)
|
||||
|
||||
for _, link := range header["Link"] {
|
||||
for _, m := range linkExpr.FindAllStringSubmatch(link, -1) {
|
||||
if len(m) != 3 {
|
||||
continue
|
||||
}
|
||||
if m[2] == rel {
|
||||
return m[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getLocation get the value of the header Location
|
||||
func getLocation(resp *http.Response) string {
|
||||
if resp == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return resp.Header.Get("Location")
|
||||
}
|
||||
|
||||
// getRetryAfter get the value of the header Retry-After
|
||||
func getRetryAfter(resp *http.Response) string {
|
||||
if resp == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return resp.Header.Get("Retry-After")
|
||||
}
|
284
vendor/github.com/go-acme/lego/acme/commons.go
generated
vendored
Normal file
284
vendor/github.com/go-acme/lego/acme/commons.go
generated
vendored
Normal file
|
@ -0,0 +1,284 @@
|
|||
// Package acme contains all objects related the ACME endpoints.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Challenge statuses
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.6
|
||||
const (
|
||||
StatusPending = "pending"
|
||||
StatusInvalid = "invalid"
|
||||
StatusValid = "valid"
|
||||
StatusProcessing = "processing"
|
||||
StatusDeactivated = "deactivated"
|
||||
StatusExpired = "expired"
|
||||
StatusRevoked = "revoked"
|
||||
)
|
||||
|
||||
// Directory the ACME directory object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
|
||||
type Directory struct {
|
||||
NewNonceURL string `json:"newNonce"`
|
||||
NewAccountURL string `json:"newAccount"`
|
||||
NewOrderURL string `json:"newOrder"`
|
||||
NewAuthzURL string `json:"newAuthz"`
|
||||
RevokeCertURL string `json:"revokeCert"`
|
||||
KeyChangeURL string `json:"keyChange"`
|
||||
Meta Meta `json:"meta"`
|
||||
}
|
||||
|
||||
// Meta the ACME meta object (related to Directory).
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.1
|
||||
type Meta struct {
|
||||
// termsOfService (optional, string):
|
||||
// A URL identifying the current terms of service.
|
||||
TermsOfService string `json:"termsOfService"`
|
||||
|
||||
// website (optional, string):
|
||||
// An HTTP or HTTPS URL locating a website providing more information about the ACME server.
|
||||
Website string `json:"website"`
|
||||
|
||||
// caaIdentities (optional, array of string):
|
||||
// The hostnames that the ACME server recognizes as referring to itself
|
||||
// for the purposes of CAA record validation as defined in [RFC6844].
|
||||
// Each string MUST represent the same sequence of ASCII code points
|
||||
// that the server will expect to see as the "Issuer Domain Name" in a CAA issue or issuewild property tag.
|
||||
// This allows clients to determine the correct issuer domain name to use when configuring CAA records.
|
||||
CaaIdentities []string `json:"caaIdentities"`
|
||||
|
||||
// externalAccountRequired (optional, boolean):
|
||||
// If this field is present and set to "true",
|
||||
// then the CA requires that all new- account requests include an "externalAccountBinding" field
|
||||
// associating the new account with an external account.
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||
}
|
||||
|
||||
// ExtendedAccount a extended Account.
|
||||
type ExtendedAccount struct {
|
||||
Account
|
||||
// Contains the value of the response header `Location`
|
||||
Location string `json:"-"`
|
||||
}
|
||||
|
||||
// Account the ACME account Object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.2
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3
|
||||
type Account struct {
|
||||
// status (required, string):
|
||||
// The status of this account.
|
||||
// Possible values are: "valid", "deactivated", and "revoked".
|
||||
// The value "deactivated" should be used to indicate client-initiated deactivation
|
||||
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
// contact (optional, array of string):
|
||||
// An array of URLs that the server can use to contact the client for issues related to this account.
|
||||
// For example, the server may wish to notify the client about server-initiated revocation or certificate expiration.
|
||||
// For information on supported URL schemes, see Section 7.3
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
|
||||
// termsOfServiceAgreed (optional, boolean):
|
||||
// Including this field in a new-account request,
|
||||
// with a value of true, indicates the client's agreement with the terms of service.
|
||||
// This field is not updateable by the client.
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
|
||||
// orders (required, string):
|
||||
// A URL from which a list of orders submitted by this account can be fetched via a POST-as-GET request,
|
||||
// as described in Section 7.1.2.1.
|
||||
Orders string `json:"orders,omitempty"`
|
||||
|
||||
// onlyReturnExisting (optional, boolean):
|
||||
// If this field is present with the value "true",
|
||||
// then the server MUST NOT create a new account if one does not already exist.
|
||||
// This allows a client to look up an account URL based on an account key (see Section 7.3.1).
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
||||
|
||||
// externalAccountBinding (optional, object):
|
||||
// An optional field for binding the new account with an existing non-ACME account (see Section 7.3.4).
|
||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
|
||||
}
|
||||
|
||||
// ExtendedOrder a extended Order.
|
||||
type ExtendedOrder struct {
|
||||
Order
|
||||
// The order URL, contains the value of the response header `Location`
|
||||
Location string `json:"-"`
|
||||
}
|
||||
|
||||
// Order the ACME order Object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.3
|
||||
type Order struct {
|
||||
// status (required, string):
|
||||
// The status of this order.
|
||||
// Possible values are: "pending", "ready", "processing", "valid", and "invalid".
|
||||
Status string `json:"status,omitempty"`
|
||||
|
||||
// expires (optional, string):
|
||||
// The timestamp after which the server will consider this order invalid,
|
||||
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||
// This field is REQUIRED for objects with "pending" or "valid" in the status field.
|
||||
Expires string `json:"expires,omitempty"`
|
||||
|
||||
// identifiers (required, array of object):
|
||||
// An array of identifier objects that the order pertains to.
|
||||
Identifiers []Identifier `json:"identifiers"`
|
||||
|
||||
// notBefore (optional, string):
|
||||
// The requested value of the notBefore field in the certificate,
|
||||
// in the date format defined in [RFC3339].
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
|
||||
// notAfter (optional, string):
|
||||
// The requested value of the notAfter field in the certificate,
|
||||
// in the date format defined in [RFC3339].
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
|
||||
// error (optional, object):
|
||||
// The error that occurred while processing the order, if any.
|
||||
// This field is structured as a problem document [RFC7807].
|
||||
Error *ProblemDetails `json:"error,omitempty"`
|
||||
|
||||
// authorizations (required, array of string):
|
||||
// For pending orders,
|
||||
// the authorizations that the client needs to complete before the requested certificate can be issued (see Section 7.5),
|
||||
// including unexpired authorizations that the client has completed in the past for identifiers specified in the order.
|
||||
// The authorizations required are dictated by server policy
|
||||
// and there may not be a 1:1 relationship between the order identifiers and the authorizations required.
|
||||
// For final orders (in the "valid" or "invalid" state), the authorizations that were completed.
|
||||
// Each entry is a URL from which an authorization can be fetched with a POST-as-GET request.
|
||||
Authorizations []string `json:"authorizations,omitempty"`
|
||||
|
||||
// finalize (required, string):
|
||||
// A URL that a CSR must be POSTed to once all of the order's authorizations are satisfied to finalize the order.
|
||||
// The result of a successful finalization will be the population of the certificate URL for the order.
|
||||
Finalize string `json:"finalize,omitempty"`
|
||||
|
||||
// certificate (optional, string):
|
||||
// A URL for the certificate that has been issued in response to this order
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
}
|
||||
|
||||
// Authorization the ACME authorization object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
|
||||
type Authorization struct {
|
||||
// status (required, string):
|
||||
// The status of this authorization.
|
||||
// Possible values are: "pending", "valid", "invalid", "deactivated", "expired", and "revoked".
|
||||
Status string `json:"status"`
|
||||
|
||||
// expires (optional, string):
|
||||
// The timestamp after which the server will consider this authorization invalid,
|
||||
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||
// This field is REQUIRED for objects with "valid" in the "status" field.
|
||||
Expires time.Time `json:"expires,omitempty"`
|
||||
|
||||
// identifier (required, object):
|
||||
// The identifier that the account is authorized to represent
|
||||
Identifier Identifier `json:"identifier,omitempty"`
|
||||
|
||||
// challenges (required, array of objects):
|
||||
// For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier.
|
||||
// For valid authorizations, the challenge that was validated.
|
||||
// For invalid authorizations, the challenge that was attempted and failed.
|
||||
// Each array entry is an object with parameters required to validate the challenge.
|
||||
// A client should attempt to fulfill one of these challenges,
|
||||
// and a server should consider any one of the challenges sufficient to make the authorization valid.
|
||||
Challenges []Challenge `json:"challenges,omitempty"`
|
||||
|
||||
// wildcard (optional, boolean):
|
||||
// For authorizations created as a result of a newOrder request containing a DNS identifier
|
||||
// with a value that contained a wildcard prefix this field MUST be present, and true.
|
||||
Wildcard bool `json:"wildcard,omitempty"`
|
||||
}
|
||||
|
||||
// ExtendedChallenge a extended Challenge.
|
||||
type ExtendedChallenge struct {
|
||||
Challenge
|
||||
// Contains the value of the response header `Retry-After`
|
||||
RetryAfter string `json:"-"`
|
||||
// Contains the value of the response header `Link` rel="up"
|
||||
AuthorizationURL string `json:"-"`
|
||||
}
|
||||
|
||||
// Challenge the ACME challenge object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.5
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8
|
||||
type Challenge struct {
|
||||
// type (required, string):
|
||||
// The type of challenge encoded in the object.
|
||||
Type string `json:"type"`
|
||||
|
||||
// url (required, string):
|
||||
// The URL to which a response can be posted.
|
||||
URL string `json:"url"`
|
||||
|
||||
// status (required, string):
|
||||
// The status of this challenge. Possible values are: "pending", "processing", "valid", and "invalid".
|
||||
Status string `json:"status"`
|
||||
|
||||
// validated (optional, string):
|
||||
// The time at which the server validated this challenge,
|
||||
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||
// This field is REQUIRED if the "status" field is "valid".
|
||||
Validated time.Time `json:"validated,omitempty"`
|
||||
|
||||
// error (optional, object):
|
||||
// Error that occurred while the server was validating the challenge, if any,
|
||||
// structured as a problem document [RFC7807].
|
||||
// Multiple errors can be indicated by using subproblems Section 6.7.1.
|
||||
// A challenge object with an error MUST have status equal to "invalid".
|
||||
Error *ProblemDetails `json:"error,omitempty"`
|
||||
|
||||
// token (required, string):
|
||||
// A random value that uniquely identifies the challenge.
|
||||
// This value MUST have at least 128 bits of entropy.
|
||||
// It MUST NOT contain any characters outside the base64url alphabet,
|
||||
// and MUST NOT include base64 padding characters ("=").
|
||||
// See [RFC4086] for additional information on randomness requirements.
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
|
||||
Token string `json:"token"`
|
||||
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.1
|
||||
KeyAuthorization string `json:"keyAuthorization"`
|
||||
}
|
||||
|
||||
// Identifier the ACME identifier object.
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-9.7.7
|
||||
type Identifier struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// CSRMessage Certificate Signing Request
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.4
|
||||
type CSRMessage struct {
|
||||
// csr (required, string):
|
||||
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
||||
// The CSR is sent in the base64url-encoded version of the DER format.
|
||||
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.).
|
||||
Csr string `json:"csr"`
|
||||
}
|
||||
|
||||
// RevokeCertMessage a certificate revocation message
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.6
|
||||
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||
type RevokeCertMessage struct {
|
||||
// certificate (required, string):
|
||||
// The certificate to be revoked, in the base64url-encoded version of the DER format.
|
||||
// (Note: Because this field uses base64url, and does not include headers, it is different from PEM.)
|
||||
Certificate string `json:"certificate"`
|
||||
|
||||
// reason (optional, int):
|
||||
// One of the revocation reasonCodes defined in Section 5.3.1 of [RFC5280] to be used when generating OCSP responses and CRLs.
|
||||
// If this field is not set the server SHOULD omit the reasonCode CRL entry extension when generating OCSP responses and CRLs.
|
||||
// The server MAY disallow a subset of reasonCodes from being used by the user.
|
||||
// If a request contains a disallowed reasonCode the server MUST reject it with the error type "urn:ietf:params:acme:error:badRevocationReason".
|
||||
// The problem document detail SHOULD indicate which reasonCodes are allowed.
|
||||
Reason *uint `json:"reason,omitempty"`
|
||||
}
|
58
vendor/github.com/go-acme/lego/acme/errors.go
generated
vendored
Normal file
58
vendor/github.com/go-acme/lego/acme/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Errors types
|
||||
const (
|
||||
errNS = "urn:ietf:params:acme:error:"
|
||||
BadNonceErr = errNS + "badNonce"
|
||||
)
|
||||
|
||||
// ProblemDetails the problem details object
|
||||
// - https://tools.ietf.org/html/rfc7807#section-3.1
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.3.3
|
||||
type ProblemDetails struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
HTTPStatus int `json:"status,omitempty"`
|
||||
Instance string `json:"instance,omitempty"`
|
||||
SubProblems []SubProblem `json:"subproblems,omitempty"`
|
||||
|
||||
// additional values to have a better error message (Not defined by the RFC)
|
||||
Method string `json:"method,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// SubProblem a "subproblems"
|
||||
// - https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-6.7.1
|
||||
type SubProblem struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
Identifier Identifier `json:"identifier,omitempty"`
|
||||
}
|
||||
|
||||
func (p ProblemDetails) Error() string {
|
||||
msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus)
|
||||
if len(p.Method) != 0 || len(p.URL) != 0 {
|
||||
msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)
|
||||
}
|
||||
msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)
|
||||
|
||||
for _, sub := range p.SubProblems {
|
||||
msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)
|
||||
}
|
||||
|
||||
if len(p.Instance) == 0 {
|
||||
msg += ", url: " + p.Instance
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
// NonceError represents the error which is returned
|
||||
// if the nonce sent by the client was not accepted by the server.
|
||||
type NonceError struct {
|
||||
*ProblemDetails
|
||||
}
|
256
vendor/github.com/go-acme/lego/certcrypto/crypto.go
generated
vendored
Normal file
256
vendor/github.com/go-acme/lego/certcrypto/crypto.go
generated
vendored
Normal file
|
@ -0,0 +1,256 @@
|
|||
package certcrypto
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
// Constants for all key types we support.
|
||||
const (
|
||||
EC256 = KeyType("P256")
|
||||
EC384 = KeyType("P384")
|
||||
RSA2048 = KeyType("2048")
|
||||
RSA4096 = KeyType("4096")
|
||||
RSA8192 = KeyType("8192")
|
||||
)
|
||||
|
||||
const (
|
||||
// OCSPGood means that the certificate is valid.
|
||||
OCSPGood = ocsp.Good
|
||||
// OCSPRevoked means that the certificate has been deliberately revoked.
|
||||
OCSPRevoked = ocsp.Revoked
|
||||
// OCSPUnknown means that the OCSP responder doesn't know about the certificate.
|
||||
OCSPUnknown = ocsp.Unknown
|
||||
// OCSPServerFailed means that the OCSP responder failed to process the request.
|
||||
OCSPServerFailed = ocsp.ServerFailed
|
||||
)
|
||||
|
||||
// Constants for OCSP must staple
|
||||
var (
|
||||
tlsFeatureExtensionOID = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}
|
||||
ocspMustStapleFeature = []byte{0x30, 0x03, 0x02, 0x01, 0x05}
|
||||
)
|
||||
|
||||
// KeyType represents the key algo as well as the key size or curve to use.
|
||||
type KeyType string
|
||||
|
||||
type DERCertificateBytes []byte
|
||||
|
||||
// ParsePEMBundle parses a certificate bundle from top to bottom and returns
|
||||
// a slice of x509 certificates. This function will error if no certificates are found.
|
||||
func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||
var certificates []*x509.Certificate
|
||||
var certDERBlock *pem.Block
|
||||
|
||||
for {
|
||||
certDERBlock, bundle = pem.Decode(bundle)
|
||||
if certDERBlock == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if certDERBlock.Type == "CERTIFICATE" {
|
||||
cert, err := x509.ParseCertificate(certDERBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
}
|
||||
|
||||
if len(certificates) == 0 {
|
||||
return nil, errors.New("no certificates were found while parsing the bundle")
|
||||
}
|
||||
|
||||
return certificates, nil
|
||||
}
|
||||
|
||||
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||
keyBlock, _ := pem.Decode(key)
|
||||
|
||||
switch keyBlock.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
return x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
|
||||
case "EC PRIVATE KEY":
|
||||
return x509.ParseECPrivateKey(keyBlock.Bytes)
|
||||
default:
|
||||
return nil, errors.New("unknown PEM header value")
|
||||
}
|
||||
}
|
||||
|
||||
func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||
switch keyType {
|
||||
case EC256:
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
case EC384:
|
||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
case RSA2048:
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
case RSA4096:
|
||||
return rsa.GenerateKey(rand.Reader, 4096)
|
||||
case RSA8192:
|
||||
return rsa.GenerateKey(rand.Reader, 8192)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
|
||||
}
|
||||
|
||||
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
||||
template := x509.CertificateRequest{
|
||||
Subject: pkix.Name{CommonName: domain},
|
||||
DNSNames: san,
|
||||
}
|
||||
|
||||
if mustStaple {
|
||||
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
|
||||
Id: tlsFeatureExtensionOID,
|
||||
Value: ocspMustStapleFeature,
|
||||
})
|
||||
}
|
||||
|
||||
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||
}
|
||||
|
||||
func PEMEncode(data interface{}) []byte {
|
||||
return pem.EncodeToMemory(PEMBlock(data))
|
||||
}
|
||||
|
||||
func PEMBlock(data interface{}) *pem.Block {
|
||||
var pemBlock *pem.Block
|
||||
switch key := data.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||
pemBlock = &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}
|
||||
case *rsa.PrivateKey:
|
||||
pemBlock = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}
|
||||
case *x509.CertificateRequest:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: key.Raw}
|
||||
case DERCertificateBytes:
|
||||
pemBlock = &pem.Block{Type: "CERTIFICATE", Bytes: []byte(data.(DERCertificateBytes))}
|
||||
}
|
||||
|
||||
return pemBlock
|
||||
}
|
||||
|
||||
func pemDecode(data []byte) (*pem.Block, error) {
|
||||
pemBlock, _ := pem.Decode(data)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("PEM decode did not yield a valid block. Is the certificate in the right format?")
|
||||
}
|
||||
|
||||
return pemBlock, nil
|
||||
}
|
||||
|
||||
func PemDecodeTox509CSR(pem []byte) (*x509.CertificateRequest, error) {
|
||||
pemBlock, err := pemDecode(pem)
|
||||
if pemBlock == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pemBlock.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, fmt.Errorf("PEM block is not a certificate request")
|
||||
}
|
||||
|
||||
return x509.ParseCertificateRequest(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
// ParsePEMCertificate returns Certificate from a PEM encoded certificate.
|
||||
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
|
||||
func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
|
||||
pemBlock, err := pemDecode(cert)
|
||||
if pemBlock == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// from a DER encoded certificate
|
||||
return x509.ParseCertificate(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
func ExtractDomains(cert *x509.Certificate) []string {
|
||||
domains := []string{cert.Subject.CommonName}
|
||||
|
||||
// Check for SAN certificate
|
||||
for _, sanDomain := range cert.DNSNames {
|
||||
if sanDomain == cert.Subject.CommonName {
|
||||
continue
|
||||
}
|
||||
domains = append(domains, sanDomain)
|
||||
}
|
||||
|
||||
return domains
|
||||
}
|
||||
|
||||
func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||
domains := []string{csr.Subject.CommonName}
|
||||
|
||||
// loop over the SubjectAltName DNS names
|
||||
for _, sanName := range csr.DNSNames {
|
||||
if containsSAN(domains, sanName) {
|
||||
// Duplicate; skip this name
|
||||
continue
|
||||
}
|
||||
|
||||
// Name is unique
|
||||
domains = append(domains, sanName)
|
||||
}
|
||||
|
||||
return domains
|
||||
}
|
||||
|
||||
func containsSAN(domains []string, sanName string) bool {
|
||||
for _, existingName := range domains {
|
||||
if existingName == sanName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||
}
|
||||
|
||||
func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expiration.IsZero() {
|
||||
expiration = time.Now().Add(365)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "ACME Challenge TEMP",
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: expiration,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||
BasicConstraintsValid: true,
|
||||
DNSNames: []string{domain},
|
||||
ExtraExtensions: extensions,
|
||||
}
|
||||
|
||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||
}
|
69
vendor/github.com/go-acme/lego/certificate/authorization.go
generated
vendored
Normal file
69
vendor/github.com/go-acme/lego/certificate/authorization.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// overallRequestLimit is the overall number of request per second
|
||||
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
||||
// From the documentation the limitation is 20 requests per second,
|
||||
// but using 20 as value doesn't work but 18 do
|
||||
overallRequestLimit = 18
|
||||
)
|
||||
|
||||
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
|
||||
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
||||
|
||||
delay := time.Second / overallRequestLimit
|
||||
|
||||
for _, authzURL := range order.Authorizations {
|
||||
time.Sleep(delay)
|
||||
|
||||
go func(authzURL string) {
|
||||
authz, err := c.core.Authorizations.Get(authzURL)
|
||||
if err != nil {
|
||||
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
|
||||
return
|
||||
}
|
||||
|
||||
resc <- authz
|
||||
}(authzURL)
|
||||
}
|
||||
|
||||
var responses []acme.Authorization
|
||||
failures := make(obtainError)
|
||||
for i := 0; i < len(order.Authorizations); i++ {
|
||||
select {
|
||||
case res := <-resc:
|
||||
responses = append(responses, res)
|
||||
case err := <-errc:
|
||||
failures[err.Domain] = err.Error
|
||||
}
|
||||
}
|
||||
|
||||
for i, auth := range order.Authorizations {
|
||||
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
|
||||
}
|
||||
|
||||
close(resc)
|
||||
close(errc)
|
||||
|
||||
// be careful to not return an empty failures map;
|
||||
// even if empty, they become non-nil error values
|
||||
if len(failures) > 0 {
|
||||
return responses, failures
|
||||
}
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
||||
for _, auth := range order.Authorizations {
|
||||
if err := c.core.Authorizations.Deactivate(auth); err != nil {
|
||||
log.Infof("Unable to deactivated authorizations: %s", auth)
|
||||
}
|
||||
}
|
||||
}
|
493
vendor/github.com/go-acme/lego/certificate/certificates.go
generated
vendored
Normal file
493
vendor/github.com/go-acme/lego/certificate/certificates.go
generated
vendored
Normal file
|
@ -0,0 +1,493 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
// maxBodySize is the maximum size of body that we will read.
|
||||
const maxBodySize = 1024 * 1024
|
||||
|
||||
// Resource represents a CA issued certificate.
|
||||
// PrivateKey, Certificate and IssuerCertificate are all
|
||||
// already PEM encoded and can be directly written to disk.
|
||||
// Certificate may be a certificate bundle,
|
||||
// depending on the options supplied to create it.
|
||||
type Resource struct {
|
||||
Domain string `json:"domain"`
|
||||
CertURL string `json:"certUrl"`
|
||||
CertStableURL string `json:"certStableUrl"`
|
||||
PrivateKey []byte `json:"-"`
|
||||
Certificate []byte `json:"-"`
|
||||
IssuerCertificate []byte `json:"-"`
|
||||
CSR []byte `json:"-"`
|
||||
}
|
||||
|
||||
// ObtainRequest The request to obtain certificate.
|
||||
//
|
||||
// The first domain in domains is used for the CommonName field of the certificate,
|
||||
// all other domains are added using the Subject Alternate Names extension.
|
||||
//
|
||||
// A new private key is generated for every invocation of the function Obtain.
|
||||
// If you do not want that you can supply your own private key in the privateKey parameter.
|
||||
// If this parameter is non-nil it will be used instead of generating a new one.
|
||||
//
|
||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||
type ObtainRequest struct {
|
||||
Domains []string
|
||||
Bundle bool
|
||||
PrivateKey crypto.PrivateKey
|
||||
MustStaple bool
|
||||
}
|
||||
|
||||
type resolver interface {
|
||||
Solve(authorizations []acme.Authorization) error
|
||||
}
|
||||
|
||||
type CertifierOptions struct {
|
||||
KeyType certcrypto.KeyType
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Certifier A service to obtain/renew/revoke certificates.
|
||||
type Certifier struct {
|
||||
core *api.Core
|
||||
resolver resolver
|
||||
options CertifierOptions
|
||||
}
|
||||
|
||||
// NewCertifier creates a Certifier.
|
||||
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
||||
return &Certifier{
|
||||
core: core,
|
||||
resolver: resolver,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain tries to obtain a single certificate using all domains passed into it.
|
||||
//
|
||||
// This function will never return a partial certificate.
|
||||
// If one domain in the list fails, the whole certificate will fail.
|
||||
func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||
if len(request.Domains) == 0 {
|
||||
return nil, errors.New("no domains to obtain a certificate for")
|
||||
}
|
||||
|
||||
domains := sanitizeDomain(request.Domains)
|
||||
|
||||
if request.Bundle {
|
||||
log.Infof("[%s] acme: Obtaining bundled SAN certificate", strings.Join(domains, ", "))
|
||||
} else {
|
||||
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||
}
|
||||
|
||||
order, err := c.core.Orders.New(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authz, err := c.getAuthorizations(order)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
c.deactivateAuthorizations(order)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.resolver.Solve(authz)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
failures := make(obtainError)
|
||||
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[challenge.GetTargetedDomain(auth)] = err
|
||||
}
|
||||
}
|
||||
|
||||
// Do not return an empty failures map, because
|
||||
// it would still be a non-nil error value
|
||||
if len(failures) > 0 {
|
||||
return cert, failures
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||
//
|
||||
// The domains are inferred from the CommonName and SubjectAltNames, if any.
|
||||
// The private key for this CSR is not required.
|
||||
//
|
||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||
//
|
||||
// This function will never return a partial certificate.
|
||||
// If one domain in the list fails, the whole certificate will fail.
|
||||
func (c *Certifier) ObtainForCSR(csr x509.CertificateRequest, bundle bool) (*Resource, error) {
|
||||
// figure out what domains it concerns
|
||||
// start with the common name
|
||||
domains := certcrypto.ExtractDomainsCSR(&csr)
|
||||
|
||||
if bundle {
|
||||
log.Infof("[%s] acme: Obtaining bundled SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||
} else {
|
||||
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||
}
|
||||
|
||||
order, err := c.core.Orders.New(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authz, err := c.getAuthorizations(order)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
c.deactivateAuthorizations(order)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.resolver.Solve(authz)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||
|
||||
failures := make(obtainError)
|
||||
cert, err := c.getForCSR(domains, order, bundle, csr.Raw, nil)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[challenge.GetTargetedDomain(auth)] = err
|
||||
}
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
// Add the CSR to the certificate so that it can be used for renewals.
|
||||
cert.CSR = certcrypto.PEMEncode(&csr)
|
||||
}
|
||||
|
||||
// Do not return an empty failures map,
|
||||
// because it would still be a non-nil error value
|
||||
if len(failures) > 0 {
|
||||
return cert, failures
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool) (*Resource, error) {
|
||||
if privateKey == nil {
|
||||
var err error
|
||||
privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Determine certificate name(s) based on the authorization resources
|
||||
commonName := domains[0]
|
||||
|
||||
// ACME draft Section 7.4 "Applying for Certificate Issuance"
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-12#section-7.4
|
||||
// says:
|
||||
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||
// "identifiers" or "authorizations" elements in the returned order
|
||||
// object.
|
||||
san := []string{commonName}
|
||||
for _, auth := range order.Identifiers {
|
||||
if auth.Value != commonName {
|
||||
san = append(san, auth.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should the CSR be customizable?
|
||||
csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey))
|
||||
}
|
||||
|
||||
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr []byte, privateKeyPem []byte) (*Resource, error) {
|
||||
respOrder, err := c.core.Orders.UpdateForCSR(order.Finalize, csr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commonName := domains[0]
|
||||
certRes := &Resource{
|
||||
Domain: commonName,
|
||||
CertURL: respOrder.Certificate,
|
||||
PrivateKey: privateKeyPem,
|
||||
}
|
||||
|
||||
if respOrder.Status == acme.StatusValid {
|
||||
// if the certificate is available right away, short cut!
|
||||
ok, errR := c.checkResponse(respOrder, certRes, bundle)
|
||||
if errR != nil {
|
||||
return nil, errR
|
||||
}
|
||||
|
||||
if ok {
|
||||
return certRes, nil
|
||||
}
|
||||
}
|
||||
|
||||
timeout := c.options.Timeout
|
||||
if c.options.Timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
err = wait.For("certificate", timeout, timeout/60, func() (bool, error) {
|
||||
ord, errW := c.core.Orders.Get(order.Location)
|
||||
if errW != nil {
|
||||
return false, errW
|
||||
}
|
||||
|
||||
done, errW := c.checkResponse(ord, certRes, bundle)
|
||||
if errW != nil {
|
||||
return false, errW
|
||||
}
|
||||
|
||||
return done, nil
|
||||
})
|
||||
|
||||
return certRes, err
|
||||
}
|
||||
|
||||
// checkResponse checks to see if the certificate is ready and a link is contained in the response.
|
||||
//
|
||||
// If so, loads it into certRes and returns true.
|
||||
// If the cert is not yet ready, it returns false.
|
||||
//
|
||||
// The certRes input should already have the Domain (common name) field populated.
|
||||
//
|
||||
// If bundle is true, the certificate will be bundled with the issuer's cert.
|
||||
func (c *Certifier) checkResponse(order acme.Order, certRes *Resource, bundle bool) (bool, error) {
|
||||
valid, err := checkOrderStatus(order)
|
||||
if err != nil || !valid {
|
||||
return valid, err
|
||||
}
|
||||
|
||||
cert, issuer, err := c.core.Certificates.Get(order.Certificate, bundle)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
|
||||
|
||||
certRes.IssuerCertificate = issuer
|
||||
certRes.Certificate = cert
|
||||
certRes.CertURL = order.Certificate
|
||||
certRes.CertStableURL = order.Certificate
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||
func (c *Certifier) Revoke(cert []byte) error {
|
||||
certificates, err := certcrypto.ParsePEMBundle(cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return fmt.Errorf("certificate bundle starts with a CA certificate")
|
||||
}
|
||||
|
||||
revokeMsg := acme.RevokeCertMessage{
|
||||
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
||||
}
|
||||
|
||||
return c.core.Certificates.Revoke(revokeMsg)
|
||||
}
|
||||
|
||||
// Renew takes a Resource and tries to renew the certificate.
|
||||
//
|
||||
// If the renewal process succeeds, the new certificate will ge returned in a new CertResource.
|
||||
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
||||
// If the server does not provide us with a new cert on a GET request to the CertURL
|
||||
// this function will start a new-cert flow where a new certificate gets generated.
|
||||
//
|
||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||
//
|
||||
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool) (*Resource, error) {
|
||||
// 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.
|
||||
certificates, err := certcrypto.ParsePEMBundle(certRes.Certificate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return nil, fmt.Errorf("[%s] Certificate bundle starts with a CA certificate", certRes.Domain)
|
||||
}
|
||||
|
||||
// This is just meant to be informal for the user.
|
||||
timeLeft := x509Cert.NotAfter.Sub(time.Now().UTC())
|
||||
log.Infof("[%s] acme: Trying renewal with %d hours remaining", certRes.Domain, int(timeLeft.Hours()))
|
||||
|
||||
// We always need to request a new certificate to renew.
|
||||
// Start by checking to see if the certificate was based off a CSR,
|
||||
// and use that if it's defined.
|
||||
if len(certRes.CSR) > 0 {
|
||||
csr, errP := certcrypto.PemDecodeTox509CSR(certRes.CSR)
|
||||
if errP != nil {
|
||||
return nil, errP
|
||||
}
|
||||
|
||||
return c.ObtainForCSR(*csr, bundle)
|
||||
}
|
||||
|
||||
var privateKey crypto.PrivateKey
|
||||
if certRes.PrivateKey != nil {
|
||||
privateKey, err = certcrypto.ParsePEMPrivateKey(certRes.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
query := ObtainRequest{
|
||||
Domains: certcrypto.ExtractDomains(x509Cert),
|
||||
Bundle: bundle,
|
||||
PrivateKey: privateKey,
|
||||
MustStaple: mustStaple,
|
||||
}
|
||||
return c.Obtain(query)
|
||||
}
|
||||
|
||||
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||
// the parsed response, and an error, if any.
|
||||
//
|
||||
// The returned []byte can be passed directly into the OCSPStaple property of a tls.Certificate.
|
||||
// If the bundle only contains the issued certificate,
|
||||
// this function will try to get the issuer certificate from the IssuingCertificateURL in the certificate.
|
||||
//
|
||||
// If the []byte and/or ocsp.Response return values are nil, the OCSP status may be assumed OCSPUnknown.
|
||||
func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||
certificates, err := certcrypto.ParsePEMBundle(bundle)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// We expect the certificate slice to be ordered downwards the chain.
|
||||
// SRV CRT -> CA. We need to pull the leaf and issuer certs out of it,
|
||||
// which should always be the first two certificates.
|
||||
// If there's no OCSP server listed in the leaf cert, there's nothing to do.
|
||||
// And if we have only one certificate so far, we need to get the issuer cert.
|
||||
|
||||
issuedCert := certificates[0]
|
||||
|
||||
if len(issuedCert.OCSPServer) == 0 {
|
||||
return nil, nil, errors.New("no OCSP server specified in cert")
|
||||
}
|
||||
|
||||
if len(certificates) == 1 {
|
||||
// TODO: build fallback. If this fails, check the remaining array entries.
|
||||
if len(issuedCert.IssuingCertificateURL) == 0 {
|
||||
return nil, nil, errors.New("no issuing certificate URL")
|
||||
}
|
||||
|
||||
resp, errC := c.core.HTTPClient.Get(issuedCert.IssuingCertificateURL[0])
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
|
||||
issuerCert, errC := x509.ParseCertificate(issuerBytes)
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
|
||||
// Insert it into the slice on position 0
|
||||
// We want it ordered right SRV CRT -> CA
|
||||
certificates = append(certificates, issuerCert)
|
||||
}
|
||||
|
||||
issuerCert := certificates[1]
|
||||
|
||||
// Finally kick off the OCSP request.
|
||||
ocspReq, err := ocsp.CreateRequest(issuedCert, issuerCert, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
resp, err := c.core.HTTPClient.Post(issuedCert.OCSPServer[0], "application/ocsp-request", bytes.NewReader(ocspReq))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ocspRes, err := ocsp.ParseResponse(ocspResBytes, issuerCert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ocspResBytes, ocspRes, nil
|
||||
}
|
||||
|
||||
func checkOrderStatus(order acme.Order) (bool, error) {
|
||||
switch order.Status {
|
||||
case acme.StatusValid:
|
||||
return true, nil
|
||||
case acme.StatusInvalid:
|
||||
return false, order.Error
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-7.1.4
|
||||
// The domain name MUST be encoded
|
||||
// in the form in which it would appear in a certificate. That is, it
|
||||
// MUST be encoded according to the rules in Section 7 of [RFC5280].
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc5280#section-7
|
||||
func sanitizeDomain(domains []string) []string {
|
||||
var sanitizedDomains []string
|
||||
for _, domain := range domains {
|
||||
sanitizedDomain, err := idna.ToASCII(domain)
|
||||
if err != nil {
|
||||
log.Infof("skip domain %q: unable to sanitize (punnycode): %v", domain, err)
|
||||
} else {
|
||||
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
|
||||
}
|
||||
}
|
||||
return sanitizedDomains
|
||||
}
|
30
vendor/github.com/go-acme/lego/certificate/errors.go
generated
vendored
Normal file
30
vendor/github.com/go-acme/lego/certificate/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// obtainError is returned when there are specific errors available per domain.
|
||||
type obtainError map[string]error
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
sort.Strings(domains)
|
||||
|
||||
for _, domain := range domains {
|
||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
type domainError struct {
|
||||
Domain string
|
||||
Error error
|
||||
}
|
44
vendor/github.com/go-acme/lego/challenge/challenges.go
generated
vendored
Normal file
44
vendor/github.com/go-acme/lego/challenge/challenges.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package challenge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
)
|
||||
|
||||
// Type is a string that identifies a particular challenge type and version of ACME challenge.
|
||||
type Type string
|
||||
|
||||
const (
|
||||
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.3
|
||||
// Note: ChallengePath returns the URL path to fulfill this challenge
|
||||
HTTP01 = Type("http-01")
|
||||
|
||||
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-acme-16#section-8.4
|
||||
// Note: GetRecord returns a DNS record which will fulfill this challenge
|
||||
DNS01 = Type("dns-01")
|
||||
|
||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05
|
||||
TLSALPN01 = Type("tls-alpn-01")
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func FindChallenge(chlgType Type, authz acme.Authorization) (acme.Challenge, error) {
|
||||
for _, chlg := range authz.Challenges {
|
||||
if chlg.Type == string(chlgType) {
|
||||
return chlg, nil
|
||||
}
|
||||
}
|
||||
|
||||
return acme.Challenge{}, fmt.Errorf("[%s] acme: unable to find challenge %s", GetTargetedDomain(authz), chlgType)
|
||||
}
|
||||
|
||||
func GetTargetedDomain(authz acme.Authorization) string {
|
||||
if authz.Wildcard {
|
||||
return "*." + authz.Identifier.Value
|
||||
}
|
||||
return authz.Identifier.Value
|
||||
}
|
16
vendor/github.com/go-acme/lego/challenge/dns01/cname.go
generated
vendored
Normal file
16
vendor/github.com/go-acme/lego/challenge/dns01/cname.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package dns01
|
||||
|
||||
import "github.com/miekg/dns"
|
||||
|
||||
// Update FQDN with CNAME if any
|
||||
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
|
||||
for _, rr := range r.Answer {
|
||||
if cn, ok := rr.(*dns.CNAME); ok {
|
||||
if cn.Hdr.Name == fqdn {
|
||||
return cn.Target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fqdn
|
||||
}
|
188
vendor/github.com/go-acme/lego/challenge/dns01/dns_challenge.go
generated
vendored
Normal file
188
vendor/github.com/go-acme/lego/challenge/dns01/dns_challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,188 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultPropagationTimeout default propagation timeout
|
||||
DefaultPropagationTimeout = 60 * time.Second
|
||||
|
||||
// DefaultPollingInterval default polling interval
|
||||
DefaultPollingInterval = 2 * time.Second
|
||||
|
||||
// DefaultTTL default TTL
|
||||
DefaultTTL = 120
|
||||
)
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
|
||||
type ChallengeOption func(*Challenge) error
|
||||
|
||||
// CondOption Conditional challenge option.
|
||||
func CondOption(condition bool, opt ChallengeOption) ChallengeOption {
|
||||
if !condition {
|
||||
// NoOp options
|
||||
return func(*Challenge) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
// Challenge implements the dns-01 challenge
|
||||
type Challenge struct {
|
||||
core *api.Core
|
||||
validate ValidateFunc
|
||||
provider challenge.Provider
|
||||
preCheck preCheck
|
||||
dnsTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
|
||||
chlg := &Challenge{
|
||||
core: core,
|
||||
validate: validate,
|
||||
provider: provider,
|
||||
preCheck: newPreCheck(),
|
||||
dnsTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
err := opt(chlg)
|
||||
if err != nil {
|
||||
log.Infof("challenge option error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return chlg
|
||||
}
|
||||
|
||||
// PreSolve just submits the txt record to the dns provider.
|
||||
// It does not validate record propagation, or do anything at all with the acme server.
|
||||
func (c *Challenge) PreSolve(authz acme.Authorization) error {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
log.Infof("[%s] acme: Preparing to solve DNS-01", domain)
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.provider == nil {
|
||||
return fmt.Errorf("[%s] acme: no DNS Provider configured", domain)
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %s", domain, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
log.Infof("[%s] acme: Trying to solve DNS-01", domain)
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fqdn, value := GetRecord(authz.Identifier.Value, keyAuth)
|
||||
|
||||
var timeout, interval time.Duration
|
||||
switch provider := c.provider.(type) {
|
||||
case challenge.ProviderTimeout:
|
||||
timeout, interval = provider.Timeout()
|
||||
default:
|
||||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
||||
}
|
||||
|
||||
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
|
||||
|
||||
err = wait.For("propagation", timeout, interval, func() (bool, error) {
|
||||
stop, errP := c.preCheck.call(domain, fqdn, value)
|
||||
if !stop || errP != nil {
|
||||
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
|
||||
}
|
||||
return stop, errP
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chlng.KeyAuthorization = keyAuth
|
||||
return c.validate(c.core, domain, chlng)
|
||||
}
|
||||
|
||||
// CleanUp cleans the challenge.
|
||||
func (c *Challenge) CleanUp(authz acme.Authorization) error {
|
||||
log.Infof("[%s] acme: Cleaning DNS-01 challenge", challenge.GetTargetedDomain(authz))
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.DNS01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
}
|
||||
|
||||
func (c *Challenge) Sequential() (bool, time.Duration) {
|
||||
if p, ok := c.provider.(sequential); ok {
|
||||
return ok, p.Sequential()
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
type sequential interface {
|
||||
Sequential() time.Duration
|
||||
}
|
||||
|
||||
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge
|
||||
func GetRecord(domain, keyAuth string) (fqdn string, value string) {
|
||||
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||||
// base64URL encoding without padding
|
||||
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||||
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
||||
|
||||
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
|
||||
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
||||
// Check if the domain has CNAME then return that
|
||||
if err == nil && r.Rcode == dns.RcodeSuccess {
|
||||
fqdn = updateDomainWithCName(r, fqdn)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
52
vendor/github.com/go-acme/lego/challenge/dns01/dns_challenge_manual.go
generated
vendored
Normal file
52
vendor/github.com/go-acme/lego/challenge/dns01/dns_challenge_manual.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
dnsTemplate = `%s %d IN TXT "%s"`
|
||||
)
|
||||
|
||||
// DNSProviderManual is an implementation of the ChallengeProvider interface
|
||||
type DNSProviderManual struct{}
|
||||
|
||||
// NewDNSProviderManual returns a DNSProviderManual instance.
|
||||
func NewDNSProviderManual() (*DNSProviderManual, error) {
|
||||
return &DNSProviderManual{}, nil
|
||||
}
|
||||
|
||||
// Present prints instructions for manually creating the TXT record
|
||||
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
||||
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, value)
|
||||
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
||||
|
||||
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp prints instructions for manually removing the TXT record
|
||||
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
||||
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, "...")
|
||||
|
||||
return nil
|
||||
}
|
19
vendor/github.com/go-acme/lego/challenge/dns01/fqdn.go
generated
vendored
Normal file
19
vendor/github.com/go-acme/lego/challenge/dns01/fqdn.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
package dns01
|
||||
|
||||
// ToFqdn converts the name into a fqdn appending a trailing dot.
|
||||
func ToFqdn(name string) string {
|
||||
n := len(name)
|
||||
if n == 0 || name[n-1] == '.' {
|
||||
return name
|
||||
}
|
||||
return name + "."
|
||||
}
|
||||
|
||||
// UnFqdn converts the fqdn into a name removing the trailing dot.
|
||||
func UnFqdn(name string) string {
|
||||
n := len(name)
|
||||
if n != 0 && name[n-1] == '.' {
|
||||
return name[:n-1]
|
||||
}
|
||||
return name
|
||||
}
|
232
vendor/github.com/go-acme/lego/challenge/dns01/nameserver.go
generated
vendored
Normal file
232
vendor/github.com/go-acme/lego/challenge/dns01/nameserver.go
generated
vendored
Normal file
|
@ -0,0 +1,232 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const defaultResolvConf = "/etc/resolv.conf"
|
||||
|
||||
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
|
||||
var dnsTimeout = 10 * time.Second
|
||||
|
||||
var (
|
||||
fqdnToZone = map[string]string{}
|
||||
muFqdnToZone sync.Mutex
|
||||
)
|
||||
|
||||
var defaultNameservers = []string{
|
||||
"google-public-dns-a.google.com:53",
|
||||
"google-public-dns-b.google.com:53",
|
||||
}
|
||||
|
||||
// recursiveNameservers are used to pre-check DNS propagation
|
||||
var recursiveNameservers = getNameservers(defaultResolvConf, defaultNameservers)
|
||||
|
||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||
func ClearFqdnCache() {
|
||||
muFqdnToZone.Lock()
|
||||
fqdnToZone = map[string]string{}
|
||||
muFqdnToZone.Unlock()
|
||||
}
|
||||
|
||||
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
||||
return func(_ *Challenge) error {
|
||||
dnsTimeout = timeout
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddRecursiveNameservers(nameservers []string) ChallengeOption {
|
||||
return func(_ *Challenge) error {
|
||||
recursiveNameservers = ParseNameservers(nameservers)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// getNameservers attempts to get systems nameservers before falling back to the defaults
|
||||
func getNameservers(path string, defaults []string) []string {
|
||||
config, err := dns.ClientConfigFromFile(path)
|
||||
if err != nil || len(config.Servers) == 0 {
|
||||
return defaults
|
||||
}
|
||||
|
||||
return ParseNameservers(config.Servers)
|
||||
}
|
||||
|
||||
func ParseNameservers(servers []string) []string {
|
||||
var resolvers []string
|
||||
for _, resolver := range servers {
|
||||
// ensure all servers have a port number
|
||||
if _, _, err := net.SplitHostPort(resolver); err != nil {
|
||||
resolvers = append(resolvers, net.JoinHostPort(resolver, "53"))
|
||||
} else {
|
||||
resolvers = append(resolvers, resolver)
|
||||
}
|
||||
}
|
||||
return resolvers
|
||||
}
|
||||
|
||||
// lookupNameservers returns the authoritative nameservers for the given fqdn.
|
||||
func lookupNameservers(fqdn string) ([]string, error) {
|
||||
var authoritativeNss []string
|
||||
|
||||
zone, err := FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine the zone: %v", err)
|
||||
}
|
||||
|
||||
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rr := range r.Answer {
|
||||
if ns, ok := rr.(*dns.NS); ok {
|
||||
authoritativeNss = append(authoritativeNss, strings.ToLower(ns.Ns))
|
||||
}
|
||||
}
|
||||
|
||||
if len(authoritativeNss) > 0 {
|
||||
return authoritativeNss, nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not determine authoritative nameservers")
|
||||
}
|
||||
|
||||
// FindZoneByFqdn determines the zone apex for the given fqdn
|
||||
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||
func FindZoneByFqdn(fqdn string) (string, error) {
|
||||
return FindZoneByFqdnCustom(fqdn, recursiveNameservers)
|
||||
}
|
||||
|
||||
// FindZoneByFqdnCustom determines the zone apex for the given fqdn
|
||||
// by recursing up the domain labels until the nameserver returns a SOA record in the answer section.
|
||||
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||
muFqdnToZone.Lock()
|
||||
defer muFqdnToZone.Unlock()
|
||||
|
||||
// Do we have it cached?
|
||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
var in *dns.Msg
|
||||
|
||||
labelIndexes := dns.Split(fqdn)
|
||||
for _, index := range labelIndexes {
|
||||
domain := fqdn[index:]
|
||||
|
||||
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if in == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch in.Rcode {
|
||||
case dns.RcodeSuccess:
|
||||
// Check if we got a SOA RR in the answer section
|
||||
|
||||
if len(in.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// CNAME records cannot/should not exist at the root of a zone.
|
||||
// So we skip a domain when a CNAME is found.
|
||||
if dnsMsgContainsCNAME(in) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ans := range in.Answer {
|
||||
if soa, ok := ans.(*dns.SOA); ok {
|
||||
zone := soa.Hdr.Name
|
||||
fqdnToZone[fqdn] = zone
|
||||
return zone, nil
|
||||
}
|
||||
}
|
||||
case dns.RcodeNameError:
|
||||
// NXDOMAIN
|
||||
default:
|
||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||
return "", fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||
}
|
||||
|
||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg
|
||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||
for _, ans := range msg.Answer {
|
||||
if _, ok := ans.(*dns.CNAME); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||
m := createDNSMsg(fqdn, rtype, recursive)
|
||||
|
||||
var in *dns.Msg
|
||||
var err error
|
||||
|
||||
for _, ns := range nameservers {
|
||||
in, err = sendDNSQuery(m, ns)
|
||||
if err == nil && len(in.Answer) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return in, err
|
||||
}
|
||||
|
||||
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(fqdn, rtype)
|
||||
m.SetEdns0(4096, false)
|
||||
|
||||
if !recursive {
|
||||
m.RecursionDesired = false
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
||||
in, _, err := udp.Exchange(m, ns)
|
||||
|
||||
if in != nil && in.Truncated {
|
||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||
// If the TCP request succeeds, the err will reset to nil
|
||||
in, _, err = tcp.Exchange(m, ns)
|
||||
}
|
||||
|
||||
return in, err
|
||||
}
|
||||
|
||||
func formatDNSError(msg *dns.Msg, err error) string {
|
||||
var parts []string
|
||||
|
||||
if msg != nil {
|
||||
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
parts = append(parts, fmt.Sprintf("%v", err))
|
||||
}
|
||||
|
||||
if len(parts) > 0 {
|
||||
return ": " + strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
127
vendor/github.com/go-acme/lego/challenge/dns01/precheck.go
generated
vendored
Normal file
127
vendor/github.com/go-acme/lego/challenge/dns01/precheck.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||
type PreCheckFunc func(fqdn, value string) (bool, error)
|
||||
|
||||
// WrapPreCheckFunc wraps a PreCheckFunc in order to do extra operations before or after
|
||||
// the main check, put it in a loop, etc.
|
||||
type WrapPreCheckFunc func(domain, fqdn, value string, check PreCheckFunc) (bool, error)
|
||||
|
||||
// WrapPreCheck Allow to define checks before notifying ACME that the DNS challenge is ready.
|
||||
func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
|
||||
return func(chlg *Challenge) error {
|
||||
chlg.preCheck.checkFunc = wrap
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddPreCheck Allow to define checks before notifying ACME that the DNS challenge is ready.
|
||||
// Deprecated: use WrapPreCheck instead.
|
||||
func AddPreCheck(preCheck PreCheckFunc) ChallengeOption {
|
||||
// Prevent race condition
|
||||
check := preCheck
|
||||
return func(chlg *Challenge) error {
|
||||
chlg.preCheck.checkFunc = func(_, fqdn, value string, _ PreCheckFunc) (bool, error) {
|
||||
if check == nil {
|
||||
return false, errors.New("invalid preCheck: preCheck is nil")
|
||||
}
|
||||
return check(fqdn, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DisableCompletePropagationRequirement() ChallengeOption {
|
||||
return func(chlg *Challenge) error {
|
||||
chlg.preCheck.requireCompletePropagation = false
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type preCheck struct {
|
||||
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||
checkFunc WrapPreCheckFunc
|
||||
// require the TXT record to be propagated to all authoritative name servers
|
||||
requireCompletePropagation bool
|
||||
}
|
||||
|
||||
func newPreCheck() preCheck {
|
||||
return preCheck{
|
||||
requireCompletePropagation: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (p preCheck) call(domain, fqdn, value string) (bool, error) {
|
||||
if p.checkFunc == nil {
|
||||
return p.checkDNSPropagation(fqdn, value)
|
||||
}
|
||||
|
||||
return p.checkFunc(domain, fqdn, value, p.checkDNSPropagation)
|
||||
}
|
||||
|
||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
|
||||
// Initial attempt to resolve at the recursive NS
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !p.requireCompletePropagation {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if r.Rcode == dns.RcodeSuccess {
|
||||
fqdn = updateDomainWithCName(r, fqdn)
|
||||
}
|
||||
|
||||
authoritativeNss, err := lookupNameservers(fqdn)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
||||
}
|
||||
|
||||
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
|
||||
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
|
||||
for _, ns := range nameservers {
|
||||
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if r.Rcode != dns.RcodeSuccess {
|
||||
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
|
||||
}
|
||||
|
||||
var records []string
|
||||
|
||||
var found bool
|
||||
for _, rr := range r.Answer {
|
||||
if txt, ok := rr.(*dns.TXT); ok {
|
||||
record := strings.Join(txt.Txt, "")
|
||||
records = append(records, record)
|
||||
if record == value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s, value: %s]: %s", ns, fqdn, value, strings.Join(records, " ,"))
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
65
vendor/github.com/go-acme/lego/challenge/http01/http_challenge.go
generated
vendored
Normal file
65
vendor/github.com/go-acme/lego/challenge/http01/http_challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
|
||||
// ChallengePath returns the URL path for the `http-01` challenge
|
||||
func ChallengePath(token string) string {
|
||||
return "/.well-known/acme-challenge/" + token
|
||||
}
|
||||
|
||||
type Challenge struct {
|
||||
core *api.Core
|
||||
validate ValidateFunc
|
||||
provider challenge.Provider
|
||||
}
|
||||
|
||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||
return &Challenge{
|
||||
core: core,
|
||||
validate: validate,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||
c.provider = provider
|
||||
}
|
||||
|
||||
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.HTTP01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.provider.Present(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %v", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: error cleaning up: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
|
||||
chlng.KeyAuthorization = keyAuth
|
||||
return c.validate(c.core, domain, chlng)
|
||||
}
|
96
vendor/github.com/go-acme/lego/challenge/http01/http_challenge_server.go
generated
vendored
Normal file
96
vendor/github.com/go-acme/lego/challenge/http01/http_challenge_server.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// ProviderServer implements ChallengeProvider for `http-01` challenge
|
||||
// It may be instantiated without using the NewProviderServer function if
|
||||
// you want only to use the default values.
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
done chan bool
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewProviderServer creates a new ProviderServer on the selected interface and port.
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 80 respectively.
|
||||
func NewProviderServer(iface, port string) *ProviderServer {
|
||||
return &ProviderServer{iface: iface, port: port}
|
||||
}
|
||||
|
||||
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if s.port == "" {
|
||||
s.port = "80"
|
||||
}
|
||||
|
||||
var err error
|
||||
s.listener, err = net.Listen("tcp", s.GetAddress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
|
||||
}
|
||||
|
||||
s.done = make(chan bool)
|
||||
go s.serve(domain, token, keyAuth)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProviderServer) GetAddress() string {
|
||||
return net.JoinHostPort(s.iface, s.port)
|
||||
}
|
||||
|
||||
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`
|
||||
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
s.listener.Close()
|
||||
<-s.done
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||
path := ChallengePath(token)
|
||||
|
||||
// The handler validates the HOST header and request type.
|
||||
// For validation it then writes the token the server returned with the challenge
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.Host, domain) && r.Method == http.MethodGet {
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
_, err := w.Write([]byte(keyAuth))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Infof("[%s] Served key authentication", domain)
|
||||
} else {
|
||||
log.Warnf("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)
|
||||
_, err := w.Write([]byte("TEST"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
httpServer := &http.Server{Handler: mux}
|
||||
|
||||
// Once httpServer is shut down
|
||||
// we don't want any lingering connections, so disable KeepAlives.
|
||||
httpServer.SetKeepAlivesEnabled(false)
|
||||
|
||||
err := httpServer.Serve(s.listener)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
log.Println(err)
|
||||
}
|
||||
s.done <- true
|
||||
}
|
28
vendor/github.com/go-acme/lego/challenge/provider.go
generated
vendored
Normal file
28
vendor/github.com/go-acme/lego/challenge/provider.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
package challenge
|
||||
|
||||
import "time"
|
||||
|
||||
// Provider enables implementing a custom challenge
|
||||
// provider. Present presents the solution to a challenge available to
|
||||
// be solved. CleanUp will be called by the challenge if Present ends
|
||||
// in a non-error state.
|
||||
type Provider interface {
|
||||
Present(domain, token, keyAuth string) error
|
||||
CleanUp(domain, token, keyAuth string) error
|
||||
}
|
||||
|
||||
// ProviderTimeout allows for implementing a
|
||||
// Provider where an unusually long timeout is required when
|
||||
// waiting for an ACME challenge to be satisfied, such as when
|
||||
// checking for DNS record propagation. If an implementor of a
|
||||
// Provider provides a Timeout method, then the return values
|
||||
// of the Timeout method will be used when appropriate by the acme
|
||||
// package. The interval value is the time between checks.
|
||||
//
|
||||
// The default values used for timeout and interval are 60 seconds and
|
||||
// 2 seconds respectively. These are used when no Timeout method is
|
||||
// defined for the Provider.
|
||||
type ProviderTimeout interface {
|
||||
Provider
|
||||
Timeout() (timeout, interval time.Duration)
|
||||
}
|
25
vendor/github.com/go-acme/lego/challenge/resolver/errors.go
generated
vendored
Normal file
25
vendor/github.com/go-acme/lego/challenge/resolver/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// obtainError is returned when there are specific errors available per domain.
|
||||
type obtainError map[string]error
|
||||
|
||||
func (e obtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||
|
||||
var domains []string
|
||||
for domain := range e {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
sort.Strings(domains)
|
||||
|
||||
for _, domain := range domains {
|
||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
173
vendor/github.com/go-acme/lego/challenge/resolver/prober.go
generated
vendored
Normal file
173
vendor/github.com/go-acme/lego/challenge/resolver/prober.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// Interface for all challenge solvers to implement.
|
||||
type solver interface {
|
||||
Solve(authorization acme.Authorization) error
|
||||
}
|
||||
|
||||
// Interface for challenges like dns, where we can set a record in advance for ALL challenges.
|
||||
// This saves quite a bit of time vs creating the records and solving them serially.
|
||||
type preSolver interface {
|
||||
PreSolve(authorization acme.Authorization) error
|
||||
}
|
||||
|
||||
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
|
||||
type cleanup interface {
|
||||
CleanUp(authorization acme.Authorization) error
|
||||
}
|
||||
|
||||
type sequential interface {
|
||||
Sequential() (bool, time.Duration)
|
||||
}
|
||||
|
||||
// an authz with the solver we have chosen and the index of the challenge associated with it
|
||||
type selectedAuthSolver struct {
|
||||
authz acme.Authorization
|
||||
solver solver
|
||||
}
|
||||
|
||||
type Prober struct {
|
||||
solverManager *SolverManager
|
||||
}
|
||||
|
||||
func NewProber(solverManager *SolverManager) *Prober {
|
||||
return &Prober{
|
||||
solverManager: solverManager,
|
||||
}
|
||||
}
|
||||
|
||||
// Solve Looks through the challenge combinations to find a solvable match.
|
||||
// Then solves the challenges in series and returns.
|
||||
func (p *Prober) Solve(authorizations []acme.Authorization) error {
|
||||
failures := make(obtainError)
|
||||
|
||||
var authSolvers []*selectedAuthSolver
|
||||
var authSolversSequential []*selectedAuthSolver
|
||||
|
||||
// Loop through the resources, basically through the domains.
|
||||
// First pass just selects a solver for each authz.
|
||||
for _, authz := range authorizations {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
if authz.Status == acme.StatusValid {
|
||||
// Boulder might recycle recent validated authz (see issue #267)
|
||||
log.Infof("[%s] acme: authorization already valid; skipping challenge", domain)
|
||||
continue
|
||||
}
|
||||
|
||||
if solvr := p.solverManager.chooseSolver(authz); solvr != nil {
|
||||
authSolver := &selectedAuthSolver{authz: authz, solver: solvr}
|
||||
|
||||
switch s := solvr.(type) {
|
||||
case sequential:
|
||||
if ok, _ := s.Sequential(); ok {
|
||||
authSolversSequential = append(authSolversSequential, authSolver)
|
||||
} else {
|
||||
authSolvers = append(authSolvers, authSolver)
|
||||
}
|
||||
default:
|
||||
authSolvers = append(authSolvers, authSolver)
|
||||
}
|
||||
} else {
|
||||
failures[domain] = fmt.Errorf("[%s] acme: could not determine solvers", domain)
|
||||
}
|
||||
}
|
||||
|
||||
parallelSolve(authSolvers, failures)
|
||||
|
||||
sequentialSolve(authSolversSequential, failures)
|
||||
|
||||
// Be careful not to return an empty failures map,
|
||||
// for even an empty obtainError is a non-nil error value
|
||||
if len(failures) > 0 {
|
||||
return failures
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||
for i, authSolver := range authSolvers {
|
||||
// Submit the challenge
|
||||
domain := challenge.GetTargetedDomain(authSolver.authz)
|
||||
|
||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||
err := solvr.PreSolve(authSolver.authz)
|
||||
if err != nil {
|
||||
failures[domain] = err
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Solve challenge
|
||||
err := authSolver.solver.Solve(authSolver.authz)
|
||||
if err != nil {
|
||||
failures[domain] = err
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
continue
|
||||
}
|
||||
|
||||
// Clean challenge
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
|
||||
if len(authSolvers)-1 > i {
|
||||
solvr := authSolver.solver.(sequential)
|
||||
_, interval := solvr.Sequential()
|
||||
log.Infof("sequence: wait for %s", interval)
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||
// For all valid preSolvers, first submit the challenges so they have max time to propagate
|
||||
for _, authSolver := range authSolvers {
|
||||
authz := authSolver.authz
|
||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||
err := solvr.PreSolve(authz)
|
||||
if err != nil {
|
||||
failures[challenge.GetTargetedDomain(authz)] = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Clean all created TXT records
|
||||
for _, authSolver := range authSolvers {
|
||||
cleanUp(authSolver.solver, authSolver.authz)
|
||||
}
|
||||
}()
|
||||
|
||||
// Finally solve all challenges for real
|
||||
for _, authSolver := range authSolvers {
|
||||
authz := authSolver.authz
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
if failures[domain] != nil {
|
||||
// already failed in previous loop
|
||||
continue
|
||||
}
|
||||
|
||||
err := authSolver.solver.Solve(authz)
|
||||
if err != nil {
|
||||
failures[domain] = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanUp(solvr solver, authz acme.Authorization) {
|
||||
if solvr, ok := solvr.(cleanup); ok {
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
err := solvr.CleanUp(authz)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: error cleaning up: %v ", domain, err)
|
||||
}
|
||||
}
|
||||
}
|
169
vendor/github.com/go-acme/lego/challenge/resolver/solver_manager.go
generated
vendored
Normal file
169
vendor/github.com/go-acme/lego/challenge/resolver/solver_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,169 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/challenge/http01"
|
||||
"github.com/go-acme/lego/challenge/tlsalpn01"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
type byType []acme.Challenge
|
||||
|
||||
func (a byType) Len() int { return len(a) }
|
||||
func (a byType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byType) Less(i, j int) bool { return a[i].Type > a[j].Type }
|
||||
|
||||
type SolverManager struct {
|
||||
core *api.Core
|
||||
solvers map[challenge.Type]solver
|
||||
}
|
||||
|
||||
func NewSolversManager(core *api.Core) *SolverManager {
|
||||
return &SolverManager{
|
||||
solvers: map[challenge.Type]solver{},
|
||||
core: core,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
|
||||
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
|
||||
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
|
||||
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error {
|
||||
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDNS01Provider specifies a custom provider p that can solve the given DNS-01 challenge.
|
||||
func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.ChallengeOption) error {
|
||||
c.solvers[challenge.DNS01] = dns01.NewChallenge(c.core, validate, p, opts...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove Remove a challenge type from the available solvers.
|
||||
func (c *SolverManager) Remove(chlgType challenge.Type) {
|
||||
delete(c.solvers, chlgType)
|
||||
}
|
||||
|
||||
// Checks all challenges from the server in order and returns the first matching solver.
|
||||
func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
|
||||
// Allow to have a deterministic challenge order
|
||||
sort.Sort(byType(authz.Challenges))
|
||||
|
||||
domain := challenge.GetTargetedDomain(authz)
|
||||
for _, chlg := range authz.Challenges {
|
||||
if solvr, ok := c.solvers[challenge.Type(chlg.Type)]; ok {
|
||||
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
|
||||
return solvr
|
||||
}
|
||||
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
||||
chlng, err := core.Challenges.New(chlg.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initiate challenge: %v", err)
|
||||
}
|
||||
|
||||
valid, err := checkChallengeStatus(chlng)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if valid {
|
||||
log.Infof("[%s] The server validated our request", domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
ra, err := strconv.Atoi(chlng.RetryAfter)
|
||||
if err != nil {
|
||||
// The ACME server MUST return a Retry-After.
|
||||
// If it doesn't, we'll just poll hard.
|
||||
// Boulder does not implement the ability to retry challenges or the Retry-After header.
|
||||
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
|
||||
ra = 5
|
||||
}
|
||||
initialInterval := time.Duration(ra) * time.Second
|
||||
|
||||
bo := backoff.NewExponentialBackOff()
|
||||
bo.InitialInterval = initialInterval
|
||||
bo.MaxInterval = 10 * initialInterval
|
||||
bo.MaxElapsedTime = 100 * initialInterval
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// After the path is sent, the ACME server will access our server.
|
||||
// Repeatedly check the server for an updated status on our request.
|
||||
operation := func() error {
|
||||
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
valid, err := checkAuthorizationStatus(authz)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
if valid {
|
||||
log.Infof("[%s] The server validated our request", domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("the server didn't respond to our request")
|
||||
}
|
||||
|
||||
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||
}
|
||||
|
||||
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
||||
switch chlng.Status {
|
||||
case acme.StatusValid:
|
||||
return true, nil
|
||||
case acme.StatusPending, acme.StatusProcessing:
|
||||
return false, nil
|
||||
case acme.StatusInvalid:
|
||||
return false, chlng.Error
|
||||
default:
|
||||
return false, errors.New("the server returned an unexpected state")
|
||||
}
|
||||
}
|
||||
|
||||
func checkAuthorizationStatus(authz acme.Authorization) (bool, error) {
|
||||
switch authz.Status {
|
||||
case acme.StatusValid:
|
||||
return true, nil
|
||||
case acme.StatusPending, acme.StatusProcessing:
|
||||
return false, nil
|
||||
case acme.StatusDeactivated, acme.StatusExpired, acme.StatusRevoked:
|
||||
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||
case acme.StatusInvalid:
|
||||
for _, chlg := range authz.Challenges {
|
||||
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
|
||||
return false, chlg.Error
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||
default:
|
||||
return false, errors.New("the server returned an unexpected state")
|
||||
}
|
||||
}
|
129
vendor/github.com/go-acme/lego/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
Normal file
129
vendor/github.com/go-acme/lego/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
package tlsalpn01
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/acme"
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1
|
||||
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||
|
||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||
|
||||
type Challenge struct {
|
||||
core *api.Core
|
||||
validate ValidateFunc
|
||||
provider challenge.Provider
|
||||
}
|
||||
|
||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||
return &Challenge{
|
||||
core: core,
|
||||
validate: validate,
|
||||
provider: provider,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||
c.provider = provider
|
||||
}
|
||||
|
||||
// Solve manages the provider to validate and solve the challenge.
|
||||
func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||
domain := authz.Identifier.Value
|
||||
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", challenge.GetTargetedDomain(authz))
|
||||
|
||||
chlng, err := challenge.FindChallenge(challenge.TLSALPN01, authz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := c.core.GetKeyAuthorization(chlng.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] acme: error presenting token: %v", challenge.GetTargetedDomain(authz), err)
|
||||
}
|
||||
defer func() {
|
||||
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] acme: error cleaning up: %v", challenge.GetTargetedDomain(authz), err)
|
||||
}
|
||||
}()
|
||||
|
||||
chlng.KeyAuthorization = keyAuth
|
||||
return c.validate(c.core, domain, chlng)
|
||||
}
|
||||
|
||||
// ChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
|
||||
// and domain name for the `tls-alpn-01` challenge.
|
||||
func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
||||
// Compute the SHA-256 digest of the key authorization.
|
||||
zBytes := sha256.Sum256([]byte(keyAuth))
|
||||
|
||||
value, err := asn1.Marshal(zBytes[:sha256.Size])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Add the keyAuth digest as the acmeValidation-v1 extension
|
||||
// (marked as critical such that it won't be used by non-ACME software).
|
||||
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-3
|
||||
extensions := []pkix.Extension{
|
||||
{
|
||||
Id: idPeAcmeIdentifierV1,
|
||||
Critical: true,
|
||||
Value: value,
|
||||
},
|
||||
}
|
||||
|
||||
// Generate a new RSA key for the certificates.
|
||||
tempPrivateKey, err := certcrypto.GeneratePrivateKey(certcrypto.RSA2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivateKey := tempPrivateKey.(*rsa.PrivateKey)
|
||||
|
||||
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
|
||||
tempCertPEM, err := certcrypto.GeneratePemCert(rsaPrivateKey, domain, extensions)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Encode the private key into a PEM format. We'll need to use it to generate the x509 keypair.
|
||||
rsaPrivatePEM := certcrypto.PEMEncode(rsaPrivateKey)
|
||||
|
||||
return tempCertPEM, rsaPrivatePEM, nil
|
||||
}
|
||||
|
||||
// ChallengeCert returns a certificate with the acmeValidation-v1 extension
|
||||
// and domain name for the `tls-alpn-01` challenge.
|
||||
func ChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
|
||||
tempCertPEM, rsaPrivatePEM, err := ChallengeBlocks(domain, keyAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(tempCertPEM, rsaPrivatePEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
95
vendor/github.com/go-acme/lego/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
Normal file
95
vendor/github.com/go-acme/lego/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
package tlsalpn01
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// ACMETLS1Protocol is the ALPN Protocol ID for the ACME-TLS/1 Protocol.
|
||||
ACMETLS1Protocol = "acme-tls/1"
|
||||
|
||||
// defaultTLSPort is the port that the ProviderServer will default to
|
||||
// when no other port is provided.
|
||||
defaultTLSPort = "443"
|
||||
)
|
||||
|
||||
// ProviderServer implements ChallengeProvider for `TLS-ALPN-01` challenge.
|
||||
// It may be instantiated without using the NewProviderServer
|
||||
// if you want only to use the default values.
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewProviderServer creates a new ProviderServer on the selected interface and port.
|
||||
// Setting iface and / or port to an empty string will make the server fall back to
|
||||
// the "any" interface and port 443 respectively.
|
||||
func NewProviderServer(iface, port string) *ProviderServer {
|
||||
return &ProviderServer{iface: iface, port: port}
|
||||
}
|
||||
|
||||
func (s *ProviderServer) GetAddress() string {
|
||||
return net.JoinHostPort(s.iface, s.port)
|
||||
}
|
||||
|
||||
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
||||
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if s.port == "" {
|
||||
// Fallback to port 443 if the port was not provided.
|
||||
s.port = defaultTLSPort
|
||||
}
|
||||
|
||||
// Generate the challenge certificate using the provided keyAuth and domain.
|
||||
cert, err := ChallengeCert(domain, keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Place the generated certificate with the extension into the TLS config
|
||||
// so that it can serve the correct details.
|
||||
tlsConf := new(tls.Config)
|
||||
tlsConf.Certificates = []tls.Certificate{*cert}
|
||||
|
||||
// We must set that the `acme-tls/1` application level protocol is supported
|
||||
// so that the protocol negotiation can succeed. Reference:
|
||||
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01#section-5.2
|
||||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||
|
||||
// Create the listener with the created tls.Config.
|
||||
s.listener, err = tls.Listen("tcp", s.GetAddress(), tlsConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTPS server for challenge -> %v", err)
|
||||
}
|
||||
|
||||
// Shut the server down when we're finished.
|
||||
go func() {
|
||||
err := http.Serve(s.listener, nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp closes the HTTPS server.
|
||||
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server was created, close it.
|
||||
if err := s.listener.Close(); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
74
vendor/github.com/go-acme/lego/lego/client.go
generated
vendored
Normal file
74
vendor/github.com/go-acme/lego/lego/client.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
package lego
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-acme/lego/acme/api"
|
||||
"github.com/go-acme/lego/certificate"
|
||||
"github.com/go-acme/lego/challenge/resolver"
|
||||
"github.com/go-acme/lego/registration"
|
||||
)
|
||||
|
||||
// Client is the user-friendly way to ACME
|
||||
type Client struct {
|
||||
Certificate *certificate.Certifier
|
||||
Challenge *resolver.SolverManager
|
||||
Registration *registration.Registrar
|
||||
core *api.Core
|
||||
}
|
||||
|
||||
// NewClient creates a new ACME client on behalf of the user.
|
||||
// The client will depend on the ACME directory located at CADirURL for the rest of its actions.
|
||||
// A private key of type keyType (see KeyType constants) will be generated when requesting a new certificate if one isn't provided.
|
||||
func NewClient(config *Config) (*Client, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("a configuration must be provided")
|
||||
}
|
||||
|
||||
_, err := url.Parse(config.CADirURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.HTTPClient == nil {
|
||||
return nil, errors.New("the HTTP client cannot be nil")
|
||||
}
|
||||
|
||||
privateKey := config.User.GetPrivateKey()
|
||||
if privateKey == nil {
|
||||
return nil, errors.New("private key was nil")
|
||||
}
|
||||
|
||||
var kid string
|
||||
if reg := config.User.GetRegistration(); reg != nil {
|
||||
kid = reg.URI
|
||||
}
|
||||
|
||||
core, err := api.New(config.HTTPClient, config.UserAgent, config.CADirURL, kid, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
solversManager := resolver.NewSolversManager(core)
|
||||
|
||||
prober := resolver.NewProber(solversManager)
|
||||
certifier := certificate.NewCertifier(core, prober, certificate.CertifierOptions{KeyType: config.Certificate.KeyType, Timeout: config.Certificate.Timeout})
|
||||
|
||||
return &Client{
|
||||
Certificate: certifier,
|
||||
Challenge: solversManager,
|
||||
Registration: registration.NewRegistrar(core, config.User),
|
||||
core: core,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetToSURL returns the current ToS URL from the Directory
|
||||
func (c *Client) GetToSURL() string {
|
||||
return c.core.GetDirectory().Meta.TermsOfService
|
||||
}
|
||||
|
||||
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
|
||||
func (c *Client) GetExternalAccountRequired() bool {
|
||||
return c.core.GetDirectory().Meta.ExternalAccountRequired
|
||||
}
|
104
vendor/github.com/go-acme/lego/lego/client_config.go
generated
vendored
Normal file
104
vendor/github.com/go-acme/lego/lego/client_config.go
generated
vendored
Normal file
|
@ -0,0 +1,104 @@
|
|||
package lego
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/certcrypto"
|
||||
"github.com/go-acme/lego/registration"
|
||||
)
|
||||
|
||||
const (
|
||||
// caCertificatesEnvVar is the environment variable name that can be used to
|
||||
// specify the path to PEM encoded CA Certificates that can be used to
|
||||
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
||||
// the system-wide trusted root list.
|
||||
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
||||
|
||||
// caServerNameEnvVar is the environment variable name that can be used to
|
||||
// specify the CA server name that can be used to
|
||||
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
||||
// the system-wide trusted root list.
|
||||
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
||||
|
||||
// LEDirectoryProduction URL to the Let's Encrypt production
|
||||
LEDirectoryProduction = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
|
||||
// LEDirectoryStaging URL to the Let's Encrypt staging
|
||||
LEDirectoryStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CADirURL string
|
||||
User registration.User
|
||||
UserAgent string
|
||||
HTTPClient *http.Client
|
||||
Certificate CertificateConfig
|
||||
}
|
||||
|
||||
func NewConfig(user registration.User) *Config {
|
||||
return &Config{
|
||||
CADirURL: LEDirectoryProduction,
|
||||
User: user,
|
||||
HTTPClient: createDefaultHTTPClient(),
|
||||
Certificate: CertificateConfig{
|
||||
KeyType: certcrypto.RSA2048,
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CertificateConfig struct {
|
||||
KeyType certcrypto.KeyType
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
|
||||
// and potentially a custom *x509.CertPool
|
||||
// based on the caCertificatesEnvVar environment variable (see the `initCertPool` function)
|
||||
func createDefaultHTTPClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
TLSHandshakeTimeout: 15 * time.Second,
|
||||
ResponseHeaderTimeout: 15 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
ServerName: os.Getenv(caServerNameEnvVar),
|
||||
RootCAs: initCertPool(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
||||
// found in the filepath specified in the caCertificatesEnvVar OS environment
|
||||
// variable. If the caCertificatesEnvVar is not set then initCertPool will
|
||||
// return nil. If there is an error creating a *x509.CertPool from the provided
|
||||
// caCertificatesEnvVar value then initCertPool will panic.
|
||||
func initCertPool() *x509.CertPool {
|
||||
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
|
||||
customCAs, err := ioutil.ReadFile(customCACertsPath)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error reading %s=%q: %v",
|
||||
caCertificatesEnvVar, customCACertsPath, err))
|
||||
}
|
||||
certPool := x509.NewCertPool()
|
||||
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
||||
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
|
||||
caCertificatesEnvVar, customCACertsPath, err))
|
||||
}
|
||||
return certPool
|
||||
}
|
||||
return nil
|
||||
}
|
59
vendor/github.com/go-acme/lego/log/logger.go
generated
vendored
Normal file
59
vendor/github.com/go-acme/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 StdLogger = log.New(os.Stdout, "", log.LstdFlags)
|
||||
|
||||
// StdLogger interface for Standard Logger.
|
||||
type StdLogger interface {
|
||||
Fatal(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// Fatal writes a log entry.
|
||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||
func Fatal(args ...interface{}) {
|
||||
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{}) {
|
||||
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{}) {
|
||||
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{}) {
|
||||
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{}) {
|
||||
Logger.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Warnf writes a log entry.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
Printf("[WARN] "+format, args...)
|
||||
}
|
||||
|
||||
// Infof writes a log entry.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
Printf("[INFO] "+format, args...)
|
||||
}
|
163
vendor/github.com/go-acme/lego/platform/config/env/env.go
generated
vendored
Normal file
163
vendor/github.com/go-acme/lego/platform/config/env/env.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// Get environment variables
|
||||
func Get(names ...string) (map[string]string, error) {
|
||||
values := map[string]string{}
|
||||
|
||||
var missingEnvVars []string
|
||||
for _, envVar := range names {
|
||||
value := GetOrFile(envVar)
|
||||
if value == "" {
|
||||
missingEnvVars = append(missingEnvVars, envVar)
|
||||
}
|
||||
values[envVar] = value
|
||||
}
|
||||
|
||||
if len(missingEnvVars) > 0 {
|
||||
return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ","))
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetWithFallback Get environment variable values
|
||||
// The first name in each group is use as key in the result map
|
||||
//
|
||||
// // LEGO_ONE="ONE"
|
||||
// // LEGO_TWO="TWO"
|
||||
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
||||
// // => "LEGO_ONE" = "ONE"
|
||||
//
|
||||
// ----
|
||||
//
|
||||
// // LEGO_ONE=""
|
||||
// // LEGO_TWO="TWO"
|
||||
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
||||
// // => "LEGO_ONE" = "TWO"
|
||||
//
|
||||
// ----
|
||||
//
|
||||
// // LEGO_ONE=""
|
||||
// // LEGO_TWO=""
|
||||
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
||||
// // => error
|
||||
//
|
||||
func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
||||
values := map[string]string{}
|
||||
|
||||
var missingEnvVars []string
|
||||
for _, names := range groups {
|
||||
if len(names) == 0 {
|
||||
return nil, errors.New("undefined environment variable names")
|
||||
}
|
||||
|
||||
value, envVar := getOneWithFallback(names[0], names[1:]...)
|
||||
if len(value) == 0 {
|
||||
missingEnvVars = append(missingEnvVars, envVar)
|
||||
continue
|
||||
}
|
||||
values[envVar] = value
|
||||
}
|
||||
|
||||
if len(missingEnvVars) > 0 {
|
||||
return nil, fmt.Errorf("some credentials information are missing: %s", strings.Join(missingEnvVars, ","))
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func getOneWithFallback(main string, names ...string) (string, string) {
|
||||
value := GetOrFile(main)
|
||||
if len(value) > 0 {
|
||||
return value, main
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
value := GetOrFile(name)
|
||||
if len(value) > 0 {
|
||||
return value, main
|
||||
}
|
||||
}
|
||||
|
||||
return "", main
|
||||
}
|
||||
|
||||
// GetOrDefaultInt returns the given environment variable value as an integer.
|
||||
// Returns the default if the envvar cannot be coopered to an int, or is not found.
|
||||
func GetOrDefaultInt(envVar string, defaultValue int) int {
|
||||
v, err := strconv.Atoi(GetOrFile(envVar))
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrDefaultSecond returns the given environment variable value as an time.Duration (second).
|
||||
// Returns the default if the envvar cannot be coopered to an int, or is not found.
|
||||
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
|
||||
v := GetOrDefaultInt(envVar, -1)
|
||||
if v < 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return time.Duration(v) * time.Second
|
||||
}
|
||||
|
||||
// GetOrDefaultString returns the given environment variable value as a string.
|
||||
// Returns the default if the envvar cannot be find.
|
||||
func GetOrDefaultString(envVar string, defaultValue string) string {
|
||||
v := GetOrFile(envVar)
|
||||
if len(v) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrDefaultBool returns the given environment variable value as a boolean.
|
||||
// Returns the default if the envvar cannot be coopered to a boolean, or is not found.
|
||||
func GetOrDefaultBool(envVar string, defaultValue bool) bool {
|
||||
v, err := strconv.ParseBool(GetOrFile(envVar))
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// GetOrFile Attempts to resolve 'key' as an environment variable.
|
||||
// Failing that, it will check to see if '<key>_FILE' exists.
|
||||
// If so, it will attempt to read from the referenced file to populate a value.
|
||||
func GetOrFile(envVar string) string {
|
||||
envVarValue := os.Getenv(envVar)
|
||||
if envVarValue != "" {
|
||||
return envVarValue
|
||||
}
|
||||
|
||||
fileVar := envVar + "_FILE"
|
||||
fileVarValue := os.Getenv(fileVar)
|
||||
if fileVarValue == "" {
|
||||
return envVarValue
|
||||
}
|
||||
|
||||
fileContents, err := ioutil.ReadFile(fileVarValue)
|
||||
if err != nil {
|
||||
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(fileContents)
|
||||
}
|
33
vendor/github.com/go-acme/lego/platform/wait/wait.go
generated
vendored
Normal file
33
vendor/github.com/go-acme/lego/platform/wait/wait.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
package wait
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// For polls the given function 'f', once every 'interval', up to 'timeout'.
|
||||
func For(msg string, timeout, interval time.Duration, f func() (bool, error)) error {
|
||||
log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
|
||||
|
||||
var lastErr string
|
||||
timeUp := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-timeUp:
|
||||
return fmt.Errorf("time limit exceeded: last error: %s", lastErr)
|
||||
default:
|
||||
}
|
||||
|
||||
stop, err := f()
|
||||
if stop {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
lastErr = err.Error()
|
||||
}
|
||||
|
||||
time.Sleep(interval)
|
||||
}
|
||||
}
|
162
vendor/github.com/go-acme/lego/providers/dns/acmedns/acmedns.go
generated
vendored
Normal file
162
vendor/github.com/go-acme/lego/providers/dns/acmedns/acmedns.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Package acmedns implements a DNS provider for solving DNS-01 challenges using Joohoi's acme-dns project.
|
||||
// For more information see the ACME-DNS homepage: https://github.com/joohoi/acme-dns
|
||||
package acmedns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cpu/goacmedns"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
// envNamespace is the prefix for ACME-DNS environment variables.
|
||||
envNamespace = "ACME_DNS_"
|
||||
// apiBaseEnvVar is the environment variable name for the ACME-DNS API address
|
||||
// (e.g. https://acmedns.your-domain.com).
|
||||
apiBaseEnvVar = envNamespace + "API_BASE"
|
||||
// storagePathEnvVar is the environment variable name for the ACME-DNS JSON account data file.
|
||||
// A per-domain account will be registered/persisted to this file and used for TXT updates.
|
||||
storagePathEnvVar = envNamespace + "STORAGE_PATH"
|
||||
)
|
||||
|
||||
// acmeDNSClient is an interface describing the goacmedns.Client functions the DNSProvider uses.
|
||||
// It makes it easier for tests to shim a mock Client into the DNSProvider.
|
||||
type acmeDNSClient interface {
|
||||
// UpdateTXTRecord updates the provided account's TXT record
|
||||
// to the given value or returns an error.
|
||||
UpdateTXTRecord(goacmedns.Account, string) error
|
||||
// RegisterAccount registers and returns a new account
|
||||
// with the given allowFrom restriction or returns an error.
|
||||
RegisterAccount([]string) (goacmedns.Account, error)
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface for
|
||||
// an ACME-DNS server.
|
||||
type DNSProvider struct {
|
||||
client acmeDNSClient
|
||||
storage goacmedns.Storage
|
||||
}
|
||||
|
||||
// NewDNSProvider creates an ACME-DNS provider using file based account storage.
|
||||
// Its configuration is loaded from the environment by reading apiBaseEnvVar and storagePathEnvVar.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(apiBaseEnvVar, storagePathEnvVar)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme-dns: %v", err)
|
||||
}
|
||||
|
||||
client := goacmedns.NewClient(values[apiBaseEnvVar])
|
||||
storage := goacmedns.NewFileStorage(values[storagePathEnvVar], 0600)
|
||||
return NewDNSProviderClient(client, storage)
|
||||
}
|
||||
|
||||
// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given acmeDNSClient and goacmedns.Storage.
|
||||
func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNSProvider, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("ACME-DNS Client must be not nil")
|
||||
}
|
||||
|
||||
if storage == nil {
|
||||
return nil, errors.New("ACME-DNS Storage must be not nil")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
storage: storage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ErrCNAMERequired is returned by Present when the Domain indicated had no
|
||||
// existing ACME-DNS account in the Storage and additional setup is required.
|
||||
// The user must create a CNAME in the DNS zone for Domain that aliases FQDN
|
||||
// to Target in order to complete setup for the ACME-DNS account that was created.
|
||||
type ErrCNAMERequired struct {
|
||||
// The Domain that is being issued for.
|
||||
Domain string
|
||||
// The alias of the CNAME (left hand DNS label).
|
||||
FQDN string
|
||||
// The RDATA of the CNAME (right hand side, canonical name).
|
||||
Target string
|
||||
}
|
||||
|
||||
// Error returns a descriptive message for the ErrCNAMERequired instance telling
|
||||
// the user that a CNAME needs to be added to the DNS zone of c.Domain before
|
||||
// the ACME-DNS hook will work. The CNAME to be created should be of the form:
|
||||
// {{ c.FQDN }} CNAME {{ c.Target }}
|
||||
func (e ErrCNAMERequired) Error() string {
|
||||
return fmt.Sprintf("acme-dns: new account created for %q. "+
|
||||
"To complete setup for %q you must provision the following "+
|
||||
"CNAME in your DNS zone and re-run this provider when it is "+
|
||||
"in place:\n"+
|
||||
"%s CNAME %s.",
|
||||
e.Domain, e.Domain, e.FQDN, e.Target)
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the DNS-01 challenge.
|
||||
// If there is an existing account for the domain in the provider's storage
|
||||
// then it will be used to set the challenge response TXT record with the ACME-DNS server and issuance will continue.
|
||||
// If there is not an account for the given domain present in the DNSProvider storage
|
||||
// one will be created and registered with the ACME DNS server and an ErrCNAMERequired error is returned.
|
||||
// This will halt issuance and indicate to the user that a one-time manual setup is required for the domain.
|
||||
func (d *DNSProvider) Present(domain, _, keyAuth string) error {
|
||||
// Compute the challenge response FQDN and TXT value for the domain based
|
||||
// on the keyAuth.
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// Check if credentials were previously saved for this domain.
|
||||
account, err := d.storage.Fetch(domain)
|
||||
// Errors other than goacmeDNS.ErrDomainNotFound are unexpected.
|
||||
if err != nil && err != goacmedns.ErrDomainNotFound {
|
||||
return err
|
||||
}
|
||||
if err == goacmedns.ErrDomainNotFound {
|
||||
// The account did not exist. Create a new one and return an error
|
||||
// indicating the required one-time manual CNAME setup.
|
||||
return d.register(domain, fqdn)
|
||||
}
|
||||
|
||||
// Update the acme-dns TXT record.
|
||||
return d.client.UpdateTXTRecord(account, value)
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters. It is not
|
||||
// implemented for the ACME-DNS provider.
|
||||
func (d *DNSProvider) CleanUp(_, _, _ string) error {
|
||||
// ACME-DNS doesn't support the notion of removing a record.
|
||||
// For users of ACME-DNS it is expected the stale records remain in-place.
|
||||
return nil
|
||||
}
|
||||
|
||||
// register creates a new ACME-DNS account for the given domain.
|
||||
// If account creation works as expected a ErrCNAMERequired error is returned describing
|
||||
// the one-time manual CNAME setup required to complete setup of the ACME-DNS hook for the domain.
|
||||
// If any other error occurs it is returned as-is.
|
||||
func (d *DNSProvider) register(domain, fqdn string) error {
|
||||
// TODO(@cpu): Read CIDR whitelists from the environment
|
||||
newAcct, err := d.client.RegisterAccount(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the new account in the storage and call save to persist the data.
|
||||
err = d.storage.Put(domain, newAcct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.storage.Save()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop issuance by returning an error.
|
||||
// The user needs to perform a manual one-time CNAME setup in their DNS zone
|
||||
// to complete the setup of the new account we created.
|
||||
return ErrCNAMERequired{
|
||||
Domain: domain,
|
||||
FQDN: fqdn,
|
||||
Target: newAcct.FullDomain,
|
||||
}
|
||||
}
|
220
vendor/github.com/go-acme/lego/providers/dns/alidns/alidns.go
generated
vendored
Normal file
220
vendor/github.com/go-acme/lego/providers/dns/alidns/alidns.go
generated
vendored
Normal file
|
@ -0,0 +1,220 @@
|
|||
// Package alidns implements a DNS provider for solving the DNS-01 challenge using Alibaba Cloud DNS.
|
||||
package alidns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultRegionID = "cn-hangzhou"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
SecretKey string
|
||||
RegionID string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("ALICLOUD_TTL", 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("ALICLOUD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("ALICLOUD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond("ALICLOUD_HTTP_TIMEOUT", 10*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *alidns.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Alibaba Cloud DNS.
|
||||
// Credentials must be passed in the environment variables: ALICLOUD_ACCESS_KEY and ALICLOUD_SECRET_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("ALICLOUD_ACCESS_KEY", "ALICLOUD_SECRET_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["ALICLOUD_ACCESS_KEY"]
|
||||
config.SecretKey = values["ALICLOUD_SECRET_KEY"]
|
||||
config.RegionID = env.GetOrFile("ALICLOUD_REGION_ID")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for alidns.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("alicloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" || config.SecretKey == "" {
|
||||
return nil, fmt.Errorf("alicloud: credentials missing")
|
||||
}
|
||||
|
||||
if len(config.RegionID) == 0 {
|
||||
config.RegionID = defaultRegionID
|
||||
}
|
||||
|
||||
conf := sdk.NewConfig().WithTimeout(config.HTTPTimeout)
|
||||
credential := credentials.NewAccessKeyCredential(config.APIKey, config.SecretKey)
|
||||
|
||||
client, err := alidns.NewClientWithOptions(config.RegionID, conf, credential)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alicloud: credentials failed: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
_, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
recordAttributes := d.newTxtRecord(zoneName, fqdn, value)
|
||||
|
||||
_, err = d.client.AddDomainRecord(recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: API call failed: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
_, _, err = d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
request := alidns.CreateDeleteDomainRecordRequest()
|
||||
request.RecordId = rec.RecordId
|
||||
_, err = d.client.DeleteDomainRecord(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("alicloud: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||
request := alidns.CreateDescribeDomainsRequest()
|
||||
|
||||
var domains []alidns.Domain
|
||||
startPage := 1
|
||||
|
||||
for {
|
||||
request.PageNumber = requests.NewInteger(startPage)
|
||||
|
||||
response, err := d.client.DescribeDomains(request)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
domains = append(domains, response.Domains.Domain...)
|
||||
|
||||
if response.PageNumber*response.PageSize >= response.TotalCount {
|
||||
break
|
||||
}
|
||||
|
||||
startPage++
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone alidns.Domain
|
||||
for _, zone := range domains {
|
||||
if zone.DomainName == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.DomainId == "" {
|
||||
return "", "", fmt.Errorf("zone %s not found in AliDNS for domain %s", authZone, domain)
|
||||
}
|
||||
return fmt.Sprintf("%v", hostedZone.DomainId), hostedZone.DomainName, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newTxtRecord(zone, fqdn, value string) *alidns.AddDomainRecordRequest {
|
||||
request := alidns.CreateAddDomainRecordRequest()
|
||||
request.Type = "TXT"
|
||||
request.DomainName = zone
|
||||
request.RR = d.extractRecordName(fqdn, zone)
|
||||
request.Value = value
|
||||
request.TTL = requests.NewInteger(d.config.TTL)
|
||||
return request
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]alidns.Record, error) {
|
||||
_, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := alidns.CreateDescribeDomainRecordsRequest()
|
||||
request.DomainName = zoneName
|
||||
request.PageSize = requests.NewInteger(500)
|
||||
|
||||
var records []alidns.Record
|
||||
|
||||
result, err := d.client.DescribeDomainRecords(request)
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, zoneName)
|
||||
for _, record := range result.DomainRecords.Record {
|
||||
if record.RR == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
191
vendor/github.com/go-acme/lego/providers/dns/auroradns/auroradns.go
generated
vendored
Normal file
191
vendor/github.com/go-acme/lego/providers/dns/auroradns/auroradns.go
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Package auroradns implements a DNS provider for solving the DNS-01 challenge using Aurora DNS.
|
||||
package auroradns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/nrdcg/auroradns"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.auroradns.eu"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
UserID string
|
||||
Key string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("AURORA_TTL", 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AURORA_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AURORA_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for AuroraDNS
|
||||
type DNSProvider struct {
|
||||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
config *Config
|
||||
client *auroradns.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for AuroraDNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// AURORA_USER_ID and AURORA_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("AURORA_USER_ID", "AURORA_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = env.GetOrFile("AURORA_ENDPOINT")
|
||||
config.UserID = values["AURORA_USER_ID"]
|
||||
config.Key = values["AURORA_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for AuroraDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("aurora: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.UserID == "" || config.Key == "" {
|
||||
return nil, errors.New("aurora: some credentials information are missing")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
tr, err := auroradns.NewTokenTransport(config.UserID, config.Key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
client, err := auroradns.NewClient(tr.Client(), auroradns.WithBaseURL(config.BaseURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("aurora: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: client,
|
||||
recordIDs: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a record with a secret
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
// 1. Aurora will happily create the TXT record when it is provided a fqdn,
|
||||
// but it will only appear in the control panel and will not be
|
||||
// propagated to DNS servers. Extract and use subdomain instead.
|
||||
// 2. A trailing dot in the fqdn will cause Aurora to add a trailing dot to
|
||||
// the subdomain, resulting in _acme-challenge..<domain> rather
|
||||
// than _acme-challenge.<domain>
|
||||
|
||||
subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]
|
||||
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not create record: %v", err)
|
||||
}
|
||||
|
||||
record := auroradns.Record{
|
||||
RecordType: "TXT",
|
||||
Name: subdomain,
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
newRecord, _, err := d.client.CreateRecord(zone.ID, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not create record: %v", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = newRecord.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a given record that was generated by Present
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown recordID for %q", fqdn)
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain: %q. %v", domain, err)
|
||||
}
|
||||
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = d.client.DeleteRecord(zone.ID, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneInformationByName(name string) (auroradns.Zone, error) {
|
||||
zs, _, err := d.client.ListZones()
|
||||
if err != nil {
|
||||
return auroradns.Zone{}, err
|
||||
}
|
||||
|
||||
for _, element := range zs {
|
||||
if element.Name == name {
|
||||
return element, nil
|
||||
}
|
||||
}
|
||||
|
||||
return auroradns.Zone{}, fmt.Errorf("could not find Zone record")
|
||||
}
|
274
vendor/github.com/go-acme/lego/providers/dns/azure/azure.go
generated
vendored
Normal file
274
vendor/github.com/go-acme/lego/providers/dns/azure/azure.go
generated
vendored
Normal file
|
@ -0,0 +1,274 @@
|
|||
// Package azure implements a DNS provider for solving the DNS-01 challenge using azure DNS.
|
||||
// Azure doesn't like trailing dots on domain names, most of the acme code does.
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-09-01/dns"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultMetadataEndpoint = "http://169.254.169.254"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
// optional if using instance metadata service
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
TenantID string
|
||||
|
||||
SubscriptionID string
|
||||
ResourceGroup string
|
||||
|
||||
MetadataEndpoint string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("AZURE_TTL", 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AZURE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("AZURE_POLLING_INTERVAL", 2*time.Second),
|
||||
MetadataEndpoint: env.GetOrFile("AZURE_METADATA_ENDPOINT"),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
authorizer autorest.Authorizer
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for azure.
|
||||
// Credentials can be passed in the environment variables:
|
||||
// AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
|
||||
// If the credentials are _not_ set via the environment,
|
||||
// then it will attempt to get a bearer token via the instance metadata service.
|
||||
// see: https://github.com/Azure/go-autorest/blob/v10.14.0/autorest/azure/auth/auth.go#L38-L42
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.SubscriptionID = env.GetOrFile("AZURE_SUBSCRIPTION_ID")
|
||||
config.ResourceGroup = env.GetOrFile("AZURE_RESOURCE_GROUP")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Azure.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("azure: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.HTTPClient == nil {
|
||||
config.HTTPClient = http.DefaultClient
|
||||
}
|
||||
|
||||
authorizer, err := getAuthorizer(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.SubscriptionID == "" {
|
||||
subsID, err := getMetadata(config, "subscriptionId")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
if subsID == "" {
|
||||
return nil, errors.New("azure: SubscriptionID is missing")
|
||||
}
|
||||
config.SubscriptionID = subsID
|
||||
}
|
||||
|
||||
if config.ResourceGroup == "" {
|
||||
resGroup, err := getMetadata(config, "resourceGroupName")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
if resGroup == "" {
|
||||
return nil, errors.New("azure: ResourceGroup is missing")
|
||||
}
|
||||
config.ResourceGroup = resGroup
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, authorizer: authorizer}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneID(ctx, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
relative := toRelativeRecord(fqdn, dns01.ToFqdn(zone))
|
||||
|
||||
// Get existing record set
|
||||
rset, err := rsc.Get(ctx, d.config.ResourceGroup, zone, relative, dns.TXT)
|
||||
if err != nil {
|
||||
detailedError, ok := err.(autorest.DetailedError)
|
||||
if !ok || detailedError.StatusCode != http.StatusNotFound {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Construct unique TXT records using map
|
||||
uniqRecords := map[string]struct{}{value: {}}
|
||||
if rset.RecordSetProperties != nil && rset.TxtRecords != nil {
|
||||
for _, txtRecord := range *rset.TxtRecords {
|
||||
// Assume Value doesn't contain multiple strings
|
||||
if txtRecord.Value != nil && len(*txtRecord.Value) > 0 {
|
||||
uniqRecords[(*txtRecord.Value)[0]] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var txtRecords []dns.TxtRecord
|
||||
for txt := range uniqRecords {
|
||||
txtRecords = append(txtRecords, dns.TxtRecord{Value: &[]string{txt}})
|
||||
}
|
||||
|
||||
rec := dns.RecordSet{
|
||||
Name: &relative,
|
||||
RecordSetProperties: &dns.RecordSetProperties{
|
||||
TTL: to.Int64Ptr(int64(d.config.TTL)),
|
||||
TxtRecords: &txtRecords,
|
||||
},
|
||||
}
|
||||
|
||||
_, err = rsc.CreateOrUpdate(ctx, d.config.ResourceGroup, zone, relative, dns.TXT, rec, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneID(ctx, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
relative := toRelativeRecord(fqdn, dns01.ToFqdn(zone))
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
_, err = rsc.Delete(ctx, d.config.ResourceGroup, zone, relative, dns.TXT, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks that azure has a zone for this domain name.
|
||||
func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dc := dns.NewZonesClient(d.config.SubscriptionID)
|
||||
dc.Authorizer = d.authorizer
|
||||
|
||||
zone, err := dc.Get(ctx, d.config.ResourceGroup, dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// zone.Name shouldn't have a trailing dot(.)
|
||||
return to.String(zone.Name), nil
|
||||
}
|
||||
|
||||
// Returns the relative record to the domain
|
||||
func toRelativeRecord(domain, zone string) string {
|
||||
return dns01.UnFqdn(strings.TrimSuffix(domain, zone))
|
||||
}
|
||||
|
||||
func getAuthorizer(config *Config) (autorest.Authorizer, error) {
|
||||
if config.ClientID != "" && config.ClientSecret != "" && config.TenantID != "" {
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, config.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt, err := adal.NewServicePrincipalToken(*oauthConfig, config.ClientID, config.ClientSecret, azure.PublicCloud.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spt.SetSender(config.HTTPClient)
|
||||
return autorest.NewBearerAuthorizer(spt), nil
|
||||
}
|
||||
|
||||
return auth.NewAuthorizerFromEnvironment()
|
||||
}
|
||||
|
||||
// Fetches metadata from environment or he instance metadata service
|
||||
// borrowed from https://github.com/Microsoft/azureimds/blob/master/imdssample.go
|
||||
func getMetadata(config *Config, field string) (string, error) {
|
||||
metadataEndpoint := config.MetadataEndpoint
|
||||
if len(metadataEndpoint) == 0 {
|
||||
metadataEndpoint = defaultMetadataEndpoint
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("%s/metadata/instance/compute/%s", metadataEndpoint, field)
|
||||
req, err := http.NewRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("Metadata", "True")
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("format", "text")
|
||||
q.Add("api-version", "2017-12-01")
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
resp, err := config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(respBody), nil
|
||||
}
|
201
vendor/github.com/go-acme/lego/providers/dns/bluecat/bluecat.go
generated
vendored
Normal file
201
vendor/github.com/go-acme/lego/providers/dns/bluecat/bluecat.go
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Package bluecat implements a DNS provider for solving the DNS-01 challenge using a self-hosted Bluecat Address Manager.
|
||||
package bluecat
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
configType = "Configuration"
|
||||
viewType = "View"
|
||||
zoneType = "Zone"
|
||||
txtType = "TXTRecord"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
UserName string
|
||||
Password string
|
||||
ConfigName string
|
||||
DNSView string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("BLUECAT_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("BLUECAT_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("BLUECAT_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("BLUECAT_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// Bluecat's Address Manager REST API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Bluecat DNS.
|
||||
// Credentials must be passed in the environment variables: BLUECAT_SERVER_URL, BLUECAT_USER_NAME and BLUECAT_PASSWORD.
|
||||
// BLUECAT_SERVER_URL should have the scheme, hostname, and port (if required) of the authoritative Bluecat BAM server.
|
||||
// The REST endpoint will be appended.
|
||||
// In addition, the Configuration name and external DNS View Name must be passed in BLUECAT_CONFIG_NAME and BLUECAT_DNS_VIEW
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("BLUECAT_SERVER_URL", "BLUECAT_USER_NAME", "BLUECAT_PASSWORD", "BLUECAT_CONFIG_NAME", "BLUECAT_DNS_VIEW")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = values["BLUECAT_SERVER_URL"]
|
||||
config.UserName = values["BLUECAT_USER_NAME"]
|
||||
config.Password = values["BLUECAT_PASSWORD"]
|
||||
config.ConfigName = values["BLUECAT_CONFIG_NAME"]
|
||||
config.DNSView = values["BLUECAT_DNS_VIEW"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Bluecat DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("bluecat: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" || config.UserName == "" || config.Password == "" || config.ConfigName == "" || config.DNSView == "" {
|
||||
return nil, fmt.Errorf("bluecat: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
// This will *not* create a subzone to contain the TXT record,
|
||||
// so make sure the FQDN specified is within an extant zone.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
viewID, err := d.lookupViewID(d.config.DNSView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentZoneID, name, err := d.lookupParentZoneID(viewID, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(parentZoneID), 10),
|
||||
}
|
||||
|
||||
body := bluecatEntity{
|
||||
Name: name,
|
||||
Type: "TXTRecord",
|
||||
Properties: fmt.Sprintf("ttl=%d|absoluteName=%s|txt=%s|", d.config.TTL, fqdn, value),
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodPost, "addEntity", body, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
addTxtBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
addTxtResp := string(addTxtBytes)
|
||||
// addEntity responds only with body text containing the ID of the created record
|
||||
_, err = strconv.ParseUint(addTxtResp, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bluecat: addEntity request failed: %s", addTxtResp)
|
||||
}
|
||||
|
||||
err = d.deploy(parentZoneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
viewID, err := d.lookupViewID(d.config.DNSView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentID, name, err := d.lookupParentZoneID(viewID, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(parentID), 10),
|
||||
"name": name,
|
||||
"type": txtType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var txtRec entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&txtRec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
queryArgs = map[string]string{
|
||||
"objectId": strconv.FormatUint(uint64(txtRec.ID), 10),
|
||||
}
|
||||
|
||||
resp, err = d.sendRequest(http.MethodDelete, http.MethodDelete, nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = d.deploy(parentID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
249
vendor/github.com/go-acme/lego/providers/dns/bluecat/client.go
generated
vendored
Normal file
249
vendor/github.com/go-acme/lego/providers/dns/bluecat/client.go
generated
vendored
Normal file
|
@ -0,0 +1,249 @@
|
|||
package bluecat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSON body for Bluecat entity requests and responses
|
||||
type bluecatEntity struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
type entityResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Properties string `json:"properties"`
|
||||
}
|
||||
|
||||
// Starts a new Bluecat API Session. Authenticates using customerName, userName,
|
||||
// password and receives a token to be used in for subsequent requests.
|
||||
func (d *DNSProvider) login() error {
|
||||
queryArgs := map[string]string{
|
||||
"username": d.config.UserName,
|
||||
"password": d.config.Password,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "login", nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
authBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
authResp := string(authBytes)
|
||||
|
||||
if strings.Contains(authResp, "Authentication Error") {
|
||||
msg := strings.Trim(authResp, "\"")
|
||||
return fmt.Errorf("bluecat: request failed: %s", msg)
|
||||
}
|
||||
|
||||
// Upon success, API responds with "Session Token-> BAMAuthToken: dQfuRMTUxNjc3MjcyNDg1ODppcGFybXM= <- for User : username"
|
||||
d.token = regexp.MustCompile("BAMAuthToken: [^ ]+").FindString(authResp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroys Bluecat Session
|
||||
func (d *DNSProvider) logout() error {
|
||||
if len(d.token) == 0 {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "logout", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("bluecat: request failed to delete session with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
authBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authResp := string(authBytes)
|
||||
|
||||
if !strings.Contains(authResp, "successfully") {
|
||||
msg := strings.Trim(authResp, "\"")
|
||||
return fmt.Errorf("bluecat: request failed to delete session: %s", msg)
|
||||
}
|
||||
|
||||
d.token = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup the entity ID of the configuration named in our properties
|
||||
func (d *DNSProvider) lookupConfID() (uint, error) {
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.Itoa(0),
|
||||
"name": d.config.ConfigName,
|
||||
"type": configType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var conf entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&conf)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
return conf.ID, nil
|
||||
}
|
||||
|
||||
// Find the DNS view with the given name within
|
||||
func (d *DNSProvider) lookupViewID(viewName string) (uint, error) {
|
||||
confID, err := d.lookupConfID()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(confID), 10),
|
||||
"name": viewName,
|
||||
"type": viewType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var view entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&view)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
return view.ID, nil
|
||||
}
|
||||
|
||||
// Return the entityId of the parent zone by recursing from the root view
|
||||
// Also return the simple name of the host
|
||||
func (d *DNSProvider) lookupParentZoneID(viewID uint, fqdn string) (uint, string, error) {
|
||||
parentViewID := viewID
|
||||
name := ""
|
||||
|
||||
if fqdn != "" {
|
||||
zones := strings.Split(strings.Trim(fqdn, "."), ".")
|
||||
last := len(zones) - 1
|
||||
name = zones[0]
|
||||
|
||||
for i := last; i > -1; i-- {
|
||||
zoneID, err := d.getZone(parentViewID, zones[i])
|
||||
if err != nil || zoneID == 0 {
|
||||
return parentViewID, name, err
|
||||
}
|
||||
if i > 0 {
|
||||
name = strings.Join(zones[0:i], ".")
|
||||
}
|
||||
parentViewID = zoneID
|
||||
}
|
||||
}
|
||||
|
||||
return parentViewID, name, nil
|
||||
}
|
||||
|
||||
// Get the DNS zone with the specified name under the parentId
|
||||
func (d *DNSProvider) getZone(parentID uint, name string) (uint, error) {
|
||||
queryArgs := map[string]string{
|
||||
"parentId": strconv.FormatUint(uint64(parentID), 10),
|
||||
"name": name,
|
||||
"type": zoneType,
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodGet, "getEntityByName", nil, queryArgs)
|
||||
|
||||
// Return an empty zone if the named zone doesn't exist
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return 0, fmt.Errorf("bluecat: could not find zone named %s", name)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var zone entityResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&zone)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
return zone.ID, nil
|
||||
}
|
||||
|
||||
// Deploy the DNS config for the specified entity to the authoritative servers
|
||||
func (d *DNSProvider) deploy(entityID uint) error {
|
||||
queryArgs := map[string]string{
|
||||
"entityId": strconv.FormatUint(uint64(entityID), 10),
|
||||
}
|
||||
|
||||
resp, err := d.sendRequest(http.MethodPost, "quickDeploy", nil, queryArgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Send a REST request, using query parameters specified. The Authorization
|
||||
// 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) {
|
||||
url := fmt.Sprintf("%s/Services/REST/v1/%s", d.config.BaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if len(d.token) > 0 {
|
||||
req.Header.Set("Authorization", d.token)
|
||||
}
|
||||
|
||||
// Add all query parameters
|
||||
q := req.URL.Query()
|
||||
for argName, argVal := range queryArgs {
|
||||
q.Add(argName, argVal)
|
||||
}
|
||||
req.URL.RawQuery = q.Encode()
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
errBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
errResp := string(errBytes)
|
||||
return nil, fmt.Errorf("bluecat: request failed with HTTP status code %d\n Full message: %s",
|
||||
resp.StatusCode, errResp)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
157
vendor/github.com/go-acme/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
Normal file
157
vendor/github.com/go-acme/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Package cloudflare implements a DNS provider for solving the DNS-01 challenge using cloudflare DNS.
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
minTTL = 120
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AuthEmail string
|
||||
AuthKey string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("CLOUDFLARE_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDFLARE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDFLARE_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CLOUDFLARE_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *cloudflare.API
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Cloudflare.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.GetWithFallback(
|
||||
[]string{"CLOUDFLARE_EMAIL", "CF_API_EMAIL"},
|
||||
[]string{"CLOUDFLARE_API_KEY", "CF_API_KEY"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AuthEmail = values["CLOUDFLARE_EMAIL"]
|
||||
config.AuthKey = values["CLOUDFLARE_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Cloudflare.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("cloudflare: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("cloudflare: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
client, err := cloudflare.New(config.AuthKey, config.AuthEmail, cloudflare.HTTPClient(config.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
dnsRecord := cloudflare.DNSRecord{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
response, err := d.client.CreateDNSRecord(zoneID, dnsRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to create TXT record: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return fmt.Errorf("cloudflare: failed to create TXT record: %+v %+v", response.Errors, response.Messages)
|
||||
}
|
||||
|
||||
log.Infof("cloudflare: new record for %s, ID %s", domain, response.Result.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
dnsRecord := cloudflare.DNSRecord{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
}
|
||||
|
||||
records, err := d.client.DNSRecords(zoneID, dnsRecord)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find TXT records: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err = d.client.DeleteDNSRecord(zoneID, record.ID)
|
||||
if err != nil {
|
||||
log.Printf("cloudflare: failed to delete TXT record: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
108
vendor/github.com/go-acme/lego/providers/dns/cloudns/cloudns.go
generated
vendored
Normal file
108
vendor/github.com/go-acme/lego/providers/dns/cloudns/cloudns.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Package cloudns implements a DNS provider for solving the DNS-01 challenge using ClouDNS DNS.
|
||||
package cloudns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/cloudns/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AuthID string
|
||||
AuthPassword string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDNS_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDNS_POLLING_INTERVAL", 4*time.Second),
|
||||
TTL: env.GetOrDefaultInt("CLOUDNS_TTL", dns01.DefaultTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CLOUDNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for ClouDNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDNS_AUTH_ID and CLOUDNS_AUTH_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("CLOUDNS_AUTH_ID", "CLOUDNS_AUTH_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AuthID = values["CLOUDNS_AUTH_ID"]
|
||||
config.AuthPassword = values["CLOUDNS_AUTH_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for ClouDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ClouDNS: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.AuthID, config.AuthPassword)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.client.GetZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.AddTxtRecord(zone.Name, fqdn, value, d.config.TTL)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.client.GetZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record, err := d.client.FindTxtRecord(zone.Name, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.RemoveTxtRecord(record.ID, zone.Name)
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
209
vendor/github.com/go-acme/lego/providers/dns/cloudns/internal/client.go
generated
vendored
Normal file
209
vendor/github.com/go-acme/lego/providers/dns/cloudns/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,209 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.cloudns.net/dns/"
|
||||
|
||||
type Zone struct {
|
||||
Name string
|
||||
Type string
|
||||
Zone string
|
||||
Status string // is an integer, but cast as string
|
||||
}
|
||||
|
||||
// TXTRecord a TXT record
|
||||
type TXTRecord struct {
|
||||
ID int `json:"id,string"`
|
||||
Type string `json:"type"`
|
||||
Host string `json:"host"`
|
||||
Record string `json:"record"`
|
||||
Failover int `json:"failover,string"`
|
||||
TTL int `json:"ttl,string"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type TXTRecords map[string]TXTRecord
|
||||
|
||||
// NewClient creates a ClouDNS client
|
||||
func NewClient(authID string, authPassword string) (*Client, error) {
|
||||
if authID == "" {
|
||||
return nil, fmt.Errorf("ClouDNS: credentials missing: authID")
|
||||
}
|
||||
|
||||
if authPassword == "" {
|
||||
return nil, fmt.Errorf("ClouDNS: credentials missing: authPassword")
|
||||
}
|
||||
|
||||
baseURL, err := url.Parse(defaultBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
authID: authID,
|
||||
authPassword: authPassword,
|
||||
HTTPClient: &http.Client{},
|
||||
BaseURL: baseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client ClouDNS client
|
||||
type Client struct {
|
||||
authID string
|
||||
authPassword string
|
||||
HTTPClient *http.Client
|
||||
BaseURL *url.URL
|
||||
}
|
||||
|
||||
// GetZone Get domain name information for a FQDN
|
||||
func (c *Client) GetZone(authFQDN string) (*Zone, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(authFQDN)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authZoneName := dns01.UnFqdn(authZone)
|
||||
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "get-zone-info.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", authZoneName)
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
result, err := c.doRequest(http.MethodGet, &reqURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var zone Zone
|
||||
|
||||
if len(result) > 0 {
|
||||
if err = json.Unmarshal(result, &zone); err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: zone unmarshaling error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if zone.Name == authZoneName {
|
||||
return &zone, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ClouDNS: zone %s not found for authFQDN %s", authZoneName, authFQDN)
|
||||
}
|
||||
|
||||
// FindTxtRecord return the TXT record a zone ID and a FQDN
|
||||
func (c *Client) FindTxtRecord(zoneName, fqdn string) (*TXTRecord, error) {
|
||||
host := dns01.UnFqdn(strings.TrimSuffix(dns01.UnFqdn(fqdn), zoneName))
|
||||
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "records.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", zoneName)
|
||||
q.Add("host", host)
|
||||
q.Add("type", "TXT")
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
result, err := c.doRequest(http.MethodGet, &reqURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records TXTRecords
|
||||
if err = json.Unmarshal(result, &records); err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: TXT record unmarshaling error: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if record.Host == host && record.Type == "TXT" {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ClouDNS: no existing record found for %q", fqdn)
|
||||
}
|
||||
|
||||
// AddTxtRecord add a TXT record
|
||||
func (c *Client) AddTxtRecord(zoneName string, fqdn, value string, ttl int) error {
|
||||
host := dns01.UnFqdn(strings.TrimSuffix(dns01.UnFqdn(fqdn), zoneName))
|
||||
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "add-record.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", zoneName)
|
||||
q.Add("host", host)
|
||||
q.Add("record", value)
|
||||
q.Add("ttl", strconv.Itoa(ttl))
|
||||
q.Add("record-type", "TXT")
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
_, err := c.doRequest(http.MethodPost, &reqURL)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveTxtRecord remove a TXT record
|
||||
func (c *Client) RemoveTxtRecord(recordID int, zoneName string) error {
|
||||
reqURL := *c.BaseURL
|
||||
reqURL.Path += "delete-record.json"
|
||||
|
||||
q := reqURL.Query()
|
||||
q.Add("domain-name", zoneName)
|
||||
q.Add("record-id", strconv.Itoa(recordID))
|
||||
reqURL.RawQuery = q.Encode()
|
||||
|
||||
_, err := c.doRequest(http.MethodPost, &reqURL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(method string, url *url.URL) (json.RawMessage, error) {
|
||||
req, err := c.buildRequest(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: %s", toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("ClouDNS: invalid code (%v), error: %s", resp.StatusCode, content)
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (c *Client) buildRequest(method string, url *url.URL) (*http.Request, error) {
|
||||
q := url.Query()
|
||||
q.Add("auth-id", c.authID)
|
||||
q.Add("auth-password", c.authPassword)
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(method, url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ClouDNS: invalid request: %v", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
|
||||
}
|
108
vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
Normal file
108
vendor/github.com/go-acme/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Package cloudxns implements a DNS provider for solving the DNS-01 challenge using CloudXNS DNS.
|
||||
package cloudxns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/cloudxns/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
SecretKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDXNS_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDXNS_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("CLOUDXNS_TTL", dns01.DefaultTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CLOUDXNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for CloudXNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDXNS_API_KEY and CLOUDXNS_SECRET_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("CLOUDXNS_API_KEY", "CLOUDXNS_SECRET_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["CLOUDXNS_API_KEY"]
|
||||
config.SecretKey = values["CLOUDXNS_SECRET_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for CloudXNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("CloudXNS: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.APIKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
info, err := d.client.GetDomainInformation(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.AddTxtRecord(info, fqdn, value, d.config.TTL)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
info, err := d.client.GetDomainInformation(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record, err := d.client.FindTxtRecord(info.ID, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.client.RemoveTxtRecord(record.RecordID, info.ID)
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
208
vendor/github.com/go-acme/lego/providers/dns/cloudxns/internal/client.go
generated
vendored
Normal file
208
vendor/github.com/go-acme/lego/providers/dns/cloudxns/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,208 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://www.cloudxns.net/api2/"
|
||||
|
||||
type apiResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Data Domain information
|
||||
type Data struct {
|
||||
ID string `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// TXTRecord a TXT record
|
||||
type TXTRecord struct {
|
||||
ID int `json:"domain_id,omitempty"`
|
||||
RecordID string `json:"record_id,omitempty"`
|
||||
|
||||
Host string `json:"host"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
LineID int `json:"line_id,string"`
|
||||
TTL int `json:"ttl,string"`
|
||||
}
|
||||
|
||||
// NewClient creates a CloudXNS client
|
||||
func NewClient(apiKey string, secretKey string) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("CloudXNS: credentials missing: apiKey")
|
||||
}
|
||||
|
||||
if secretKey == "" {
|
||||
return nil, fmt.Errorf("CloudXNS: credentials missing: secretKey")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
secretKey: secretKey,
|
||||
HTTPClient: &http.Client{},
|
||||
BaseURL: defaultBaseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client CloudXNS client
|
||||
type Client struct {
|
||||
apiKey string
|
||||
secretKey string
|
||||
HTTPClient *http.Client
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// GetDomainInformation Get domain name information for a FQDN
|
||||
func (c *Client) GetDomainInformation(fqdn string) (*Data, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := c.doRequest(http.MethodGet, "domain", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var domains []Data
|
||||
if len(result) > 0 {
|
||||
err = json.Unmarshal(result, &domains)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: domains unmarshaling error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, data := range domains {
|
||||
if data.Domain == authZone {
|
||||
return &data, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("CloudXNS: zone %s not found for domain %s", authZone, fqdn)
|
||||
}
|
||||
|
||||
// FindTxtRecord return the TXT record a zone ID and a FQDN
|
||||
func (c *Client) FindTxtRecord(zoneID, fqdn string) (*TXTRecord, error) {
|
||||
result, err := c.doRequest(http.MethodGet, fmt.Sprintf("record/%s?host_id=0&offset=0&row_num=2000", zoneID), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []TXTRecord
|
||||
err = json.Unmarshal(result, &records)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: TXT record unmarshaling error: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if record.Host == dns01.UnFqdn(fqdn) && record.Type == "TXT" {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("CloudXNS: no existing record found for %q", fqdn)
|
||||
}
|
||||
|
||||
// AddTxtRecord add a TXT record
|
||||
func (c *Client) AddTxtRecord(info *Data, fqdn, value string, ttl int) error {
|
||||
id, err := strconv.Atoi(info.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CloudXNS: invalid zone ID: %v", err)
|
||||
}
|
||||
|
||||
payload := TXTRecord{
|
||||
ID: id,
|
||||
Host: dns01.UnFqdn(strings.TrimSuffix(fqdn, info.Domain)),
|
||||
Value: value,
|
||||
Type: "TXT",
|
||||
LineID: 1,
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CloudXNS: record unmarshaling error: %v", err)
|
||||
}
|
||||
|
||||
_, err = c.doRequest(http.MethodPost, "record", body)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveTxtRecord remove a TXT record
|
||||
func (c *Client) RemoveTxtRecord(recordID, zoneID string) error {
|
||||
_, err := c.doRequest(http.MethodDelete, fmt.Sprintf("record/%s/%s", recordID, zoneID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(method, uri string, body []byte) (json.RawMessage, error) {
|
||||
req, err := c.buildRequest(method, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: %s", toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
var r apiResponse
|
||||
err = json.Unmarshal(content, &r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: response unmashaling error: %v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
if r.Code != 1 {
|
||||
return nil, fmt.Errorf("CloudXNS: invalid code (%v), error: %s", r.Code, r.Message)
|
||||
}
|
||||
return r.Data, nil
|
||||
}
|
||||
|
||||
func (c *Client) buildRequest(method, uri string, body []byte) (*http.Request, error) {
|
||||
url := c.BaseURL + uri
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("CloudXNS: invalid request: %v", err)
|
||||
}
|
||||
|
||||
requestDate := time.Now().Format(time.RFC1123Z)
|
||||
|
||||
req.Header.Set("API-KEY", c.apiKey)
|
||||
req.Header.Set("API-REQUEST-DATE", requestDate)
|
||||
req.Header.Set("API-HMAC", c.hmac(url, requestDate, string(body)))
|
||||
req.Header.Set("API-FORMAT", "json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) hmac(url, date, body string) string {
|
||||
sum := md5.Sum([]byte(c.apiKey + url + body + date + c.secretKey))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
|
||||
}
|
148
vendor/github.com/go-acme/lego/providers/dns/conoha/conoha.go
generated
vendored
Normal file
148
vendor/github.com/go-acme/lego/providers/dns/conoha/conoha.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
// Package conoha implements a DNS provider for solving the DNS-01 challenge using ConoHa DNS.
|
||||
package conoha
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/conoha/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Region string
|
||||
TenantID string
|
||||
Username string
|
||||
Password string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Region: env.GetOrDefaultString("CONOHA_REGION", "tyo1"),
|
||||
TTL: env.GetOrDefaultInt("CONOHA_TTL", 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CONOHA_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("CONOHA_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("CONOHA_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS.
|
||||
// Credentials must be passed in the environment variables: CONOHA_TENANT_ID, CONOHA_API_USERNAME, CONOHA_API_PASSWORD
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("CONOHA_TENANT_ID", "CONOHA_API_USERNAME", "CONOHA_API_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conoha: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.TenantID = values["CONOHA_TENANT_ID"]
|
||||
config.Username = values["CONOHA_API_USERNAME"]
|
||||
config.Password = values["CONOHA_API_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for ConoHa DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("conoha: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.TenantID == "" || config.Username == "" || config.Password == "" {
|
||||
return nil, errors.New("conoha: some credentials information are missing")
|
||||
}
|
||||
|
||||
auth := internal.Auth{
|
||||
TenantID: config.TenantID,
|
||||
PasswordCredentials: internal.PasswordCredentials{
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.Region, auth, config.HTTPClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conoha: failed to create client: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := d.client.GetDomainID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
|
||||
}
|
||||
|
||||
record := internal.Record{
|
||||
Name: fqdn,
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
err = d.client.CreateRecord(id, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to create record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp clears ConoHa DNS TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domID, err := d.client.GetDomainID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
|
||||
}
|
||||
|
||||
recID, err := d.client.GetRecordID(domID, fqdn, "TXT", value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to get record ID: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.DeleteRecord(domID, recID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("conoha: failed to delete record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
205
vendor/github.com/go-acme/lego/providers/dns/conoha/internal/client.go
generated
vendored
Normal file
205
vendor/github.com/go-acme/lego/providers/dns/conoha/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
identityBaseURL = "https://identity.%s.conoha.io"
|
||||
dnsServiceBaseURL = "https://dns-service.%s.conoha.io"
|
||||
)
|
||||
|
||||
// IdentityRequest is an authentication request body.
|
||||
type IdentityRequest struct {
|
||||
Auth Auth `json:"auth"`
|
||||
}
|
||||
|
||||
// Auth is an authentication information.
|
||||
type Auth struct {
|
||||
TenantID string `json:"tenantId"`
|
||||
PasswordCredentials PasswordCredentials `json:"passwordCredentials"`
|
||||
}
|
||||
|
||||
// PasswordCredentials is API-user's credentials.
|
||||
type PasswordCredentials struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// IdentityResponse is an authentication response body.
|
||||
type IdentityResponse struct {
|
||||
Access Access `json:"access"`
|
||||
}
|
||||
|
||||
// Access is an identity information.
|
||||
type Access struct {
|
||||
Token Token `json:"token"`
|
||||
}
|
||||
|
||||
// Token is an api access token.
|
||||
type Token struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// DomainListResponse is a response of a domain listing request.
|
||||
type DomainListResponse struct {
|
||||
Domains []Domain `json:"domains"`
|
||||
}
|
||||
|
||||
// Domain is a hosted domain entry.
|
||||
type Domain struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// RecordListResponse is a response of record listing request.
|
||||
type RecordListResponse struct {
|
||||
Records []Record `json:"records"`
|
||||
}
|
||||
|
||||
// Record is a record entry.
|
||||
type Record struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// Client is a ConoHa API client.
|
||||
type Client struct {
|
||||
token string
|
||||
endpoint string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a client instance logged into the ConoHa service.
|
||||
func NewClient(region string, auth Auth, httpClient *http.Client) (*Client, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = &http.Client{}
|
||||
}
|
||||
|
||||
c := &Client{httpClient: httpClient}
|
||||
|
||||
c.endpoint = fmt.Sprintf(identityBaseURL, region)
|
||||
|
||||
identity, err := c.getIdentity(auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to login: %v", err)
|
||||
}
|
||||
|
||||
c.token = identity.Access.Token.ID
|
||||
c.endpoint = fmt.Sprintf(dnsServiceBaseURL, region)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) getIdentity(auth Auth) (*IdentityResponse, error) {
|
||||
req := &IdentityRequest{Auth: auth}
|
||||
|
||||
identity := &IdentityResponse{}
|
||||
|
||||
err := c.do(http.MethodPost, "/v2.0/tokens", req, identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// GetDomainID returns an ID of specified domain.
|
||||
func (c *Client) GetDomainID(domainName string) (string, error) {
|
||||
domainList := &DomainListResponse{}
|
||||
|
||||
err := c.do(http.MethodGet, "/v1/domains", nil, domainList)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, domain := range domainList.Domains {
|
||||
if domain.Name == domainName {
|
||||
return domain.ID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no such domain: %s", domainName)
|
||||
}
|
||||
|
||||
// GetRecordID returns an ID of specified record.
|
||||
func (c *Client) GetRecordID(domainID, recordName, recordType, data string) (string, error) {
|
||||
recordList := &RecordListResponse{}
|
||||
|
||||
err := c.do(http.MethodGet, fmt.Sprintf("/v1/domains/%s/records", domainID), nil, recordList)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, record := range recordList.Records {
|
||||
if record.Name == recordName && record.Type == recordType && record.Data == data {
|
||||
return record.ID, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no such record")
|
||||
}
|
||||
|
||||
// CreateRecord adds new record.
|
||||
func (c *Client) CreateRecord(domainID string, record Record) error {
|
||||
return c.do(http.MethodPost, fmt.Sprintf("/v1/domains/%s/records", domainID), record, nil)
|
||||
}
|
||||
|
||||
// DeleteRecord removes specified record.
|
||||
func (c *Client) DeleteRecord(domainID, recordID string) error {
|
||||
return c.do(http.MethodDelete, fmt.Sprintf("/v1/domains/%s/records/%s", domainID, recordID), nil, nil)
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, payload, result interface{}) error {
|
||||
body := bytes.NewReader(nil)
|
||||
|
||||
if payload != nil {
|
||||
bodyBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body = bytes.NewReader(bodyBytes)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.endpoint+path, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Auth-Token", c.token)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return fmt.Errorf("HTTP request failed with status code %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
if result != nil {
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return json.Unmarshal(respBody, result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
249
vendor/github.com/go-acme/lego/providers/dns/designate/designate.go
generated
vendored
Normal file
249
vendor/github.com/go-acme/lego/providers/dns/designate/designate.go
generated
vendored
Normal file
|
@ -0,0 +1,249 @@
|
|||
// Package designate implements a DNS provider for solving the DNS-01 challenge using the Designate DNSaaS for Openstack.
|
||||
package designate
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/recordsets"
|
||||
"github.com/gophercloud/gophercloud/openstack/dns/v2/zones"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
opts gophercloud.AuthOptions
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DESIGNATE_TTL", 10),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DESIGNATE_PROPAGATION_TIMEOUT", 10*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("DESIGNATE_POLLING_INTERVAL", 10*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for Designate
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *gophercloud.ServiceClient
|
||||
dnsEntriesMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Designate.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_TENANT_NAME, OS_REGION_NAME.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
_, err := env.Get("OS_AUTH_URL", "OS_USERNAME", "OS_PASSWORD", "OS_TENANT_NAME", "OS_REGION_NAME")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
opts, err := openstack.AuthOptionsFromEnv()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.opts = opts
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Designate.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("designate: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
provider, err := openstack.AuthenticatedClient(config.opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: failed to authenticate: %v", err)
|
||||
}
|
||||
|
||||
dnsClient, err := openstack.NewDNSV2(provider, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("designate: failed to get DNS provider: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{client: dnsClient, config: config}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: couldn't get zone ID in Present: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
// use mutex to prevent race condition between creating the record and verifying it
|
||||
d.dnsEntriesMu.Lock()
|
||||
defer d.dnsEntriesMu.Unlock()
|
||||
|
||||
existingRecord, err := d.getRecord(zoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
if existingRecord != nil {
|
||||
if contains(existingRecord.Records, value) {
|
||||
log.Printf("designate: the record already exists: %s", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.updateRecord(existingRecord, value)
|
||||
}
|
||||
|
||||
err = d.createRecord(zoneID, fqdn, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: couldn't get zone ID in CleanUp: %v", err)
|
||||
}
|
||||
|
||||
// use mutex to prevent race condition between getting the record and deleting it
|
||||
d.dnsEntriesMu.Lock()
|
||||
defer d.dnsEntriesMu.Unlock()
|
||||
|
||||
record, err := d.getRecord(zoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: couldn't get Record ID in CleanUp: %v", err)
|
||||
}
|
||||
|
||||
if record == nil {
|
||||
// Record is already deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
err = recordsets.Delete(d.client, zoneID, record.ID).ExtractErr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("designate: error for %s in CleanUp: %v", fqdn, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func contains(values []string, value string) bool {
|
||||
for _, v := range values {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *DNSProvider) createRecord(zoneID, fqdn, value string) error {
|
||||
createOpts := recordsets.CreateOpts{
|
||||
Name: fqdn,
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Description: "ACME verification record",
|
||||
Records: []string{value},
|
||||
}
|
||||
|
||||
actual, err := recordsets.Create(d.client, zoneID, createOpts).Extract()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error for %s in Present while creating record: %v", fqdn, err)
|
||||
}
|
||||
|
||||
if actual.Name != fqdn || actual.TTL != d.config.TTL {
|
||||
return fmt.Errorf("the created record doesn't match what we wanted to create")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) updateRecord(record *recordsets.RecordSet, value string) error {
|
||||
if contains(record.Records, value) {
|
||||
log.Printf("skip: the record already exists: %s", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
values := append([]string{value}, record.Records...)
|
||||
|
||||
updateOpts := recordsets.UpdateOpts{
|
||||
Description: &record.Description,
|
||||
TTL: record.TTL,
|
||||
Records: values,
|
||||
}
|
||||
|
||||
result := recordsets.Update(d.client, record.ZoneID, record.ID, updateOpts)
|
||||
return result.Err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneID(wanted string) (string, error) {
|
||||
allPages, err := zones.List(d.client, nil).AllPages()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
allZones, err := zones.ExtractZones(allPages)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, zone := range allZones {
|
||||
if zone.Name == wanted {
|
||||
return zone.ID, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("zone id not found for %s", wanted)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecord(zoneID string, wanted string) (*recordsets.RecordSet, error) {
|
||||
allPages, err := recordsets.ListByZone(d.client, zoneID, nil).AllPages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allRecords, err := recordsets.ExtractRecordSets(allPages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, record := range allRecords {
|
||||
if record.Name == wanted {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
132
vendor/github.com/go-acme/lego/providers/dns/digitalocean/client.go
generated
vendored
Normal file
132
vendor/github.com/go-acme/lego/providers/dns/digitalocean/client.go
generated
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.digitalocean.com"
|
||||
|
||||
// txtRecordResponse represents a response from DO's API after making a TXT record
|
||||
type txtRecordResponse struct {
|
||||
DomainRecord record `json:"domain_record"`
|
||||
}
|
||||
|
||||
type record struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Data string `json:"data,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type apiError struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) removeTxtRecord(domain string, recordID int) error {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records/%d", d.config.BaseURL, dns01.UnFqdn(authZone), recordID)
|
||||
req, err := d.newRequest(http.MethodDelete, reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return readError(req, resp)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTxtRecord(domain, fqdn, value string) (*txtRecordResponse, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
reqData := record{Type: "TXT", Name: fqdn, Data: value, TTL: d.config.TTL}
|
||||
body, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqURL := fmt.Sprintf("%s/v2/domains/%s/records", d.config.BaseURL, dns01.UnFqdn(authZone))
|
||||
req, err := d.newRequest(http.MethodPost, reqURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, readError(req, resp)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New(toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
// Everything looks good; but we'll need the ID later to delete the record
|
||||
respData := &txtRecordResponse{}
|
||||
err = json.Unmarshal(content, respData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
return respData, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequest(method, reqURL string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, reqURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", d.config.AuthToken))
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func readError(req *http.Request, resp *http.Response) error {
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New(toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
var errInfo apiError
|
||||
err = json.Unmarshal(content, &errInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("apiError unmarshaling error: %v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
return fmt.Errorf("HTTP %d: %s: %s", resp.StatusCode, errInfo.ID, errInfo.Message)
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(req *http.Request, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", req.URL, string(rawBody))
|
||||
}
|
126
vendor/github.com/go-acme/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
Normal file
126
vendor/github.com/go-acme/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Package digitalocean implements a DNS provider for solving the DNS-01 challenge using digitalocean DNS.
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
AuthToken string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
TTL: env.GetOrDefaultInt("DO_TTL", 30),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DO_PROPAGATION_TIMEOUT", 60*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("DO_POLLING_INTERVAL", 5*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DO_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
// that uses DigitalOcean's REST API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
recordIDs map[string]int
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Digital
|
||||
// Ocean. Credentials must be passed in the environment variable:
|
||||
// DO_AUTH_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DO_AUTH_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("digitalocean: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AuthToken = values["DO_AUTH_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Digital Ocean.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("digitalocean: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.AuthToken == "" {
|
||||
return nil, fmt.Errorf("digitalocean: credentials missing")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
recordIDs: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
respData, err := d.addTxtRecord(domain, fqdn, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digitalocean: %v", err)
|
||||
}
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = respData.DomainRecord.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
d.recordIDsMu.Unlock()
|
||||
if !ok {
|
||||
return fmt.Errorf("digitalocean: unknown record ID for '%s'", fqdn)
|
||||
}
|
||||
|
||||
err := d.removeTxtRecord(domain, recordID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digitalocean: %v", err)
|
||||
}
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
177
vendor/github.com/go-acme/lego/providers/dns/dns_providers.go
generated
vendored
Normal file
177
vendor/github.com/go-acme/lego/providers/dns/dns_providers.go
generated
vendored
Normal file
|
@ -0,0 +1,177 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-acme/lego/challenge"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/providers/dns/acmedns"
|
||||
"github.com/go-acme/lego/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/providers/dns/auroradns"
|
||||
"github.com/go-acme/lego/providers/dns/azure"
|
||||
"github.com/go-acme/lego/providers/dns/bluecat"
|
||||
"github.com/go-acme/lego/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/providers/dns/cloudns"
|
||||
"github.com/go-acme/lego/providers/dns/cloudxns"
|
||||
"github.com/go-acme/lego/providers/dns/conoha"
|
||||
"github.com/go-acme/lego/providers/dns/designate"
|
||||
"github.com/go-acme/lego/providers/dns/digitalocean"
|
||||
"github.com/go-acme/lego/providers/dns/dnsimple"
|
||||
"github.com/go-acme/lego/providers/dns/dnsmadeeasy"
|
||||
"github.com/go-acme/lego/providers/dns/dnspod"
|
||||
"github.com/go-acme/lego/providers/dns/dreamhost"
|
||||
"github.com/go-acme/lego/providers/dns/duckdns"
|
||||
"github.com/go-acme/lego/providers/dns/dyn"
|
||||
"github.com/go-acme/lego/providers/dns/exec"
|
||||
"github.com/go-acme/lego/providers/dns/exoscale"
|
||||
"github.com/go-acme/lego/providers/dns/fastdns"
|
||||
"github.com/go-acme/lego/providers/dns/gandi"
|
||||
"github.com/go-acme/lego/providers/dns/gandiv5"
|
||||
"github.com/go-acme/lego/providers/dns/gcloud"
|
||||
"github.com/go-acme/lego/providers/dns/glesys"
|
||||
"github.com/go-acme/lego/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/providers/dns/hostingde"
|
||||
"github.com/go-acme/lego/providers/dns/httpreq"
|
||||
"github.com/go-acme/lego/providers/dns/iij"
|
||||
"github.com/go-acme/lego/providers/dns/inwx"
|
||||
"github.com/go-acme/lego/providers/dns/lightsail"
|
||||
"github.com/go-acme/lego/providers/dns/linode"
|
||||
"github.com/go-acme/lego/providers/dns/linodev4"
|
||||
"github.com/go-acme/lego/providers/dns/mydnsjp"
|
||||
"github.com/go-acme/lego/providers/dns/namecheap"
|
||||
"github.com/go-acme/lego/providers/dns/namedotcom"
|
||||
"github.com/go-acme/lego/providers/dns/netcup"
|
||||
"github.com/go-acme/lego/providers/dns/nifcloud"
|
||||
"github.com/go-acme/lego/providers/dns/ns1"
|
||||
"github.com/go-acme/lego/providers/dns/oraclecloud"
|
||||
"github.com/go-acme/lego/providers/dns/otc"
|
||||
"github.com/go-acme/lego/providers/dns/ovh"
|
||||
"github.com/go-acme/lego/providers/dns/pdns"
|
||||
"github.com/go-acme/lego/providers/dns/rackspace"
|
||||
"github.com/go-acme/lego/providers/dns/rfc2136"
|
||||
"github.com/go-acme/lego/providers/dns/route53"
|
||||
"github.com/go-acme/lego/providers/dns/sakuracloud"
|
||||
"github.com/go-acme/lego/providers/dns/selectel"
|
||||
"github.com/go-acme/lego/providers/dns/stackpath"
|
||||
"github.com/go-acme/lego/providers/dns/transip"
|
||||
"github.com/go-acme/lego/providers/dns/vegadns"
|
||||
"github.com/go-acme/lego/providers/dns/vscale"
|
||||
"github.com/go-acme/lego/providers/dns/vultr"
|
||||
"github.com/go-acme/lego/providers/dns/zoneee"
|
||||
)
|
||||
|
||||
// NewDNSChallengeProviderByName Factory for DNS providers
|
||||
func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
|
||||
switch name {
|
||||
case "acme-dns":
|
||||
return acmedns.NewDNSProvider()
|
||||
case "alidns":
|
||||
return alidns.NewDNSProvider()
|
||||
case "azure":
|
||||
return azure.NewDNSProvider()
|
||||
case "auroradns":
|
||||
return auroradns.NewDNSProvider()
|
||||
case "bluecat":
|
||||
return bluecat.NewDNSProvider()
|
||||
case "cloudflare":
|
||||
return cloudflare.NewDNSProvider()
|
||||
case "cloudns":
|
||||
return cloudns.NewDNSProvider()
|
||||
case "cloudxns":
|
||||
return cloudxns.NewDNSProvider()
|
||||
case "conoha":
|
||||
return conoha.NewDNSProvider()
|
||||
case "designate":
|
||||
return designate.NewDNSProvider()
|
||||
case "digitalocean":
|
||||
return digitalocean.NewDNSProvider()
|
||||
case "dnsimple":
|
||||
return dnsimple.NewDNSProvider()
|
||||
case "dnsmadeeasy":
|
||||
return dnsmadeeasy.NewDNSProvider()
|
||||
case "dnspod":
|
||||
return dnspod.NewDNSProvider()
|
||||
case "dreamhost":
|
||||
return dreamhost.NewDNSProvider()
|
||||
case "duckdns":
|
||||
return duckdns.NewDNSProvider()
|
||||
case "dyn":
|
||||
return dyn.NewDNSProvider()
|
||||
case "fastdns":
|
||||
return fastdns.NewDNSProvider()
|
||||
case "exec":
|
||||
return exec.NewDNSProvider()
|
||||
case "exoscale":
|
||||
return exoscale.NewDNSProvider()
|
||||
case "gandi":
|
||||
return gandi.NewDNSProvider()
|
||||
case "gandiv5":
|
||||
return gandiv5.NewDNSProvider()
|
||||
case "glesys":
|
||||
return glesys.NewDNSProvider()
|
||||
case "gcloud":
|
||||
return gcloud.NewDNSProvider()
|
||||
case "godaddy":
|
||||
return godaddy.NewDNSProvider()
|
||||
case "hostingde":
|
||||
return hostingde.NewDNSProvider()
|
||||
case "httpreq":
|
||||
return httpreq.NewDNSProvider()
|
||||
case "iij":
|
||||
return iij.NewDNSProvider()
|
||||
case "inwx":
|
||||
return inwx.NewDNSProvider()
|
||||
case "lightsail":
|
||||
return lightsail.NewDNSProvider()
|
||||
case "linode":
|
||||
return linode.NewDNSProvider()
|
||||
case "linodev4":
|
||||
return linodev4.NewDNSProvider()
|
||||
case "manual":
|
||||
return dns01.NewDNSProviderManual()
|
||||
case "mydnsjp":
|
||||
return mydnsjp.NewDNSProvider()
|
||||
case "namecheap":
|
||||
return namecheap.NewDNSProvider()
|
||||
case "namedotcom":
|
||||
return namedotcom.NewDNSProvider()
|
||||
case "netcup":
|
||||
return netcup.NewDNSProvider()
|
||||
case "nifcloud":
|
||||
return nifcloud.NewDNSProvider()
|
||||
case "ns1":
|
||||
return ns1.NewDNSProvider()
|
||||
case "oraclecloud":
|
||||
return oraclecloud.NewDNSProvider()
|
||||
case "otc":
|
||||
return otc.NewDNSProvider()
|
||||
case "ovh":
|
||||
return ovh.NewDNSProvider()
|
||||
case "pdns":
|
||||
return pdns.NewDNSProvider()
|
||||
case "rackspace":
|
||||
return rackspace.NewDNSProvider()
|
||||
case "route53":
|
||||
return route53.NewDNSProvider()
|
||||
case "rfc2136":
|
||||
return rfc2136.NewDNSProvider()
|
||||
case "sakuracloud":
|
||||
return sakuracloud.NewDNSProvider()
|
||||
case "stackpath":
|
||||
return stackpath.NewDNSProvider()
|
||||
case "selectel":
|
||||
return selectel.NewDNSProvider()
|
||||
case "transip":
|
||||
return transip.NewDNSProvider()
|
||||
case "vegadns":
|
||||
return vegadns.NewDNSProvider()
|
||||
case "vultr":
|
||||
return vultr.NewDNSProvider()
|
||||
case "vscale":
|
||||
return vscale.NewDNSProvider()
|
||||
case "zoneee":
|
||||
return zoneee.NewDNSProvider()
|
||||
default:
|
||||
return nil, fmt.Errorf("unrecognised DNS provider: %s", name)
|
||||
}
|
||||
}
|
211
vendor/github.com/go-acme/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
Normal file
211
vendor/github.com/go-acme/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
|||
// Package dnsimple implements a DNS provider for solving the DNS-01 challenge using dnsimple DNS.
|
||||
package dnsimple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AccessToken string
|
||||
BaseURL string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSIMPLE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSIMPLE_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSIMPLE_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dnsimple.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnsimple.
|
||||
// Credentials must be passed in the environment variables: DNSIMPLE_OAUTH_TOKEN.
|
||||
//
|
||||
// See: https://developer.dnsimple.com/v2/#authentication
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.AccessToken = env.GetOrFile("DNSIMPLE_OAUTH_TOKEN")
|
||||
config.BaseURL = env.GetOrFile("DNSIMPLE_BASE_URL")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DNSimple.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dnsimple: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.AccessToken == "" {
|
||||
return nil, fmt.Errorf("dnsimple: OAuth token is missing")
|
||||
}
|
||||
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken})
|
||||
client := dnsimple.NewClient(oauth2.NewClient(context.Background(), ts))
|
||||
|
||||
if config.BaseURL != "" {
|
||||
client.BaseURL = config.BaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
recordAttributes := newTxtRecord(zoneName, fqdn, value, d.config.TTL)
|
||||
_, err = d.client.Zones.CreateRecord(accountID, zoneName, recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, rec := range records {
|
||||
_, err := d.client.Zones.DeleteRecord(accountID, rec.ZoneID, rec.ID)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("dnsimple: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
zoneName := dns01.UnFqdn(authZone)
|
||||
|
||||
zones, err := d.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
var hostedZone dnsimple.Zone
|
||||
for _, zone := range zones.Data {
|
||||
if zone.Name == zoneName {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.ID == 0 {
|
||||
return "", fmt.Errorf("zone %s not found in DNSimple for domain %s", authZone, domain)
|
||||
}
|
||||
|
||||
return hostedZone.Name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnsimple.ZoneRecord, error) {
|
||||
zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountID, err := d.getAccountID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recordName := extractRecordName(fqdn, zoneName)
|
||||
|
||||
result, err := d.client.Zones.ListRecords(accountID, zoneName, &dnsimple.ZoneRecordListOptions{Name: recordName, Type: "TXT", ListOptions: dnsimple.ListOptions{}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
return result.Data, nil
|
||||
}
|
||||
|
||||
func newTxtRecord(zoneName, fqdn, value string, ttl int) dnsimple.ZoneRecord {
|
||||
name := extractRecordName(fqdn, zoneName)
|
||||
|
||||
return dnsimple.ZoneRecord{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Content: value,
|
||||
TTL: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getAccountID() (string, error) {
|
||||
whoamiResponse, err := d.client.Identity.Whoami()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if whoamiResponse.Data.Account == nil {
|
||||
return "", fmt.Errorf("user tokens are not supported, please use an account token")
|
||||
}
|
||||
|
||||
return strconv.FormatInt(whoamiResponse.Data.Account.ID, 10), nil
|
||||
}
|
163
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
Normal file
163
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
Normal file
|
@ -0,0 +1,163 @@
|
|||
// Package dnsmadeeasy implements a DNS provider for solving the DNS-01 challenge using DNS Made Easy.
|
||||
package dnsmadeeasy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/providers/dns/dnsmadeeasy/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
APISecret string
|
||||
Sandbox bool
|
||||
HTTPClient *http.Client
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSMADEEASY_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSMADEEASY_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSMADEEASY_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DNSMADEEASY_HTTP_TIMEOUT", 10*time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// DNSMadeEasy's DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for DNSMadeEasy DNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// DNSMADEEASY_API_KEY and DNSMADEEASY_API_SECRET.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DNSMADEEASY_API_KEY", "DNSMADEEASY_API_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnsmadeeasy: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Sandbox = env.GetOrDefaultBool("DNSMADEEASY_SANDBOX", false)
|
||||
config.APIKey = values["DNSMADEEASY_API_KEY"]
|
||||
config.APISecret = values["DNSMADEEASY_API_SECRET"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DNS Made Easy.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dnsmadeeasy: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
var baseURL string
|
||||
if config.Sandbox {
|
||||
baseURL = "https://api.sandbox.dnsmadeeasy.com/V2.0"
|
||||
} else {
|
||||
if len(config.BaseURL) > 0 {
|
||||
baseURL = config.BaseURL
|
||||
} else {
|
||||
baseURL = "https://api.dnsmadeeasy.com/V2.0"
|
||||
}
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.APIKey, config.APISecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnsmadeeasy: %v", err)
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
client.BaseURL = baseURL
|
||||
|
||||
return &DNSProvider{
|
||||
client: client,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.client.GetDomain(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
// create the TXT record
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
record := &internal.Record{Type: "TXT", Name: name, Value: value, TTL: d.config.TTL}
|
||||
|
||||
err = d.client.CreateRecord(domain, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to create record for %s: %v", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT records matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
||||
// fetch the domain details
|
||||
domain, err := d.client.GetDomain(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get domain for zone %s: %v", authZone, err)
|
||||
}
|
||||
|
||||
// find matching records
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
records, err := d.client.GetRecords(domain, name, "TXT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to get records for domain %s: %v", domain.Name, err)
|
||||
}
|
||||
|
||||
// delete records
|
||||
var lastError error
|
||||
for _, record := range *records {
|
||||
err = d.client.DeleteRecord(record)
|
||||
if err != nil {
|
||||
lastError = fmt.Errorf("dnsmadeeasy: unable to delete record [id=%d, name=%s]: %v", record.ID, record.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return lastError
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
173
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/internal/client.go
generated
vendored
Normal file
173
vendor/github.com/go-acme/lego/providers/dns/dnsmadeeasy/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Domain holds the DNSMadeEasy API representation of a Domain
|
||||
type Domain struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Record holds the DNSMadeEasy API representation of a Domain Record
|
||||
type Record struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
TTL int `json:"ttl"`
|
||||
SourceID int `json:"sourceId"`
|
||||
}
|
||||
|
||||
type recordsResponse struct {
|
||||
Records *[]Record `json:"data"`
|
||||
}
|
||||
|
||||
// Client DNSMadeEasy client
|
||||
type Client struct {
|
||||
apiKey string
|
||||
apiSecret string
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a DNSMadeEasy client
|
||||
func NewClient(apiKey string, apiSecret string) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, fmt.Errorf("credentials missing: API key")
|
||||
}
|
||||
|
||||
if apiSecret == "" {
|
||||
return nil, fmt.Errorf("credentials missing: API secret")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
apiKey: apiKey,
|
||||
apiSecret: apiSecret,
|
||||
HTTPClient: &http.Client{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDomain gets a domain
|
||||
func (c *Client) GetDomain(authZone string) (*Domain, error) {
|
||||
domainName := authZone[0 : len(authZone)-1]
|
||||
resource := fmt.Sprintf("%s%s", "/dns/managed/name?domainname=", domainName)
|
||||
|
||||
resp, err := c.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
domain := &Domain{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// GetRecords gets all TXT records
|
||||
func (c *Client) GetRecords(domain *Domain, recordName, recordType string) (*[]Record, error) {
|
||||
resource := fmt.Sprintf("%s/%d/%s%s%s%s", "/dns/managed", domain.ID, "records?recordName=", recordName, "&type=", recordType)
|
||||
|
||||
resp, err := c.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
records := &recordsResponse{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records.Records, nil
|
||||
}
|
||||
|
||||
// CreateRecord creates a TXT records
|
||||
func (c *Client) CreateRecord(domain *Domain, record *Record) error {
|
||||
url := fmt.Sprintf("%s/%d/%s", "/dns/managed", domain.ID, "records")
|
||||
|
||||
resp, err := c.sendRequest(http.MethodPost, url, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRecord deletes a TXT records
|
||||
func (c *Client) DeleteRecord(record Record) error {
|
||||
resource := fmt.Sprintf("%s/%d/%s/%d", "/dns/managed", record.SourceID, "records", record.ID)
|
||||
|
||||
resp, err := c.sendRequest(http.MethodDelete, resource, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) sendRequest(method, resource string, payload interface{}) (*http.Response, error) {
|
||||
url := fmt.Sprintf("%s%s", c.BaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format(time.RFC1123)
|
||||
signature, err := computeHMAC(timestamp, c.apiSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("x-dnsme-apiKey", c.apiKey)
|
||||
req.Header.Set("x-dnsme-requestDate", timestamp)
|
||||
req.Header.Set("x-dnsme-hmac", signature)
|
||||
req.Header.Set("accept", "application/json")
|
||||
req.Header.Set("content-type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 299 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func computeHMAC(message string, secret string) (string, error) {
|
||||
key := []byte(secret)
|
||||
h := hmac.New(sha1.New, key)
|
||||
_, err := h.Write([]byte(message))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
189
vendor/github.com/go-acme/lego/providers/dns/dnspod/dnspod.go
generated
vendored
Normal file
189
vendor/github.com/go-acme/lego/providers/dns/dnspod/dnspod.go
generated
vendored
Normal file
|
@ -0,0 +1,189 @@
|
|||
// Package dnspod implements a DNS provider for solving the DNS-01 challenge using dnspod DNS.
|
||||
package dnspod
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dnspod "github.com/decker502/dnspod-go"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
LoginToken string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSPOD_TTL", 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSPOD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSPOD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DNSPOD_HTTP_TIMEOUT", 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dnspod.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for dnspod.
|
||||
// Credentials must be passed in the environment variables: DNSPOD_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DNSPOD_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnspod: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.LoginToken = values["DNSPOD_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for dnspod.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dnspod: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.LoginToken == "" {
|
||||
return nil, fmt.Errorf("dnspod: credentials missing")
|
||||
}
|
||||
|
||||
params := dnspod.CommonParams{LoginToken: config.LoginToken, Format: "json"}
|
||||
|
||||
client := dnspod.NewClient(params)
|
||||
client.HttpClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zoneID, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordAttributes := d.newTxtRecord(zoneName, fqdn, value, d.config.TTL)
|
||||
_, _, err = d.client.Domains.CreateRecord(zoneID, *recordAttributes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zoneID, _, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
_, err := d.client.Domains.DeleteRecord(zoneID, rec.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||
zones, _, err := d.client.Domains.List()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone dnspod.Domain
|
||||
for _, zone := range zones {
|
||||
if zone.Name == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
||||
if hostedZone.ID == 0 {
|
||||
return "", "", fmt.Errorf("zone %s not found in dnspod for domain %s", authZone, domain)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", hostedZone.ID), hostedZone.Name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newTxtRecord(zone, fqdn, value string, ttl int) *dnspod.Record {
|
||||
name := d.extractRecordName(fqdn, zone)
|
||||
|
||||
return &dnspod.Record{
|
||||
Type: "TXT",
|
||||
Name: name,
|
||||
Value: value,
|
||||
Line: "默认",
|
||||
TTL: strconv.Itoa(ttl),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, error) {
|
||||
zoneID, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var records []dnspod.Record
|
||||
result, _, err := d.client.Domains.ListRecords(zoneID, "")
|
||||
if err != nil {
|
||||
return records, fmt.Errorf("API call has failed: %v", err)
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, zoneName)
|
||||
|
||||
for _, record := range result {
|
||||
if record.Name == recordName {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
73
vendor/github.com/go-acme/lego/providers/dns/dreamhost/client.go
generated
vendored
Normal file
73
vendor/github.com/go-acme/lego/providers/dns/dreamhost/client.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package dreamhost
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.dreamhost.com"
|
||||
|
||||
cmdAddRecord = "dns-add_record"
|
||||
cmdRemoveRecord = "dns-remove_record"
|
||||
)
|
||||
|
||||
type apiResponse struct {
|
||||
Data string `json:"data"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) buildQuery(action, domain, txt string) (*url.URL, error) {
|
||||
u, err := url.Parse(d.config.BaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("key", d.config.APIKey)
|
||||
query.Set("cmd", action)
|
||||
query.Set("format", "json")
|
||||
query.Set("record", domain)
|
||||
query.Set("type", "TXT")
|
||||
query.Set("value", txt)
|
||||
query.Set("comment", url.QueryEscape("Managed By lego"))
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// updateTxtRecord will either add or remove a TXT record.
|
||||
// action is either cmdAddRecord or cmdRemoveRecord
|
||||
func (d *DNSProvider) updateTxtRecord(u fmt.Stringer) error {
|
||||
resp, err := d.config.HTTPClient.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
var response apiResponse
|
||||
err = json.Unmarshal(raw, &response)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode API server response: %v: %s", err, string(raw))
|
||||
}
|
||||
|
||||
if response.Result == "error" {
|
||||
return fmt.Errorf("add TXT record failed: %s", response.Data)
|
||||
}
|
||||
|
||||
log.Infof("dreamhost: %s", response.Data)
|
||||
return nil
|
||||
}
|
111
vendor/github.com/go-acme/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
Normal file
111
vendor/github.com/go-acme/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
// Package dreamhost implements a DNS provider for solving the DNS-01 challenge using DreamHost.
|
||||
// See https://help.dreamhost.com/hc/en-us/articles/217560167-API_overview
|
||||
// and https://help.dreamhost.com/hc/en-us/articles/217555707-DNS-API-commands for the API spec.
|
||||
package dreamhost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DREAMHOST_PROPAGATION_TIMEOUT", 60*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("DREAMHOST_POLLING_INTERVAL", 1*time.Minute),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DREAMHOST_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider adds and removes the record for the DNS challenge
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a new DNS provider using
|
||||
// environment variable DREAMHOST_TOKEN for adding and removing the DNS record.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DREAMHOST_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["DREAMHOST_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DreamHost.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dreamhost: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, errors.New("dreamhost: credentials missing")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
record := dns01.UnFqdn(fqdn)
|
||||
|
||||
u, err := d.buildQuery(cmdAddRecord, record, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
|
||||
err = d.updateTxtRecord(u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp clears DreamHost TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
record := dns01.UnFqdn(fqdn)
|
||||
|
||||
u, err := d.buildQuery(cmdRemoveRecord, record, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
|
||||
err = d.updateTxtRecord(u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dreamhost: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
68
vendor/github.com/go-acme/lego/providers/dns/duckdns/client.go
generated
vendored
Normal file
68
vendor/github.com/go-acme/lego/providers/dns/duckdns/client.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package duckdns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// updateTxtRecord Update the domains TXT record
|
||||
// To update the TXT record we just need to make one simple get request.
|
||||
// In DuckDNS you only have one TXT record shared with the domain and all sub domains.
|
||||
func (d *DNSProvider) updateTxtRecord(domain, token, txt string, clear bool) error {
|
||||
u, _ := url.Parse("https://www.duckdns.org/update")
|
||||
|
||||
mainDomain := getMainDomain(domain)
|
||||
if len(mainDomain) == 0 {
|
||||
return fmt.Errorf("unable to find the main domain for: %s", domain)
|
||||
}
|
||||
|
||||
query := u.Query()
|
||||
query.Set("domains", mainDomain)
|
||||
query.Set("token", token)
|
||||
query.Set("clear", strconv.FormatBool(clear))
|
||||
query.Set("txt", txt)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
response, err := d.config.HTTPClient.Get(u.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := string(bodyBytes)
|
||||
if body != "OK" {
|
||||
return fmt.Errorf("request to change TXT record for DuckDNS returned the following result (%s) this does not match expectation (OK) used url [%s]", body, u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DuckDNS only lets you write to your subdomain
|
||||
// so it must be in format subdomain.duckdns.org
|
||||
// not in format subsubdomain.subdomain.duckdns.org
|
||||
// so strip off everything that is not top 3 levels
|
||||
func getMainDomain(domain string) string {
|
||||
domain = dns01.UnFqdn(domain)
|
||||
|
||||
split := dns.Split(domain)
|
||||
if strings.HasSuffix(strings.ToLower(domain), "duckdns.org") {
|
||||
if len(split) < 3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
firstSubDomainIndex := split[len(split)-3]
|
||||
return domain[firstSubDomainIndex:]
|
||||
}
|
||||
|
||||
return domain[split[len(split)-1]:]
|
||||
}
|
89
vendor/github.com/go-acme/lego/providers/dns/duckdns/duckdns.go
generated
vendored
Normal file
89
vendor/github.com/go-acme/lego/providers/dns/duckdns/duckdns.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Package duckdns implements a DNS provider for solving the DNS-01 challenge using DuckDNS.
|
||||
// See http://www.duckdns.org/spec.jsp for more info on updating TXT records.
|
||||
package duckdns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Token string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
SequenceInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DUCKDNS_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DUCKDNS_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
SequenceInterval: env.GetOrDefaultSecond("DUCKDNS_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DUCKDNS_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider adds and removes the record for the DNS challenge
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a new DNS provider using
|
||||
// environment variable DUCKDNS_TOKEN for adding and removing the DNS record.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DUCKDNS_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("duckdns: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values["DUCKDNS_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DuckDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("duckdns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Token == "" {
|
||||
return nil, errors.New("duckdns: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, txtRecord := dns01.GetRecord(domain, keyAuth)
|
||||
return d.updateTxtRecord(domain, d.config.Token, txtRecord, false)
|
||||
}
|
||||
|
||||
// CleanUp clears DuckDNS TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
return d.updateTxtRecord(domain, d.config.Token, "", true)
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Sequential All DNS challenges for this provider will be resolved sequentially.
|
||||
// Returns the interval between each iteration.
|
||||
func (d *DNSProvider) Sequential() time.Duration {
|
||||
return d.config.SequenceInterval
|
||||
}
|
146
vendor/github.com/go-acme/lego/providers/dns/dyn/client.go
generated
vendored
Normal file
146
vendor/github.com/go-acme/lego/providers/dns/dyn/client.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package dyn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.dynect.net/REST"
|
||||
|
||||
type dynResponse struct {
|
||||
// One of 'success', 'failure', or 'incomplete'
|
||||
Status string `json:"status"`
|
||||
|
||||
// The structure containing the actual results of the request
|
||||
Data json.RawMessage `json:"data"`
|
||||
|
||||
// The ID of the job that was created in response to a request.
|
||||
JobID int `json:"job_id"`
|
||||
|
||||
// A list of zero or more messages
|
||||
Messages json.RawMessage `json:"msgs"`
|
||||
}
|
||||
|
||||
type credentials struct {
|
||||
Customer string `json:"customer_name"`
|
||||
User string `json:"user_name"`
|
||||
Pass string `json:"password"`
|
||||
}
|
||||
|
||||
type session struct {
|
||||
Token string `json:"token"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type publish struct {
|
||||
Publish bool `json:"publish"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
// Starts a new Dyn API Session. Authenticates using customerName, userName,
|
||||
// password and receives a token to be used in for subsequent requests.
|
||||
func (d *DNSProvider) login() error {
|
||||
payload := &credentials{Customer: d.config.CustomerName, User: d.config.UserName, Pass: d.config.Password}
|
||||
dynRes, err := d.sendRequest(http.MethodPost, "Session", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var s session
|
||||
err = json.Unmarshal(dynRes.Data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.token = s.Token
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroys Dyn Session
|
||||
func (d *DNSProvider) logout() error {
|
||||
if len(d.token) == 0 {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/Session", defaultBaseURL)
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("API request failed to delete session with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
d.token = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) publish(zone, notes string) error {
|
||||
pub := &publish{Publish: true, Notes: notes}
|
||||
resource := fmt.Sprintf("Zone/%s/", zone)
|
||||
|
||||
_, err := d.sendRequest(http.MethodPut, resource, pub)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (*dynResponse, error) {
|
||||
url := fmt.Sprintf("%s/%s", defaultBaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if len(d.token) > 0 {
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 500 {
|
||||
return nil, fmt.Errorf("API request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var dynRes dynResponse
|
||||
err = json.NewDecoder(resp.Body).Decode(&dynRes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("API request failed with HTTP status code %d: %s", resp.StatusCode, dynRes.Messages)
|
||||
} else if resp.StatusCode == 307 {
|
||||
// TODO add support for HTTP 307 response and long running jobs
|
||||
return nil, fmt.Errorf("API request returned HTTP 307. This is currently unsupported")
|
||||
}
|
||||
|
||||
if dynRes.Status == "failure" {
|
||||
// TODO add better error handling
|
||||
return nil, fmt.Errorf("API request failed: %s", dynRes.Messages)
|
||||
}
|
||||
|
||||
return &dynRes, nil
|
||||
}
|
157
vendor/github.com/go-acme/lego/providers/dns/dyn/dyn.go
generated
vendored
Normal file
157
vendor/github.com/go-acme/lego/providers/dns/dyn/dyn.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
// Package dyn implements a DNS provider for solving the DNS-01 challenge using Dyn Managed DNS.
|
||||
package dyn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
CustomerName string
|
||||
UserName string
|
||||
Password string
|
||||
HTTPClient *http.Client
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DYN_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DYN_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DYN_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("DYN_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// Dyn's Managed DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// DYN_CUSTOMER_NAME, DYN_USER_NAME and DYN_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("DYN_CUSTOMER_NAME", "DYN_USER_NAME", "DYN_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.CustomerName = values["DYN_CUSTOMER_NAME"]
|
||||
config.UserName = values["DYN_USER_NAME"]
|
||||
config.Password = values["DYN_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("dyn: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.CustomerName == "" || config.UserName == "" || config.Password == "" {
|
||||
return nil, fmt.Errorf("dyn: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"rdata": map[string]string{
|
||||
"txtdata": value,
|
||||
},
|
||||
"ttl": strconv.Itoa(d.config.TTL),
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn)
|
||||
_, err = d.sendRequest(http.MethodPost, resource, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
err = d.publish(authZone, "Added TXT record for ACME dns-01 challenge using lego client")
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("TXTRecord/%s/%s/", authZone, fqdn)
|
||||
url := fmt.Sprintf("%s/%s", defaultBaseURL, resource)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Auth-Token", d.token)
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("dyn: API request failed to delete TXT record HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
err = d.publish(authZone, "Removed TXT record for ACME dns-01 challenge using lego client")
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
||||
return d.logout()
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
107
vendor/github.com/go-acme/lego/providers/dns/exec/exec.go
generated
vendored
Normal file
107
vendor/github.com/go-acme/lego/providers/dns/exec/exec.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
// Package exec implements a DNS provider which runs a program for adding/removing the DNS record.
|
||||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config Provider configuration.
|
||||
type Config struct {
|
||||
Program string
|
||||
Mode string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("EXEC_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("EXEC_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider adds and removes the record for the DNS challenge by calling a
|
||||
// program with command-line parameters.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a new DNS provider which runs the program in the
|
||||
// environment variable EXEC_PATH for adding and removing the DNS record.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("EXEC_PATH")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Program = values["EXEC_PATH"]
|
||||
config.Mode = os.Getenv("EXEC_MODE")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig returns a new DNS provider which runs the given configuration
|
||||
// for adding and removing the DNS record.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("the configuration is nil")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
var args []string
|
||||
if d.config.Mode == "RAW" {
|
||||
args = []string{"present", "--", domain, token, keyAuth}
|
||||
} else {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
args = []string{"present", fqdn, value}
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.config.Program, args...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
log.Println(string(output))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
var args []string
|
||||
if d.config.Mode == "RAW" {
|
||||
args = []string{"cleanup", "--", domain, token, keyAuth}
|
||||
} else {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
args = []string{"cleanup", fqdn, value}
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.config.Program, args...)
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if len(output) > 0 {
|
||||
log.Println(string(output))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
184
vendor/github.com/go-acme/lego/providers/dns/exoscale/exoscale.go
generated
vendored
Normal file
184
vendor/github.com/go-acme/lego/providers/dns/exoscale/exoscale.go
generated
vendored
Normal file
|
@ -0,0 +1,184 @@
|
|||
// Package exoscale implements a DNS provider for solving the DNS-01 challenge using exoscale DNS.
|
||||
package exoscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/exoscale/egoscale"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.exoscale.com/dns"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
APISecret string
|
||||
Endpoint string
|
||||
HTTPClient *http.Client
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("EXOSCALE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("EXOSCALE_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("EXOSCALE_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("EXOSCALE_HTTP_TIMEOUT", 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *egoscale.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider Credentials must be passed in the environment variables:
|
||||
// EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("EXOSCALE_API_KEY", "EXOSCALE_API_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("exoscale: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["EXOSCALE_API_KEY"]
|
||||
config.APISecret = values["EXOSCALE_API_SECRET"]
|
||||
config.Endpoint = env.GetOrFile("EXOSCALE_ENDPOINT")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Exoscale.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" || config.APISecret == "" {
|
||||
return nil, fmt.Errorf("exoscale: credentials missing")
|
||||
}
|
||||
|
||||
if config.Endpoint == "" {
|
||||
config.Endpoint = defaultBaseURL
|
||||
}
|
||||
|
||||
client := egoscale.NewClient(config.Endpoint, config.APIKey, config.APISecret)
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordID, err := d.FindExistingRecordID(zone, recordName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordID == 0 {
|
||||
record := egoscale.DNSRecord{
|
||||
Name: recordName,
|
||||
TTL: d.config.TTL,
|
||||
Content: value,
|
||||
RecordType: "TXT",
|
||||
}
|
||||
|
||||
_, err := d.client.CreateRecord(ctx, zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while creating DNS record: " + err.Error())
|
||||
}
|
||||
} else {
|
||||
record := egoscale.UpdateDNSRecord{
|
||||
ID: recordID,
|
||||
Name: recordName,
|
||||
TTL: d.config.TTL,
|
||||
Content: value,
|
||||
RecordType: "TXT",
|
||||
}
|
||||
|
||||
_, err := d.client.UpdateRecord(ctx, zone, record)
|
||||
if err != nil {
|
||||
return errors.New("Error while updating DNS record: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordID, err := d.FindExistingRecordID(zone, recordName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if recordID != 0 {
|
||||
err = d.client.DeleteRecord(ctx, zone, recordID)
|
||||
if err != nil {
|
||||
return errors.New("Error while deleting DNS record: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// FindExistingRecordID Query Exoscale to find an existing record for this name.
|
||||
// Returns nil if no record could be found
|
||||
func (d *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, error) {
|
||||
ctx := context.Background()
|
||||
records, err := d.client.GetRecords(ctx, zone)
|
||||
if err != nil {
|
||||
return -1, errors.New("Error while retrievening DNS records: " + err.Error())
|
||||
}
|
||||
for _, record := range records {
|
||||
if record.Name == recordName {
|
||||
return record.ID, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// FindZoneAndRecordName Extract DNS zone and DNS entry name
|
||||
func (d *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = dns01.UnFqdn(zone)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
}
|
158
vendor/github.com/go-acme/lego/providers/dns/fastdns/fastdns.go
generated
vendored
Normal file
158
vendor/github.com/go-acme/lego/providers/dns/fastdns/fastdns.go
generated
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
// Package fastdns implements a DNS provider for solving the DNS-01 challenge using FastDNS.
|
||||
package fastdns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1"
|
||||
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
edgegrid.Config
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AKAMAI_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AKAMAI_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("AKAMAI_TTL", dns01.DefaultTTL),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider uses the supplied environment variables to return a DNSProvider instance:
|
||||
// AKAMAI_HOST, AKAMAI_CLIENT_TOKEN, AKAMAI_CLIENT_SECRET, AKAMAI_ACCESS_TOKEN
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("AKAMAI_HOST", "AKAMAI_CLIENT_TOKEN", "AKAMAI_CLIENT_SECRET", "AKAMAI_ACCESS_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Config = edgegrid.Config{
|
||||
Host: values["AKAMAI_HOST"],
|
||||
ClientToken: values["AKAMAI_CLIENT_TOKEN"],
|
||||
ClientSecret: values["AKAMAI_CLIENT_SECRET"],
|
||||
AccessToken: values["AKAMAI_ACCESS_TOKEN"],
|
||||
MaxBody: 131072,
|
||||
}
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for FastDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("fastdns: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.ClientToken == "" || config.ClientSecret == "" || config.AccessToken == "" || config.Host == "" {
|
||||
return nil, fmt.Errorf("fastdns: credentials are missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fullfil the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
configdns.Init(d.config.Config)
|
||||
|
||||
zone, err := configdns.GetZone(zoneName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
record := configdns.NewTxtRecord()
|
||||
_ = record.SetField("name", recordName)
|
||||
_ = record.SetField("ttl", d.config.TTL)
|
||||
_ = record.SetField("target", value)
|
||||
_ = record.SetField("active", true)
|
||||
|
||||
for _, r := range zone.Zone.Txt {
|
||||
if r != nil && reflect.DeepEqual(r.ToMap(), record.ToMap()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return d.createRecord(zone, record)
|
||||
}
|
||||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
configdns.Init(d.config.Config)
|
||||
|
||||
zone, err := configdns.GetZone(zoneName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
|
||||
var removed bool
|
||||
for _, r := range zone.Zone.Txt {
|
||||
if r != nil && r.Name == recordName {
|
||||
if zone.RemoveRecord(r) != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
}
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
|
||||
if removed {
|
||||
return zone.Save()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = dns01.UnFqdn(zone)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) createRecord(zone *configdns.Zone, record *configdns.TxtRecord) error {
|
||||
err := zone.AddRecord(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return zone.Save()
|
||||
}
|
316
vendor/github.com/go-acme/lego/providers/dns/gandi/client.go
generated
vendored
Normal file
316
vendor/github.com/go-acme/lego/providers/dns/gandi/client.go
generated
vendored
Normal file
|
@ -0,0 +1,316 @@
|
|||
package gandi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// types for XML-RPC method calls and parameters
|
||||
|
||||
type param interface {
|
||||
param()
|
||||
}
|
||||
|
||||
type paramString struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
Value string `xml:"value>string"`
|
||||
}
|
||||
|
||||
type paramInt struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
Value int `xml:"value>int"`
|
||||
}
|
||||
|
||||
type structMember interface {
|
||||
structMember()
|
||||
}
|
||||
type structMemberString struct {
|
||||
Name string `xml:"name"`
|
||||
Value string `xml:"value>string"`
|
||||
}
|
||||
type structMemberInt struct {
|
||||
Name string `xml:"name"`
|
||||
Value int `xml:"value>int"`
|
||||
}
|
||||
type paramStruct struct {
|
||||
XMLName xml.Name `xml:"param"`
|
||||
StructMembers []structMember `xml:"value>struct>member"`
|
||||
}
|
||||
|
||||
func (p paramString) param() {}
|
||||
func (p paramInt) param() {}
|
||||
func (m structMemberString) structMember() {}
|
||||
func (m structMemberInt) structMember() {}
|
||||
func (p paramStruct) param() {}
|
||||
|
||||
type methodCall struct {
|
||||
XMLName xml.Name `xml:"methodCall"`
|
||||
MethodName string `xml:"methodName"`
|
||||
Params []param `xml:"params"`
|
||||
}
|
||||
|
||||
// types for XML-RPC responses
|
||||
|
||||
type response interface {
|
||||
faultCode() int
|
||||
faultString() string
|
||||
}
|
||||
|
||||
type responseFault struct {
|
||||
FaultCode int `xml:"fault>value>struct>member>value>int"`
|
||||
FaultString string `xml:"fault>value>struct>member>value>string"`
|
||||
}
|
||||
|
||||
func (r responseFault) faultCode() int { return r.FaultCode }
|
||||
func (r responseFault) faultString() string { return r.FaultString }
|
||||
|
||||
type responseStruct struct {
|
||||
responseFault
|
||||
StructMembers []struct {
|
||||
Name string `xml:"name"`
|
||||
ValueInt int `xml:"value>int"`
|
||||
} `xml:"params>param>value>struct>member"`
|
||||
}
|
||||
|
||||
type responseInt struct {
|
||||
responseFault
|
||||
Value int `xml:"params>param>value>int"`
|
||||
}
|
||||
|
||||
type responseBool struct {
|
||||
responseFault
|
||||
Value bool `xml:"params>param>value>boolean"`
|
||||
}
|
||||
|
||||
type rpcError struct {
|
||||
faultCode int
|
||||
faultString string
|
||||
}
|
||||
|
||||
func (e rpcError) Error() string {
|
||||
return fmt.Sprintf("Gandi DNS: RPC Error: (%d) %s", e.faultCode, e.faultString)
|
||||
}
|
||||
|
||||
// rpcCall makes an XML-RPC call to Gandi's RPC endpoint by
|
||||
// marshaling the data given in the call argument to XML and sending
|
||||
// that via HTTP Post to Gandi.
|
||||
// The response is then unmarshalled into the resp argument.
|
||||
func (d *DNSProvider) rpcCall(call *methodCall, resp response) error {
|
||||
// marshal
|
||||
b, err := xml.MarshalIndent(call, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal error: %v", err)
|
||||
}
|
||||
|
||||
// post
|
||||
b = append([]byte(`<?xml version="1.0"?>`+"\n"), b...)
|
||||
respBody, err := d.httpPost(d.config.BaseURL, "text/xml", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshal
|
||||
err = xml.Unmarshal(respBody, resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal error: %v", err)
|
||||
}
|
||||
if resp.faultCode() != 0 {
|
||||
return rpcError{
|
||||
faultCode: resp.faultCode(), faultString: resp.faultString()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// functions to perform API actions
|
||||
|
||||
func (d *DNSProvider) getZoneID(domain string) (int, error) {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.info",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramString{Value: domain},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var zoneID int
|
||||
for _, member := range resp.StructMembers {
|
||||
if member.Name == "zone_id" {
|
||||
zoneID = member.ValueInt
|
||||
}
|
||||
}
|
||||
|
||||
if zoneID == 0 {
|
||||
return 0, fmt.Errorf("could not determine zone_id for %s", domain)
|
||||
}
|
||||
return zoneID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) cloneZone(zoneID int, name string) (int, error) {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.clone",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: 0},
|
||||
paramStruct{
|
||||
StructMembers: []structMember{
|
||||
structMemberString{
|
||||
Name: "name",
|
||||
Value: name,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var newZoneID int
|
||||
for _, member := range resp.StructMembers {
|
||||
if member.Name == "id" {
|
||||
newZoneID = member.ValueInt
|
||||
}
|
||||
}
|
||||
|
||||
if newZoneID == 0 {
|
||||
return 0, fmt.Errorf("could not determine cloned zone_id")
|
||||
}
|
||||
return newZoneID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newZoneVersion(zoneID int) (int, error) {
|
||||
resp := &responseInt{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.version.new",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if resp.Value == 0 {
|
||||
return 0, fmt.Errorf("could not create new zone version")
|
||||
}
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTXTRecord(zoneID int, version int, name string, value string, ttl int) error {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.record.add",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: version},
|
||||
paramStruct{
|
||||
StructMembers: []structMember{
|
||||
structMemberString{
|
||||
Name: "type",
|
||||
Value: "TXT",
|
||||
}, structMemberString{
|
||||
Name: "name",
|
||||
Value: name,
|
||||
}, structMemberString{
|
||||
Name: "value",
|
||||
Value: value,
|
||||
}, structMemberInt{
|
||||
Name: "ttl",
|
||||
Value: ttl,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) setZoneVersion(zoneID int, version int) error {
|
||||
resp := &responseBool{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.version.set",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
paramInt{Value: version},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Value {
|
||||
return fmt.Errorf("could not set zone version")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) setZone(domain string, zoneID int) error {
|
||||
resp := &responseStruct{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.set",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramString{Value: domain},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var respZoneID int
|
||||
for _, member := range resp.StructMembers {
|
||||
if member.Name == "zone_id" {
|
||||
respZoneID = member.ValueInt
|
||||
}
|
||||
}
|
||||
|
||||
if respZoneID != zoneID {
|
||||
return fmt.Errorf("could not set new zone_id for %s", domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteZone(zoneID int) error {
|
||||
resp := &responseBool{}
|
||||
err := d.rpcCall(&methodCall{
|
||||
MethodName: "domain.zone.delete",
|
||||
Params: []param{
|
||||
paramString{Value: d.config.APIKey},
|
||||
paramInt{Value: zoneID},
|
||||
},
|
||||
}, resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Value {
|
||||
return fmt.Errorf("could not delete zone_id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) httpPost(url string, bodyType string, body io.Reader) ([]byte, error) {
|
||||
resp, err := d.config.HTTPClient.Post(url, bodyType, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HTTP Post Error: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("HTTP Post Error: %v", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
214
vendor/github.com/go-acme/lego/providers/dns/gandi/gandi.go
generated
vendored
Normal file
214
vendor/github.com/go-acme/lego/providers/dns/gandi/gandi.go
generated
vendored
Normal file
|
@ -0,0 +1,214 @@
|
|||
// Package gandi implements a DNS provider for solving the DNS-01 challenge using Gandi DNS.
|
||||
package gandi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Gandi API reference: http://doc.rpc.gandi.net/index.html
|
||||
// Gandi API domain examples: http://doc.rpc.gandi.net/domain/faq.html
|
||||
|
||||
const (
|
||||
// defaultBaseURL Gandi XML-RPC endpoint used by Present and CleanUp
|
||||
defaultBaseURL = "https://rpc.gandi.net/xmlrpc/"
|
||||
minTTL = 300
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GANDI_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GANDI_PROPAGATION_TIMEOUT", 40*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("GANDI_POLLING_INTERVAL", 60*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GANDI_HTTP_TIMEOUT", 60*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// inProgressInfo contains information about an in-progress challenge
|
||||
type inProgressInfo struct {
|
||||
zoneID int // zoneID of gandi zone to restore in CleanUp
|
||||
newZoneID int // zoneID of temporary gandi zone containing TXT record
|
||||
authZone string // the domain name registered at gandi with trailing "."
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses Gandi's XML-RPC
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
inProgressFQDNs map[string]inProgressInfo
|
||||
inProgressAuthZones map[string]struct{}
|
||||
inProgressMu sync.Mutex
|
||||
config *Config
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden during tests.
|
||||
findZoneByFqdn func(fqdn string) (string, error)
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
||||
// Credentials must be passed in the environment variable: GANDI_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GANDI_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["GANDI_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("gandi: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("gandi: no API Key given")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
inProgressFQDNs: make(map[string]inProgressInfo),
|
||||
inProgressAuthZones: make(map[string]struct{}),
|
||||
findZoneByFqdn: dns01.FindZoneByFqdn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters. It
|
||||
// does this by creating and activating a new temporary Gandi DNS
|
||||
// zone. This new zone contains the TXT record.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
if d.config.TTL < minTTL {
|
||||
d.config.TTL = minTTL // 300 is gandi minimum value for ttl
|
||||
}
|
||||
|
||||
// find authZone and Gandi zone_id for fqdn
|
||||
authZone, err := d.findZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf("gandi: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
if _, ok := d.inProgressAuthZones[authZone]; ok {
|
||||
return fmt.Errorf("gandi: challenge already in progress for authZone %s", authZone)
|
||||
}
|
||||
|
||||
// perform API actions to create and activate new gandi zone
|
||||
// containing the required TXT record
|
||||
newZoneName := fmt.Sprintf("%s [ACME Challenge %s]", dns01.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
|
||||
|
||||
newZoneID, err := d.cloneZone(zoneID, newZoneName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newZoneVersion, err := d.newZoneVersion(newZoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
err = d.addTXTRecord(newZoneID, newZoneVersion, name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
err = d.setZoneVersion(newZoneID, newZoneVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
err = d.setZone(authZone, newZoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
// save data necessary for CleanUp
|
||||
d.inProgressFQDNs[fqdn] = inProgressInfo{
|
||||
zoneID: zoneID,
|
||||
newZoneID: newZoneID,
|
||||
authZone: authZone,
|
||||
}
|
||||
d.inProgressAuthZones[authZone] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified
|
||||
// parameters. It does this by restoring the old Gandi DNS zone and
|
||||
// removing the temporary one created by Present.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve zoneID, newZoneID and authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
if _, ok := d.inProgressFQDNs[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
|
||||
zoneID := d.inProgressFQDNs[fqdn].zoneID
|
||||
newZoneID := d.inProgressFQDNs[fqdn].newZoneID
|
||||
authZone := d.inProgressFQDNs[fqdn].authZone
|
||||
delete(d.inProgressFQDNs, fqdn)
|
||||
delete(d.inProgressAuthZones, authZone)
|
||||
|
||||
// perform API actions to restore old gandi zone for authZone
|
||||
err := d.setZone(authZone, zoneID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
return d.deleteZone(newZoneID)
|
||||
}
|
||||
|
||||
// Timeout returns the values (40*time.Minute, 60*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with Gandi.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
199
vendor/github.com/go-acme/lego/providers/dns/gandiv5/client.go
generated
vendored
Normal file
199
vendor/github.com/go-acme/lego/providers/dns/gandiv5/client.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
package gandiv5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
const apiKeyHeader = "X-Api-Key"
|
||||
|
||||
// types for JSON responses with only a message
|
||||
type apiResponse struct {
|
||||
Message string `json:"message"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
}
|
||||
|
||||
// Record TXT record representation
|
||||
type Record struct {
|
||||
RRSetTTL int `json:"rrset_ttl"`
|
||||
RRSetValues []string `json:"rrset_values"`
|
||||
RRSetName string `json:"rrset_name,omitempty"`
|
||||
RRSetType string `json:"rrset_type,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTXTRecord(domain string, name string, value string, ttl int) error {
|
||||
// Get exiting values for the TXT records
|
||||
// Needed to create challenges for both wildcard and base name domains
|
||||
txtRecord, err := d.getTXTRecord(domain, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
values := []string{value}
|
||||
if len(txtRecord.RRSetValues) > 0 {
|
||||
values = append(values, txtRecord.RRSetValues...)
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("domains/%s/records/%s/TXT", domain, name)
|
||||
|
||||
newRecord := &Record{RRSetTTL: ttl, RRSetValues: values}
|
||||
req, err := d.newRequest(http.MethodPut, target, newRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := &apiResponse{}
|
||||
err = d.do(req, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create TXT record for domain %s and name %s: %v", domain, name, err)
|
||||
}
|
||||
|
||||
if message != nil && len(message.Message) > 0 {
|
||||
log.Infof("API response: %s", message.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getTXTRecord(domain, name string) (*Record, error) {
|
||||
target := fmt.Sprintf("domains/%s/records/%s/TXT", domain, name)
|
||||
|
||||
// Get exiting values for the TXT records
|
||||
// Needed to create challenges for both wildcard and base name domains
|
||||
req, err := d.newRequest(http.MethodGet, target, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txtRecord := &Record{}
|
||||
err = d.do(req, txtRecord)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get TXT records for domain %s and name %s: %v", domain, name, err)
|
||||
}
|
||||
|
||||
return txtRecord, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteTXTRecord(domain string, name string) error {
|
||||
target := fmt.Sprintf("domains/%s/records/%s/TXT", domain, name)
|
||||
|
||||
req, err := d.newRequest(http.MethodDelete, target, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := &apiResponse{}
|
||||
err = d.do(req, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to delete TXT record for domain %s and name %s: %v", domain, name, err)
|
||||
}
|
||||
|
||||
if message != nil && len(message.Message) > 0 {
|
||||
log.Infof("API response: %s", message.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequest(method, resource string, body interface{}) (*http.Request, error) {
|
||||
u := fmt.Sprintf("%s/%s", d.config.BaseURL, resource)
|
||||
|
||||
if body == nil {
|
||||
req, err := http.NewRequest(method, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) do(req *http.Request, v interface{}) error {
|
||||
if len(d.config.APIKey) > 0 {
|
||||
req.Header.Set(apiKeyHeader, d.config.APIKey)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
if len(raw) > 0 {
|
||||
err = json.Unmarshal(raw, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling error: %v: %s", err, string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode == 404 && resp.Request.Method == http.MethodGet {
|
||||
return nil
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
data, err := readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d [%s] request failed: %v", resp.StatusCode, http.StatusText(resp.StatusCode), err)
|
||||
}
|
||||
|
||||
message := &apiResponse{}
|
||||
err = json.Unmarshal(data, message)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d [%s] request failed: %v: %s", resp.StatusCode, http.StatusText(resp.StatusCode), err, data)
|
||||
}
|
||||
return fmt.Errorf("%d [%s] request failed: %s", resp.StatusCode, http.StatusText(resp.StatusCode), message.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBody(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawBody, nil
|
||||
}
|
167
vendor/github.com/go-acme/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
Normal file
167
vendor/github.com/go-acme/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Package gandiv5 implements a DNS provider for solving the DNS-01 challenge using Gandi LiveDNS api.
|
||||
package gandiv5
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Gandi API reference: http://doc.livedns.gandi.net/
|
||||
|
||||
const (
|
||||
// defaultBaseURL endpoint is the Gandi API endpoint used by Present and CleanUp.
|
||||
defaultBaseURL = "https://dns.api.gandi.net/api/v5"
|
||||
minTTL = 300
|
||||
)
|
||||
|
||||
// inProgressInfo contains information about an in-progress challenge
|
||||
type inProgressInfo struct {
|
||||
fieldName string
|
||||
authZone string
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GANDIV5_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GANDIV5_PROPAGATION_TIMEOUT", 20*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("GANDIV5_POLLING_INTERVAL", 20*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GANDIV5_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses Gandi's LiveDNS
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
inProgressFQDNs map[string]inProgressInfo
|
||||
inProgressMu sync.Mutex
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn. It is overridden during tests.
|
||||
findZoneByFqdn func(fqdn string) (string, error)
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Gandi.
|
||||
// Credentials must be passed in the environment variable: GANDIV5_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GANDIV5_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gandi: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["GANDIV5_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("gandiv5: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("gandiv5: no API Key given")
|
||||
}
|
||||
|
||||
if config.BaseURL == "" {
|
||||
config.BaseURL = defaultBaseURL
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("gandiv5: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
inProgressFQDNs: make(map[string]inProgressInfo),
|
||||
findZoneByFqdn: dns01.FindZoneByFqdn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// find authZone
|
||||
authZone, err := d.findZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandiv5: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf("gandiv5: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
// add TXT record into authZone
|
||||
err = d.addTXTRecord(dns01.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save data necessary for CleanUp
|
||||
d.inProgressFQDNs[fqdn] = inProgressInfo{
|
||||
authZone: authZone,
|
||||
fieldName: name,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
if _, ok := d.inProgressFQDNs[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
|
||||
fieldName := d.inProgressFQDNs[fqdn].fieldName
|
||||
authZone := d.inProgressFQDNs[fqdn].authZone
|
||||
delete(d.inProgressFQDNs, fqdn)
|
||||
|
||||
// delete TXT record from authZone
|
||||
err := d.deleteTXTRecord(dns01.UnFqdn(authZone), fieldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandiv5: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout returns the values (20*time.Minute, 20*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with Gandi.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
327
vendor/github.com/go-acme/lego/providers/dns/gcloud/googlecloud.go
generated
vendored
Normal file
327
vendor/github.com/go-acme/lego/providers/dns/gcloud/googlecloud.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
// Package gcloud implements a DNS provider for solving the DNS-01 challenge using Google Cloud DNS.
|
||||
package gcloud
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/dns/v1"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
const (
|
||||
changeStatusDone = "done"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Debug bool
|
||||
Project string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Debug: env.GetOrDefaultBool("GCE_DEBUG", false),
|
||||
TTL: env.GetOrDefaultInt("GCE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GCE_PROPAGATION_TIMEOUT", 180*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("GCE_POLLING_INTERVAL", 5*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the DNSProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dns.Service
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Google Cloud DNS.
|
||||
// Project name must be passed in the environment variable: GCE_PROJECT.
|
||||
// A Service Account can be passed in the environment variable: GCE_SERVICE_ACCOUNT
|
||||
// or by specifying the keyfile location: GCE_SERVICE_ACCOUNT_FILE
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
// Use a service account file if specified via environment variable.
|
||||
if saKey := env.GetOrFile("GCE_SERVICE_ACCOUNT"); len(saKey) > 0 {
|
||||
return NewDNSProviderServiceAccountKey([]byte(saKey))
|
||||
}
|
||||
|
||||
// Use default credentials.
|
||||
project := env.GetOrDefaultString("GCE_PROJECT", "")
|
||||
return NewDNSProviderCredentials(project)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
|
||||
if project == "" {
|
||||
return nil, fmt.Errorf("googlecloud: project name missing")
|
||||
}
|
||||
|
||||
client, err := google.DefaultClient(context.Background(), dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to get Google Cloud client: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Project = project
|
||||
config.HTTPClient = client
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderServiceAccountKey uses the supplied service account JSON
|
||||
// to return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) {
|
||||
if len(saKey) == 0 {
|
||||
return nil, fmt.Errorf("googlecloud: Service Account is missing")
|
||||
}
|
||||
|
||||
// If GCE_PROJECT is non-empty it overrides the project in the service
|
||||
// account file.
|
||||
project := env.GetOrDefaultString("GCE_PROJECT", "")
|
||||
if project == "" {
|
||||
// read project id from service account file
|
||||
var datJSON struct {
|
||||
ProjectID string `json:"project_id"`
|
||||
}
|
||||
err := json.Unmarshal(saKey, &datJSON)
|
||||
if err != nil || datJSON.ProjectID == "" {
|
||||
return nil, fmt.Errorf("googlecloud: project ID not found in Google Cloud Service Account file")
|
||||
}
|
||||
project = datJSON.ProjectID
|
||||
}
|
||||
|
||||
conf, err := google.JWTConfigFromJSON(saKey, dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to acquire config: %v", err)
|
||||
}
|
||||
client := conf.Client(context.Background())
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Project = project
|
||||
config.HTTPClient = client
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderServiceAccount uses the supplied service account JSON file
|
||||
// to return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderServiceAccount(saFile string) (*DNSProvider, error) {
|
||||
if saFile == "" {
|
||||
return nil, fmt.Errorf("googlecloud: Service Account file missing")
|
||||
}
|
||||
|
||||
saKey, err := ioutil.ReadFile(saFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to read Service Account file: %v", err)
|
||||
}
|
||||
|
||||
return NewDNSProviderServiceAccountKey(saKey)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Google Cloud DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("googlecloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
svc, err := dns.New(config.HTTPClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("googlecloud: unable to create Google Cloud DNS service: %v", err)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config, client: svc}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
// Look for existing records.
|
||||
existingRrSet, err := d.findTxtRecords(zone, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
for _, rrSet := range existingRrSet {
|
||||
var rrd []string
|
||||
for _, rr := range rrSet.Rrdatas {
|
||||
data := mustUnquote(rr)
|
||||
rrd = append(rrd, data)
|
||||
|
||||
if data == value {
|
||||
log.Printf("skip: the record already exists: %s", value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
rrSet.Rrdatas = rrd
|
||||
}
|
||||
|
||||
// Attempt to delete the existing records before adding the new one.
|
||||
if len(existingRrSet) > 0 {
|
||||
if err = d.applyChanges(zone, &dns.Change{Deletions: existingRrSet}); err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
rec := &dns.ResourceRecordSet{
|
||||
Name: fqdn,
|
||||
Rrdatas: []string{value},
|
||||
Ttl: int64(d.config.TTL),
|
||||
Type: "TXT",
|
||||
}
|
||||
|
||||
// Append existing TXT record data to the new TXT record data
|
||||
for _, rrSet := range existingRrSet {
|
||||
for _, rr := range rrSet.Rrdatas {
|
||||
if rr != value {
|
||||
rec.Rrdatas = append(rec.Rrdatas, rr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
change := &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{rec},
|
||||
}
|
||||
|
||||
if err = d.applyChanges(zone, change); err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) applyChanges(zone string, change *dns.Change) error {
|
||||
if d.config.Debug {
|
||||
data, _ := json.Marshal(change)
|
||||
log.Printf("change (Create): %s", string(data))
|
||||
}
|
||||
|
||||
chg, err := d.client.Changes.Create(d.config.Project, zone, change).Do()
|
||||
if err != nil {
|
||||
if v, ok := err.(*googleapi.Error); ok {
|
||||
if v.Code == http.StatusNotFound {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(change)
|
||||
return fmt.Errorf("failed to perform changes [zone %s, change %s]: %v", zone, string(data), err)
|
||||
}
|
||||
|
||||
if chg.Status == changeStatusDone {
|
||||
return nil
|
||||
}
|
||||
|
||||
chgID := chg.Id
|
||||
|
||||
// wait for change to be acknowledged
|
||||
return wait.For("apply change", 30*time.Second, 3*time.Second, func() (bool, error) {
|
||||
if d.config.Debug {
|
||||
data, _ := json.Marshal(change)
|
||||
log.Printf("change (Get): %s", string(data))
|
||||
}
|
||||
|
||||
chg, err = d.client.Changes.Get(d.config.Project, zone, chgID).Do()
|
||||
if err != nil {
|
||||
data, _ := json.Marshal(change)
|
||||
return false, fmt.Errorf("failed to get changes [zone %s, change %s]: %v", zone, string(data), err)
|
||||
}
|
||||
|
||||
if chg.Status == changeStatusDone {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("status: %s", chg.Status)
|
||||
})
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.findTxtRecords(zone, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = d.client.Changes.Create(d.config.Project, zone, &dns.Change{Deletions: records}).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Timeout customizes the timeout values used by the ACME package for checking
|
||||
// DNS record validity.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// getHostedZone returns the managed-zone
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
zones, err := d.client.ManagedZones.
|
||||
List(d.config.Project).
|
||||
DnsName(authZone).
|
||||
Do()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
if len(zones.ManagedZones) == 0 {
|
||||
return "", fmt.Errorf("no matching domain found for domain %s", authZone)
|
||||
}
|
||||
|
||||
return zones.ManagedZones[0].Name, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) {
|
||||
recs, err := d.client.ResourceRecordSets.List(d.config.Project, zone).Name(fqdn).Type("TXT").Do()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return recs.Rrsets, nil
|
||||
}
|
||||
|
||||
func mustUnquote(raw string) string {
|
||||
clean, err := strconv.Unquote(raw)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
return clean
|
||||
}
|
91
vendor/github.com/go-acme/lego/providers/dns/glesys/client.go
generated
vendored
Normal file
91
vendor/github.com/go-acme/lego/providers/dns/glesys/client.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
package glesys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-acme/lego/log"
|
||||
)
|
||||
|
||||
// types for JSON method calls, parameters, and responses
|
||||
|
||||
type addRecordRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
Host string `json:"host"`
|
||||
Type string `json:"type"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
type deleteRecordRequest struct {
|
||||
RecordID int `json:"recordid"`
|
||||
}
|
||||
|
||||
type responseStruct struct {
|
||||
Response struct {
|
||||
Status struct {
|
||||
Code int `json:"code"`
|
||||
} `json:"status"`
|
||||
Record deleteRecordRequest `json:"record"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTXTRecord(fqdn string, domain string, name string, value string, ttl int) (int, error) {
|
||||
response, err := d.sendRequest(http.MethodPost, "addrecord", addRecordRequest{
|
||||
DomainName: domain,
|
||||
Host: name,
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
TTL: ttl,
|
||||
})
|
||||
|
||||
if response != nil && response.Response.Status.Code == http.StatusOK {
|
||||
log.Infof("[%s]: Successfully created record id %d", fqdn, response.Response.Record.RecordID)
|
||||
return response.Response.Record.RecordID, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteTXTRecord(fqdn string, recordid int) error {
|
||||
response, err := d.sendRequest(http.MethodPost, "deleterecord", deleteRecordRequest{
|
||||
RecordID: recordid,
|
||||
})
|
||||
if response != nil && response.Response.Status.Code == 200 {
|
||||
log.Infof("[%s]: Successfully deleted record id %d", fqdn, recordid)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method string, resource string, payload interface{}) (*responseStruct, error) {
|
||||
url := fmt.Sprintf("%s/%s", defaultBaseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.SetBasicAuth(d.config.APIUser, d.config.APIKey)
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var response responseStruct
|
||||
err = json.NewDecoder(resp.Body).Decode(&response)
|
||||
|
||||
return &response, err
|
||||
}
|
146
vendor/github.com/go-acme/lego/providers/dns/glesys/glesys.go
generated
vendored
Normal file
146
vendor/github.com/go-acme/lego/providers/dns/glesys/glesys.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Package glesys implements a DNS provider for solving the DNS-01 challenge using GleSYS api.
|
||||
package glesys
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultBaseURL is the GleSYS API endpoint used by Present and CleanUp.
|
||||
defaultBaseURL = "https://api.glesys.com/domain"
|
||||
minTTL = 60
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIUser string
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GLESYS_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GLESYS_PROPAGATION_TIMEOUT", 20*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("GLESYS_POLLING_INTERVAL", 20*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GLESYS_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the
|
||||
// acme.ChallengeProviderTimeout interface that uses GleSYS
|
||||
// API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
activeRecords map[string]int
|
||||
inProgressMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for GleSYS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// GLESYS_API_USER and GLESYS_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GLESYS_API_USER", "GLESYS_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("glesys: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = values["GLESYS_API_USER"]
|
||||
config.APIKey = values["GLESYS_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for GleSYS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("glesys: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIUser == "" || config.APIKey == "" {
|
||||
return nil, fmt.Errorf("glesys: incomplete credentials provided")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("glesys: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
activeRecords: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// find authZone
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("glesys: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
||||
// determine name of TXT record
|
||||
if !strings.HasSuffix(
|
||||
strings.ToLower(fqdn), strings.ToLower("."+authZone)) {
|
||||
return fmt.Errorf("glesys: unexpected authZone %s for fqdn %s", authZone, fqdn)
|
||||
}
|
||||
name := fqdn[:len(fqdn)-len("."+authZone)]
|
||||
|
||||
// acquire lock and check there is not a challenge already in
|
||||
// progress for this value of authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
|
||||
// add TXT record into authZone
|
||||
recordID, err := d.addTXTRecord(domain, dns01.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save data necessary for CleanUp
|
||||
d.activeRecords[fqdn] = recordID
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve authZone
|
||||
d.inProgressMu.Lock()
|
||||
defer d.inProgressMu.Unlock()
|
||||
if _, ok := d.activeRecords[fqdn]; !ok {
|
||||
// if there is no cleanup information then just return
|
||||
return nil
|
||||
}
|
||||
|
||||
recordID := d.activeRecords[fqdn]
|
||||
delete(d.activeRecords, fqdn)
|
||||
|
||||
// delete TXT record from authZone
|
||||
return d.deleteTXTRecord(domain, recordID)
|
||||
}
|
||||
|
||||
// Timeout returns the values (20*time.Minute, 20*time.Second) which
|
||||
// are used by the acme package as timeout and check interval values
|
||||
// when checking for DNS record propagation with GleSYS.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
53
vendor/github.com/go-acme/lego/providers/dns/godaddy/client.go
generated
vendored
Normal file
53
vendor/github.com/go-acme/lego/providers/dns/godaddy/client.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package godaddy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// DNSRecord a DNS record
|
||||
type DNSRecord struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) updateRecords(records []DNSRecord, domainZone string, recordName string) error {
|
||||
body, err := json.Marshal(records)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
resp, err = d.makeRequest(http.MethodPut, fmt.Sprintf("/v1/domains/%s/records/TXT/%s", domainZone, recordName), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, _ := ioutil.ReadAll(resp.Body)
|
||||
return fmt.Errorf("could not create record %v; Status: %v; Body: %s", string(body), resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) makeRequest(method, uri string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("%s%s", defaultBaseURL, uri), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("sso-key %s:%s", d.config.APIKey, d.config.APISecret))
|
||||
|
||||
return d.config.HTTPClient.Do(req)
|
||||
}
|
151
vendor/github.com/go-acme/lego/providers/dns/godaddy/godaddy.go
generated
vendored
Normal file
151
vendor/github.com/go-acme/lego/providers/dns/godaddy/godaddy.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Package godaddy implements a DNS provider for solving the DNS-01 challenge using godaddy DNS.
|
||||
package godaddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultBaseURL represents the API endpoint to call.
|
||||
defaultBaseURL = "https://api.godaddy.com"
|
||||
minTTL = 600
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
APISecret string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
SequenceInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GODADDY_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("GODADDY_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("GODADDY_POLLING_INTERVAL", 2*time.Second),
|
||||
SequenceInterval: env.GetOrDefaultSecond("GODADDY_SEQUENCE_INTERVAL", dns01.DefaultPropagationTimeout),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("GODADDY_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for godaddy.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// GODADDY_API_KEY and GODADDY_API_SECRET.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("GODADDY_API_KEY", "GODADDY_API_SECRET")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("godaddy: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["GODADDY_API_KEY"]
|
||||
config.APISecret = values["GODADDY_API_SECRET"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for godaddy.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("godaddy: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" || config.APISecret == "" {
|
||||
return nil, fmt.Errorf("godaddy: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("godaddy: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
domainZone, err := d.getZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, domainZone)
|
||||
rec := []DNSRecord{
|
||||
{
|
||||
Type: "TXT",
|
||||
Name: recordName,
|
||||
Data: value,
|
||||
TTL: d.config.TTL,
|
||||
},
|
||||
}
|
||||
|
||||
return d.updateRecords(rec, domainZone, recordName)
|
||||
}
|
||||
|
||||
// CleanUp sets null value in the TXT DNS record as GoDaddy has no proper DELETE record method
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
domainZone, err := d.getZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recordName := d.extractRecordName(fqdn, domainZone)
|
||||
rec := []DNSRecord{
|
||||
{
|
||||
Type: "TXT",
|
||||
Name: recordName,
|
||||
Data: "null",
|
||||
},
|
||||
}
|
||||
|
||||
return d.updateRecords(rec, domainZone, recordName)
|
||||
}
|
||||
|
||||
// Sequential All DNS challenges for this provider will be resolved sequentially.
|
||||
// Returns the interval between each iteration.
|
||||
func (d *DNSProvider) Sequential() time.Duration {
|
||||
return d.config.SequenceInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZone(fqdn string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dns01.UnFqdn(authZone), nil
|
||||
}
|
127
vendor/github.com/go-acme/lego/providers/dns/hostingde/client.go
generated
vendored
Normal file
127
vendor/github.com/go-acme/lego/providers/dns/hostingde/client.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
package hostingde
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json"
|
||||
|
||||
// https://www.hosting.de/api/?json#list-zoneconfigs
|
||||
func (d *DNSProvider) listZoneConfigs(findRequest ZoneConfigsFindRequest) (*ZoneConfigsFindResponse, error) {
|
||||
uri := defaultBaseURL + "/zoneConfigsFind"
|
||||
|
||||
findResponse := &ZoneConfigsFindResponse{}
|
||||
|
||||
rawResp, err := d.post(uri, findRequest, findResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(findResponse.Response.Data) == 0 {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(uri, rawResp))
|
||||
}
|
||||
|
||||
if findResponse.Status != "success" && findResponse.Status != "pending" {
|
||||
return findResponse, errors.New(toUnreadableBodyMessage(uri, rawResp))
|
||||
}
|
||||
|
||||
return findResponse, nil
|
||||
}
|
||||
|
||||
// https://www.hosting.de/api/?json#updating-zones
|
||||
func (d *DNSProvider) updateZone(updateRequest ZoneUpdateRequest) (*ZoneUpdateResponse, error) {
|
||||
uri := defaultBaseURL + "/zoneUpdate"
|
||||
|
||||
// but we'll need the ID later to delete the record
|
||||
updateResponse := &ZoneUpdateResponse{}
|
||||
|
||||
rawResp, err := d.post(uri, updateRequest, updateResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if updateResponse.Status != "success" && updateResponse.Status != "pending" {
|
||||
return nil, errors.New(toUnreadableBodyMessage(uri, rawResp))
|
||||
}
|
||||
|
||||
return updateResponse, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZone(findRequest ZoneConfigsFindRequest) (*ZoneConfig, error) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
var zoneConfig *ZoneConfig
|
||||
|
||||
operation := func() error {
|
||||
findResponse, err := d.listZoneConfigs(findRequest)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
if findResponse.Response.Data[0].Status != "active" {
|
||||
return fmt.Errorf("unexpected status: %q", findResponse.Response.Data[0].Status)
|
||||
}
|
||||
|
||||
zoneConfig = &findResponse.Response.Data[0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
bo := backoff.NewExponentialBackOff()
|
||||
bo.InitialInterval = 3 * time.Second
|
||||
bo.MaxInterval = 10 * bo.InitialInterval
|
||||
bo.MaxElapsedTime = 100 * bo.InitialInterval
|
||||
|
||||
// retry in case the zone was edited recently and is not yet active
|
||||
err := backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return zoneConfig, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) post(uri string, request interface{}, response interface{}) ([]byte, error) {
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying API: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New(toUnreadableBodyMessage(uri, content))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(content, response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(uri, content))
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func toUnreadableBodyMessage(uri string, rawBody []byte) string {
|
||||
return fmt.Sprintf("the request %s sent a response with a body which is an invalid format: %q", uri, string(rawBody))
|
||||
}
|
183
vendor/github.com/go-acme/lego/providers/dns/hostingde/hostingde.go
generated
vendored
Normal file
183
vendor/github.com/go-acme/lego/providers/dns/hostingde/hostingde.go
generated
vendored
Normal file
|
@ -0,0 +1,183 @@
|
|||
// Package hostingde implements a DNS provider for solving the DNS-01 challenge using hosting.de.
|
||||
package hostingde
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
ZoneName string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("HOSTINGDE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("HOSTINGDE_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("HOSTINGDE_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("HOSTINGDE_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
recordIDs map[string]string
|
||||
recordIDsMu sync.Mutex
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for hosting.de.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// HOSTINGDE_ZONE_NAME and HOSTINGDE_API_KEY
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("HOSTINGDE_API_KEY", "HOSTINGDE_ZONE_NAME")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["HOSTINGDE_API_KEY"]
|
||||
config.ZoneName = values["HOSTINGDE_ZONE_NAME"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for hosting.de.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("hostingde: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, errors.New("hostingde: API key missing")
|
||||
}
|
||||
|
||||
if config.ZoneName == "" {
|
||||
return nil, errors.New("hostingde: Zone Name missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
recordIDs: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the ZoneConfig for that domain
|
||||
zonesFind := ZoneConfigsFindRequest{
|
||||
Filter: Filter{
|
||||
Field: "zoneName",
|
||||
Value: domain,
|
||||
},
|
||||
Limit: 1,
|
||||
Page: 1,
|
||||
}
|
||||
zonesFind.AuthToken = d.config.APIKey
|
||||
|
||||
zoneConfig, err := d.getZone(zonesFind)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
zoneConfig.Name = d.config.ZoneName
|
||||
|
||||
rec := []DNSRecord{{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}}
|
||||
|
||||
req := ZoneUpdateRequest{
|
||||
ZoneConfig: *zoneConfig,
|
||||
RecordsToAdd: rec,
|
||||
}
|
||||
req.AuthToken = d.config.APIKey
|
||||
|
||||
resp, err := d.updateZone(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
|
||||
for _, record := range resp.Response.Records {
|
||||
if record.Name == dns01.UnFqdn(fqdn) && record.Content == fmt.Sprintf(`"%s"`, value) {
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = record.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
if d.recordIDs[fqdn] == "" {
|
||||
return fmt.Errorf("hostingde: error getting ID of just created record, for domain %s", domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
rec := []DNSRecord{{
|
||||
Type: "TXT",
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: `"` + value + `"`,
|
||||
}}
|
||||
|
||||
// get the ZoneConfig for that domain
|
||||
zonesFind := ZoneConfigsFindRequest{
|
||||
Filter: Filter{
|
||||
Field: "zoneName",
|
||||
Value: domain,
|
||||
},
|
||||
Limit: 1,
|
||||
Page: 1,
|
||||
}
|
||||
zonesFind.AuthToken = d.config.APIKey
|
||||
|
||||
zoneConfig, err := d.getZone(zonesFind)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
zoneConfig.Name = d.config.ZoneName
|
||||
|
||||
req := ZoneUpdateRequest{
|
||||
ZoneConfig: *zoneConfig,
|
||||
RecordsToDelete: rec,
|
||||
}
|
||||
req.AuthToken = d.config.APIKey
|
||||
|
||||
// Delete record ID from map
|
||||
d.recordIDsMu.Lock()
|
||||
delete(d.recordIDs, fqdn)
|
||||
d.recordIDsMu.Unlock()
|
||||
|
||||
_, err = d.updateZone(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("hostingde: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
139
vendor/github.com/go-acme/lego/providers/dns/hostingde/model.go
generated
vendored
Normal file
139
vendor/github.com/go-acme/lego/providers/dns/hostingde/model.go
generated
vendored
Normal file
|
@ -0,0 +1,139 @@
|
|||
package hostingde
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// APIError represents an error in an API response.
|
||||
// https://www.hosting.de/api/?json#warnings-and-errors
|
||||
type APIError struct {
|
||||
Code int `json:"code"`
|
||||
ContextObject string `json:"contextObject"`
|
||||
ContextPath string `json:"contextPath"`
|
||||
Details []string `json:"details"`
|
||||
Text string `json:"text"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Filter is used to filter FindRequests to the API.
|
||||
// https://www.hosting.de/api/?json#filter-object
|
||||
type Filter struct {
|
||||
Field string `json:"field"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Sort is used to sort FindRequests from the API.
|
||||
// https://www.hosting.de/api/?json#filtering-and-sorting
|
||||
type Sort struct {
|
||||
Field string `json:"zoneName"`
|
||||
Order string `json:"order"`
|
||||
}
|
||||
|
||||
// Metadata represents the metadata in an API response.
|
||||
// https://www.hosting.de/api/?json#metadata-object
|
||||
type Metadata struct {
|
||||
ClientTransactionID string `json:"clientTransactionId"`
|
||||
ServerTransactionID string `json:"serverTransactionId"`
|
||||
}
|
||||
|
||||
// ZoneConfig The ZoneConfig object defines a zone.
|
||||
// https://www.hosting.de/api/?json#the-zoneconfig-object
|
||||
type ZoneConfig struct {
|
||||
ID string `json:"id"`
|
||||
AccountID string `json:"accountId"`
|
||||
Status string `json:"status"`
|
||||
Name string `json:"name"`
|
||||
NameUnicode string `json:"nameUnicode"`
|
||||
MasterIP string `json:"masterIp"`
|
||||
Type string `json:"type"`
|
||||
EMailAddress string `json:"emailAddress"`
|
||||
ZoneTransferWhitelist []string `json:"zoneTransferWhitelist"`
|
||||
LastChangeDate string `json:"lastChangeDate"`
|
||||
DNSServerGroupID string `json:"dnsServerGroupId"`
|
||||
DNSSecMode string `json:"dnsSecMode"`
|
||||
SOAValues *SOAValues `json:"soaValues,omitempty"`
|
||||
TemplateValues json.RawMessage `json:"templateValues,omitempty"`
|
||||
}
|
||||
|
||||
// SOAValues The SOA values object contains the time (seconds) used in a zone’s SOA record.
|
||||
// https://www.hosting.de/api/?json#the-soa-values-object
|
||||
type SOAValues struct {
|
||||
Refresh int `json:"refresh"`
|
||||
Retry int `json:"retry"`
|
||||
Expire int `json:"expire"`
|
||||
TTL int `json:"ttl"`
|
||||
NegativeTTL int `json:"negativeTtl"`
|
||||
}
|
||||
|
||||
// DNSRecord The DNS Record object is part of a zone. It is used to manage DNS resource records.
|
||||
// https://www.hosting.de/api/?json#the-record-object
|
||||
type DNSRecord struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
ZoneID string `json:"zoneId,omitempty"`
|
||||
RecordTemplateID string `json:"recordTemplateId,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
LastChangeDate string `json:"lastChangeDate,omitempty"`
|
||||
}
|
||||
|
||||
// Zone The Zone Object.
|
||||
// https://www.hosting.de/api/?json#the-zone-object
|
||||
type Zone struct {
|
||||
Records []DNSRecord `json:"records"`
|
||||
ZoneConfig ZoneConfig `json:"zoneConfig"`
|
||||
}
|
||||
|
||||
// ZoneUpdateRequest represents a API ZoneUpdate request.
|
||||
// https://www.hosting.de/api/?json#updating-zones
|
||||
type ZoneUpdateRequest struct {
|
||||
BaseRequest
|
||||
ZoneConfig `json:"zoneConfig"`
|
||||
RecordsToAdd []DNSRecord `json:"recordsToAdd"`
|
||||
RecordsToDelete []DNSRecord `json:"recordsToDelete"`
|
||||
}
|
||||
|
||||
// ZoneUpdateResponse represents a response from the API.
|
||||
// https://www.hosting.de/api/?json#updating-zones
|
||||
type ZoneUpdateResponse struct {
|
||||
BaseResponse
|
||||
Response Zone `json:"response"`
|
||||
}
|
||||
|
||||
// ZoneConfigsFindRequest represents a API ZonesFind request.
|
||||
// https://www.hosting.de/api/?json#list-zoneconfigs
|
||||
type ZoneConfigsFindRequest struct {
|
||||
BaseRequest
|
||||
Filter Filter `json:"filter"`
|
||||
Limit int `json:"limit"`
|
||||
Page int `json:"page"`
|
||||
Sort *Sort `json:"sort,omitempty"`
|
||||
}
|
||||
|
||||
// ZoneConfigsFindResponse represents the API response for ZoneConfigsFind.
|
||||
// https://www.hosting.de/api/?json#list-zoneconfigs
|
||||
type ZoneConfigsFindResponse struct {
|
||||
BaseResponse
|
||||
Response struct {
|
||||
Limit int `json:"limit"`
|
||||
Page int `json:"page"`
|
||||
TotalEntries int `json:"totalEntries"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Type string `json:"type"`
|
||||
Data []ZoneConfig `json:"data"`
|
||||
} `json:"response"`
|
||||
}
|
||||
|
||||
// BaseResponse Common response struct.
|
||||
// https://www.hosting.de/api/?json#responses
|
||||
type BaseResponse struct {
|
||||
Errors []APIError `json:"errors"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Warnings []string `json:"warnings"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// BaseRequest Common request struct.
|
||||
type BaseRequest struct {
|
||||
AuthToken string `json:"authToken"`
|
||||
}
|
195
vendor/github.com/go-acme/lego/providers/dns/httpreq/httpreq.go
generated
vendored
Normal file
195
vendor/github.com/go-acme/lego/providers/dns/httpreq/httpreq.go
generated
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
// Package httpreq implements a DNS provider for solving the DNS-01 challenge through a HTTP server.
|
||||
package httpreq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
FQDN string `json:"fqdn"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type messageRaw struct {
|
||||
Domain string `json:"domain"`
|
||||
Token string `json:"token"`
|
||||
KeyAuth string `json:"keyAuth"`
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Endpoint *url.URL
|
||||
Mode string
|
||||
Username string
|
||||
Password string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("HTTPREQ_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider describes a provider for acme-proxy
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("HTTPREQ_ENDPOINT")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
|
||||
endpoint, err := url.Parse(values["HTTPREQ_ENDPOINT"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Mode = os.Getenv("HTTPREQ_MODE")
|
||||
config.Username = os.Getenv("HTTPREQ_USERNAME")
|
||||
config.Password = os.Getenv("HTTPREQ_PASSWORD")
|
||||
config.Endpoint = endpoint
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider .
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("httpreq: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Endpoint == nil {
|
||||
return nil, errors.New("httpreq: the endpoint is missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
if d.config.Mode == "RAW" {
|
||||
msg := &messageRaw{
|
||||
Domain: domain,
|
||||
Token: token,
|
||||
KeyAuth: keyAuth,
|
||||
}
|
||||
|
||||
err := d.doPost("/present", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
msg := &message{
|
||||
FQDN: fqdn,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
err := d.doPost("/present", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
if d.config.Mode == "RAW" {
|
||||
msg := &messageRaw{
|
||||
Domain: domain,
|
||||
Token: token,
|
||||
KeyAuth: keyAuth,
|
||||
}
|
||||
|
||||
err := d.doPost("/cleanup", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
msg := &message{
|
||||
FQDN: fqdn,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
err := d.doPost("/cleanup", msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("httpreq: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) doPost(uri string, msg interface{}) error {
|
||||
reqBody := &bytes.Buffer{}
|
||||
err := json.NewEncoder(reqBody).Encode(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newURI := path.Join(d.config.Endpoint.EscapedPath(), uri)
|
||||
endpoint, err := d.config.Endpoint.Parse(newURI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, endpoint.String(), reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if len(d.config.Username) > 0 && len(d.config.Password) > 0 {
|
||||
req.SetBasicAuth(d.config.Username, d.config.Password)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%d: failed to read response body: %v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%d: request failed: %v", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
239
vendor/github.com/go-acme/lego/providers/dns/iij/iij.go
generated
vendored
Normal file
239
vendor/github.com/go-acme/lego/providers/dns/iij/iij.go
generated
vendored
Normal file
|
@ -0,0 +1,239 @@
|
|||
// Package iij implements a DNS provider for solving the DNS-01 challenge using IIJ DNS.
|
||||
package iij
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/iij/doapi"
|
||||
"github.com/iij/doapi/protocol"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
DoServiceCode string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("IIJ_TTL", 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("IIJ_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("IIJ_POLLING_INTERVAL", 4*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
api *doapi.API
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for IIJ DO
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("IIJ_API_ACCESS_KEY", "IIJ_API_SECRET_KEY", "IIJ_DO_SERVICE_CODE")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("iij: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccessKey = values["IIJ_API_ACCESS_KEY"]
|
||||
config.SecretKey = values["IIJ_API_SECRET_KEY"]
|
||||
config.DoServiceCode = values["IIJ_DO_SERVICE_CODE"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig takes a given config
|
||||
// and returns a custom configured DNSProvider instance
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config.SecretKey == "" || config.AccessKey == "" || config.DoServiceCode == "" {
|
||||
return nil, fmt.Errorf("iij: credentials missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
api: doapi.NewAPI(config.AccessKey, config.SecretKey),
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.addTxtRecord(domain, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iij: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.deleteTxtRecord(domain, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("iij: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) addTxtRecord(domain, value string) error {
|
||||
zones, err := d.listZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner, zone, err := splitDomain(domain, zones)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := protocol.RecordAdd{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
ZoneName: zone,
|
||||
Owner: owner,
|
||||
TTL: strconv.Itoa(d.config.TTL),
|
||||
RecordType: "TXT",
|
||||
RData: value,
|
||||
}
|
||||
|
||||
response := &protocol.RecordAddResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.commit()
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteTxtRecord(domain, value string) error {
|
||||
zones, err := d.listZones()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
owner, zone, err := splitDomain(domain, zones)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := d.findTxtRecord(owner, zone, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := protocol.RecordDelete{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
ZoneName: zone,
|
||||
RecordID: id,
|
||||
}
|
||||
|
||||
response := &protocol.RecordDeleteResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.commit()
|
||||
}
|
||||
|
||||
func (d *DNSProvider) commit() error {
|
||||
request := protocol.Commit{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
}
|
||||
|
||||
response := &protocol.CommitResponse{}
|
||||
|
||||
return doapi.Call(*d.api, request, response)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) findTxtRecord(owner, zone, value string) (string, error) {
|
||||
request := protocol.RecordListGet{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
ZoneName: zone,
|
||||
}
|
||||
|
||||
response := &protocol.RecordListGetResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var id string
|
||||
|
||||
for _, record := range response.RecordList {
|
||||
if record.Owner == owner && record.RecordType == "TXT" && record.RData == "\""+value+"\"" {
|
||||
id = record.Id
|
||||
}
|
||||
}
|
||||
|
||||
if id == "" {
|
||||
return "", fmt.Errorf("%s record in %s not found", owner, zone)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) listZones() ([]string, error) {
|
||||
request := protocol.ZoneListGet{
|
||||
DoServiceCode: d.config.DoServiceCode,
|
||||
}
|
||||
|
||||
response := &protocol.ZoneListGetResponse{}
|
||||
|
||||
if err := doapi.Call(*d.api, request, response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response.ZoneList, nil
|
||||
}
|
||||
|
||||
func splitDomain(domain string, zones []string) (string, string, error) {
|
||||
parts := strings.Split(strings.Trim(domain, "."), ".")
|
||||
|
||||
var owner string
|
||||
var zone string
|
||||
|
||||
for i := 0; i < len(parts)-1; i++ {
|
||||
zone = strings.Join(parts[i:], ".")
|
||||
if zoneContains(zone, zones) {
|
||||
baseOwner := strings.Join(parts[0:i], ".")
|
||||
if len(baseOwner) > 0 {
|
||||
baseOwner = "." + baseOwner
|
||||
}
|
||||
owner = "_acme-challenge" + baseOwner
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(owner) == 0 {
|
||||
return "", "", fmt.Errorf("%s not found", domain)
|
||||
}
|
||||
|
||||
return owner, zone, nil
|
||||
}
|
||||
|
||||
func zoneContains(zone string, zones []string) bool {
|
||||
for _, z := range zones {
|
||||
if zone == z {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
166
vendor/github.com/go-acme/lego/providers/dns/inwx/inwx.go
generated
vendored
Normal file
166
vendor/github.com/go-acme/lego/providers/dns/inwx/inwx.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
// Package inwx implements a DNS provider for solving the DNS-01 challenge using inwx dom robot
|
||||
package inwx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/nrdcg/goinwx"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Username string
|
||||
Password string
|
||||
Sandbox bool
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("INWX_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("INWX_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("INWX_TTL", 300),
|
||||
Sandbox: env.GetOrDefaultBool("INWX_SANDBOX", false),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *goinwx.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// INWX_USERNAME and INWX_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("INWX_USERNAME", "INWX_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Username = values["INWX_USERNAME"]
|
||||
config.Password = values["INWX_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("inwx: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Username == "" || config.Password == "" {
|
||||
return nil, fmt.Errorf("inwx: credentials missing")
|
||||
}
|
||||
|
||||
if config.Sandbox {
|
||||
log.Infof("inwx: sandbox mode is enabled")
|
||||
}
|
||||
|
||||
client := goinwx.NewClient(config.Username, config.Password, &goinwx.ClientOptions{Sandbox: config.Sandbox})
|
||||
|
||||
return &DNSProvider{config: config, client: client}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.Account.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
errL := d.client.Account.Logout()
|
||||
if errL != nil {
|
||||
log.Infof("inwx: failed to logout: %v", errL)
|
||||
}
|
||||
}()
|
||||
|
||||
var request = &goinwx.NameserverRecordRequest{
|
||||
Domain: dns01.UnFqdn(authZone),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
_, err = d.client.Nameservers.CreateRecord(request)
|
||||
if err != nil {
|
||||
switch er := err.(type) {
|
||||
case *goinwx.ErrorResponse:
|
||||
if er.Message == "Object exists" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
default:
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
err = d.client.Account.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
errL := d.client.Account.Logout()
|
||||
if errL != nil {
|
||||
log.Infof("inwx: failed to logout: %v", errL)
|
||||
}
|
||||
}()
|
||||
|
||||
response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{
|
||||
Domain: dns01.UnFqdn(authZone),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
||||
var lastErr error
|
||||
for _, record := range response.Records {
|
||||
err = d.client.Nameservers.DeleteRecord(record.ID)
|
||||
if err != nil {
|
||||
lastErr = fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
153
vendor/github.com/go-acme/lego/providers/dns/lightsail/lightsail.go
generated
vendored
Normal file
153
vendor/github.com/go-acme/lego/providers/dns/lightsail/lightsail.go
generated
vendored
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Package lightsail implements a DNS provider for solving the DNS-01 challenge using AWS Lightsail DNS.
|
||||
package lightsail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/lightsail"
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetries = 5
|
||||
)
|
||||
|
||||
// customRetryer implements the client.Retryer interface by composing the DefaultRetryer.
|
||||
// It controls the logic for retrying recoverable request errors (e.g. when rate limits are exceeded).
|
||||
type customRetryer struct {
|
||||
client.DefaultRetryer
|
||||
}
|
||||
|
||||
// RetryRules overwrites the DefaultRetryer's method.
|
||||
// It uses a basic exponential backoff algorithm that returns an initial
|
||||
// delay of ~400ms with an upper limit of ~30 seconds which should prevent
|
||||
// causing a high number of consecutive throttling errors.
|
||||
// For reference: Route 53 enforces an account-wide(!) 5req/s query limit.
|
||||
func (c customRetryer) RetryRules(r *request.Request) time.Duration {
|
||||
retryCount := r.RetryCount
|
||||
if retryCount > 7 {
|
||||
retryCount = 7
|
||||
}
|
||||
|
||||
delay := (1 << uint(retryCount)) * (rand.Intn(50) + 200)
|
||||
return time.Duration(delay) * time.Millisecond
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
DNSZone string
|
||||
Region string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
DNSZone: env.GetOrFile("DNS_ZONE"),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("LIGHTSAIL_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("LIGHTSAIL_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
Region: env.GetOrDefaultString("LIGHTSAIL_REGION", "us-east-1"),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *lightsail.Lightsail
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for the AWS Lightsail service.
|
||||
//
|
||||
// AWS Credentials are automatically detected in the following locations
|
||||
// and prioritized in the following order:
|
||||
// 1. Environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
|
||||
// [AWS_SESSION_TOKEN], [DNS_ZONE], [LIGHTSAIL_REGION]
|
||||
// 2. Shared credentials file (defaults to ~/.aws/credentials)
|
||||
// 3. Amazon EC2 IAM role
|
||||
//
|
||||
// public hosted zone via the FQDN.
|
||||
//
|
||||
// See also: https://github.com/aws/aws-sdk-go/wiki/configuring-sdk
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
return NewDNSProviderConfig(NewDefaultConfig())
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for AWS Lightsail.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("lightsail: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
retryer := customRetryer{}
|
||||
retryer.NumMaxRetries = maxRetries
|
||||
|
||||
conf := aws.NewConfig().WithRegion(config.Region)
|
||||
sess, err := session.NewSession(request.WithRetryer(conf, retryer))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: lightsail.New(sess),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.newTxtRecord(fqdn, `"`+value+`"`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lightsail: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
params := &lightsail.DeleteDomainEntryInput{
|
||||
DomainName: aws.String(d.config.DNSZone),
|
||||
DomainEntry: &lightsail.DomainEntry{
|
||||
Name: aws.String(fqdn),
|
||||
Type: aws.String("TXT"),
|
||||
Target: aws.String(`"` + value + `"`),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := d.client.DeleteDomainEntry(params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("lightsail: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newTxtRecord(fqdn string, value string) error {
|
||||
params := &lightsail.CreateDomainEntryInput{
|
||||
DomainName: aws.String(d.config.DNSZone),
|
||||
DomainEntry: &lightsail.DomainEntry{
|
||||
Name: aws.String(fqdn),
|
||||
Target: aws.String(value),
|
||||
Type: aws.String("TXT"),
|
||||
},
|
||||
}
|
||||
_, err := d.client.CreateDomainEntry(params)
|
||||
return err
|
||||
}
|
164
vendor/github.com/go-acme/lego/providers/dns/linode/linode.go
generated
vendored
Normal file
164
vendor/github.com/go-acme/lego/providers/dns/linode/linode.go
generated
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
// Package linode implements a DNS provider for solving the DNS-01 challenge using Linode DNS.
|
||||
package linode
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/timewasted/linode/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
minTTL = 300
|
||||
dnsUpdateFreqMins = 15
|
||||
dnsUpdateFudgeSecs = 120
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PollingInterval: env.GetOrDefaultSecond("LINODE_POLLING_INTERVAL", 15*time.Second),
|
||||
TTL: env.GetOrDefaultInt("LINODE_TTL", minTTL),
|
||||
}
|
||||
}
|
||||
|
||||
type hostedZoneInfo struct {
|
||||
domainID int
|
||||
resourceName string
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *dns.DNS
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Linode.
|
||||
// Credentials must be passed in the environment variable: LINODE_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("LINODE_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("linode: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["LINODE_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Linode.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("linode: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if len(config.APIKey) == 0 {
|
||||
return nil, errors.New("linode: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("linode: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: dns.New(config.APIKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Since Linode only updates their zone files every X minutes, we need
|
||||
// to figure out how many minutes we have to wait until we hit the next
|
||||
// interval of X. We then wait another couple of minutes, just to be
|
||||
// safe. Hopefully at some point during all of this, the record will
|
||||
// have propagated throughout Linode's network.
|
||||
minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins)
|
||||
|
||||
timeout = (time.Duration(minsRemaining) * time.Minute) +
|
||||
(minTTL * time.Second) +
|
||||
(dnsUpdateFudgeSecs * time.Second)
|
||||
interval = d.config.PollingInterval
|
||||
return
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = d.client.CreateDomainResourceTXT(zone.domainID, dns01.UnFqdn(fqdn), value, d.config.TTL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all TXT records for the specified domain.
|
||||
resources, err := d.client.GetResourcesByType(zone.domainID, "TXT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the specified resource, if it exists.
|
||||
for _, resource := range resources {
|
||||
if resource.Name == zone.resourceName && resource.Target == value {
|
||||
resp, err := d.client.DeleteDomainResource(resource.DomainID, resource.ResourceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.ResourceID != resource.ResourceID {
|
||||
return errors.New("error deleting resource: resource IDs do not match")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceName := strings.TrimSuffix(fqdn, "."+authZone)
|
||||
|
||||
// Query the authority zone.
|
||||
domain, err := d.client.GetDomain(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &hostedZoneInfo{
|
||||
domainID: domain.DomainID,
|
||||
resourceName: resourceName,
|
||||
}, nil
|
||||
}
|
190
vendor/github.com/go-acme/lego/providers/dns/linodev4/linodev4.go
generated
vendored
Normal file
190
vendor/github.com/go-acme/lego/providers/dns/linodev4/linodev4.go
generated
vendored
Normal file
|
@ -0,0 +1,190 @@
|
|||
// Package linodev4 implements a DNS provider for solving the DNS-01 challenge using Linode DNS and Linode's APIv4
|
||||
package linodev4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/linode/linodego"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
minTTL = 300
|
||||
dnsUpdateFreqMins = 15
|
||||
dnsUpdateFudgeSecs = 120
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Token string
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PollingInterval: env.GetOrDefaultSecond("LINODE_POLLING_INTERVAL", 15*time.Second),
|
||||
TTL: env.GetOrDefaultInt("LINODE_TTL", minTTL),
|
||||
HTTPTimeout: env.GetOrDefaultSecond("LINODE_HTTP_TIMEOUT", 0),
|
||||
}
|
||||
}
|
||||
|
||||
type hostedZoneInfo struct {
|
||||
domainID int
|
||||
resourceName string
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *linodego.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Linode.
|
||||
// Credentials must be passed in the environment variable: LINODE_TOKEN.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("LINODE_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("linodev4: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Token = values["LINODE_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Linode.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("linodev4: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if len(config.Token) == 0 {
|
||||
return nil, errors.New("linodev4: Linode Access Token missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("linodev4: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.Token})
|
||||
oauth2Client := &http.Client{
|
||||
Timeout: config.HTTPTimeout,
|
||||
Transport: &oauth2.Transport{
|
||||
Source: tokenSource,
|
||||
},
|
||||
}
|
||||
|
||||
client := linodego.NewClient(oauth2Client)
|
||||
client.SetUserAgent(fmt.Sprintf("lego-dns linodego/%s", linodego.Version))
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
client: &client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Since Linode only updates their zone files every X minutes, we need
|
||||
// to figure out how many minutes we have to wait until we hit the next
|
||||
// interval of X. We then wait another couple of minutes, just to be
|
||||
// safe. Hopefully at some point during all of this, the record will
|
||||
// have propagated throughout Linode's network.
|
||||
minsRemaining := dnsUpdateFreqMins - (time.Now().Minute() % dnsUpdateFreqMins)
|
||||
|
||||
timeout = (time.Duration(minsRemaining) * time.Minute) +
|
||||
(minTTL * time.Second) +
|
||||
(dnsUpdateFudgeSecs * time.Second)
|
||||
interval = d.config.PollingInterval
|
||||
return
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createOpts := linodego.DomainRecordCreateOptions{
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Target: value,
|
||||
TTLSec: d.config.TTL,
|
||||
Type: linodego.RecordTypeTXT,
|
||||
}
|
||||
|
||||
_, err = d.client.CreateDomainRecord(context.Background(), zone.domainID, createOpts)
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all TXT records for the specified domain.
|
||||
listOpts := linodego.NewListOptions(0, "{\"type\":\"TXT\"}")
|
||||
resources, err := d.client.ListDomainRecords(context.Background(), zone.domainID, listOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the specified resource, if it exists.
|
||||
for _, resource := range resources {
|
||||
if (resource.Name == strings.TrimSuffix(fqdn, ".") || resource.Name == zone.resourceName) &&
|
||||
resource.Target == value {
|
||||
if err := d.client.DeleteDomainRecord(context.Background(), zone.domainID, resource.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Query the authority zone.
|
||||
data, err := json.Marshal(map[string]string{"domain": dns01.UnFqdn(authZone)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listOpts := linodego.NewListOptions(0, string(data))
|
||||
domains, err := d.client.ListDomains(context.Background(), listOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
return nil, fmt.Errorf("domain not found")
|
||||
}
|
||||
|
||||
return &hostedZoneInfo{
|
||||
domainID: domains[0].ID,
|
||||
resourceName: strings.TrimSuffix(fqdn, "."+authZone),
|
||||
}, nil
|
||||
}
|
52
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/client.go
generated
vendored
Normal file
52
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/client.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package mydnsjp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (d *DNSProvider) doRequest(domain, value string, cmd string) error {
|
||||
req, err := d.buildRequest(domain, value, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error querying API: %v", err)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var content []byte
|
||||
content, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("request %s failed [status code %d]: %s", req.URL, resp.StatusCode, string(content))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) buildRequest(domain, value string, cmd string) (*http.Request, error) {
|
||||
params := url.Values{}
|
||||
params.Set("CERTBOT_DOMAIN", domain)
|
||||
params.Set("CERTBOT_VALIDATION", value)
|
||||
params.Set("EDIT_CMD", cmd)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, defaultBaseURL, strings.NewReader(params.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetBasicAuth(d.config.MasterID, d.config.Password)
|
||||
|
||||
return req, nil
|
||||
}
|
93
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/mydnsjp.go
generated
vendored
Normal file
93
vendor/github.com/go-acme/lego/providers/dns/mydnsjp/mydnsjp.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Package mydnsjp implements a DNS provider for solving the DNS-01 challenge using MyDNS.jp.
|
||||
package mydnsjp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://www.mydns.jp/directedit.html"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
MasterID string
|
||||
Password string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("MYDNSJP_PROPAGATION_TIMEOUT", 2*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("MYDNSJP_POLLING_INTERVAL", 2*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("MYDNSJP_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for MyDNS.jp.
|
||||
// Credentials must be passed in the environment variables: MYDNSJP_MASTER_ID and MYDNSJP_PASSWORD.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("MYDNSJP_MASTER_ID", "MYDNSJP_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.MasterID = values["MYDNSJP_MASTER_ID"]
|
||||
config.Password = values["MYDNSJP_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for MyDNS.jp.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("mydnsjp: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.MasterID == "" || config.Password == "" {
|
||||
return nil, errors.New("mydnsjp: some credentials information are missing")
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
err := d.doRequest(domain, value, "REGIST")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
err := d.doRequest(domain, value, "DELETE")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
225
vendor/github.com/go-acme/lego/providers/dns/namecheap/client.go
generated
vendored
Normal file
225
vendor/github.com/go-acme/lego/providers/dns/namecheap/client.go
generated
vendored
Normal file
|
@ -0,0 +1,225 @@
|
|||
package namecheap
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Record describes a DNS record returned by the Namecheap DNS gethosts API.
|
||||
// Namecheap uses the term "host" to refer to all DNS records that include
|
||||
// a host field (A, AAAA, CNAME, NS, TXT, URL).
|
||||
type Record struct {
|
||||
Type string `xml:",attr"`
|
||||
Name string `xml:",attr"`
|
||||
Address string `xml:",attr"`
|
||||
MXPref string `xml:",attr"`
|
||||
TTL string `xml:",attr"`
|
||||
}
|
||||
|
||||
// apiError describes an error record in a namecheap API response.
|
||||
type apiError struct {
|
||||
Number int `xml:",attr"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type setHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Result struct {
|
||||
IsSuccess string `xml:",attr"`
|
||||
} `xml:"CommandResponse>DomainDNSSetHostsResult"`
|
||||
}
|
||||
|
||||
type getHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Hosts []Record `xml:"CommandResponse>DomainDNSGetHostsResult>host"`
|
||||
}
|
||||
|
||||
type getTldsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Result []struct {
|
||||
Name string `xml:",attr"`
|
||||
} `xml:"CommandResponse>Tlds>Tld"`
|
||||
}
|
||||
|
||||
// getTLDs requests the list of available TLDs.
|
||||
// https://www.namecheap.com/support/api/methods/domains/get-tld-list.aspx
|
||||
func (d *DNSProvider) getTLDs() (map[string]string, error) {
|
||||
request, err := d.newRequestGet("namecheap.domains.getTldList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var gtr getTldsResponse
|
||||
err = d.do(request, >r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(gtr.Errors) > 0 {
|
||||
return nil, fmt.Errorf("%s [%d]", gtr.Errors[0].Description, gtr.Errors[0].Number)
|
||||
}
|
||||
|
||||
tlds := make(map[string]string)
|
||||
for _, t := range gtr.Result {
|
||||
tlds[t.Name] = t.Name
|
||||
}
|
||||
return tlds, nil
|
||||
}
|
||||
|
||||
// getHosts reads the full list of DNS host records.
|
||||
// https://www.namecheap.com/support/api/methods/domains-dns/get-hosts.aspx
|
||||
func (d *DNSProvider) getHosts(sld, tld string) ([]Record, error) {
|
||||
request, err := d.newRequestGet("namecheap.domains.dns.getHosts",
|
||||
addParam("SLD", sld),
|
||||
addParam("TLD", tld),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ghr getHostsResponse
|
||||
err = d.do(request, &ghr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ghr.Errors) > 0 {
|
||||
return nil, fmt.Errorf("%s [%d]", ghr.Errors[0].Description, ghr.Errors[0].Number)
|
||||
}
|
||||
|
||||
return ghr.Hosts, nil
|
||||
}
|
||||
|
||||
// setHosts writes the full list of DNS host records .
|
||||
// https://www.namecheap.com/support/api/methods/domains-dns/set-hosts.aspx
|
||||
func (d *DNSProvider) setHosts(sld, tld string, hosts []Record) error {
|
||||
req, err := d.newRequestPost("namecheap.domains.dns.setHosts",
|
||||
addParam("SLD", sld),
|
||||
addParam("TLD", tld),
|
||||
func(values url.Values) {
|
||||
for i, h := range hosts {
|
||||
ind := fmt.Sprintf("%d", i+1)
|
||||
values.Add("HostName"+ind, h.Name)
|
||||
values.Add("RecordType"+ind, h.Type)
|
||||
values.Add("Address"+ind, h.Address)
|
||||
values.Add("MXPref"+ind, h.MXPref)
|
||||
values.Add("TTL"+ind, h.TTL)
|
||||
}
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var shr setHostsResponse
|
||||
err = d.do(req, &shr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(shr.Errors) > 0 {
|
||||
return fmt.Errorf("%s [%d]", shr.Errors[0].Description, shr.Errors[0].Number)
|
||||
}
|
||||
if shr.Result.IsSuccess != "true" {
|
||||
return fmt.Errorf("setHosts failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) do(req *http.Request, out interface{}) error {
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
var body []byte
|
||||
body, err = readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTP error %d [%s]: %v", resp.StatusCode, http.StatusText(resp.StatusCode), err)
|
||||
}
|
||||
return fmt.Errorf("HTTP error %d [%s]: %s", resp.StatusCode, http.StatusText(resp.StatusCode), string(body))
|
||||
}
|
||||
|
||||
body, err := readBody(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := xml.Unmarshal(body, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequestGet(cmd string, params ...func(url.Values)) (*http.Request, error) {
|
||||
query := d.makeQuery(cmd, params...)
|
||||
|
||||
reqURL, err := url.Parse(d.config.BaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqURL.RawQuery = query.Encode()
|
||||
|
||||
return http.NewRequest(http.MethodGet, reqURL.String(), nil)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequestPost(cmd string, params ...func(url.Values)) (*http.Request, error) {
|
||||
query := d.makeQuery(cmd, params...)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, d.config.BaseURL, strings.NewReader(query.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) makeQuery(cmd string, params ...func(url.Values)) url.Values {
|
||||
queryParams := make(url.Values)
|
||||
queryParams.Set("ApiUser", d.config.APIUser)
|
||||
queryParams.Set("ApiKey", d.config.APIKey)
|
||||
queryParams.Set("UserName", d.config.APIUser)
|
||||
queryParams.Set("Command", cmd)
|
||||
queryParams.Set("ClientIp", d.config.ClientIP)
|
||||
|
||||
for _, param := range params {
|
||||
param(queryParams)
|
||||
}
|
||||
|
||||
return queryParams
|
||||
}
|
||||
|
||||
func addParam(key, value string) func(url.Values) {
|
||||
return func(values url.Values) {
|
||||
values.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func readBody(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawBody, nil
|
||||
}
|
260
vendor/github.com/go-acme/lego/providers/dns/namecheap/namecheap.go
generated
vendored
Normal file
260
vendor/github.com/go-acme/lego/providers/dns/namecheap/namecheap.go
generated
vendored
Normal file
|
@ -0,0 +1,260 @@
|
|||
// Package namecheap implements a DNS provider for solving the DNS-01 challenge using namecheap DNS.
|
||||
package namecheap
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Notes about namecheap's tool API:
|
||||
// 1. Using the API requires registration. Once registered, use your account
|
||||
// name and API key to access the API.
|
||||
// 2. There is no API to add or modify a single DNS record. Instead you must
|
||||
// read the entire list of records, make modifications, and then write the
|
||||
// entire updated list of records. (Yuck.)
|
||||
// 3. Namecheap's DNS updates can be slow to propagate. I've seen them take
|
||||
// as long as an hour.
|
||||
// 4. Namecheap requires you to whitelist the IP address from which you call
|
||||
// its APIs. It also requires all API calls to include the whitelisted IP
|
||||
// address as a form or query string value. This code uses a namecheap
|
||||
// service to query the client's IP address.
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://api.namecheap.com/xml.response"
|
||||
getIPURL = "https://dynamicdns.park-your-domain.com/getip"
|
||||
)
|
||||
|
||||
// A challenge represents all the data needed to specify a dns-01 challenge
|
||||
// to lets-encrypt.
|
||||
type challenge struct {
|
||||
domain string
|
||||
key string
|
||||
keyFqdn string
|
||||
keyValue string
|
||||
tld string
|
||||
sld string
|
||||
host string
|
||||
}
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Debug bool
|
||||
BaseURL string
|
||||
APIUser string
|
||||
APIKey string
|
||||
ClientIP string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
Debug: env.GetOrDefaultBool("NAMECHEAP_DEBUG", false),
|
||||
TTL: env.GetOrDefaultInt("NAMECHEAP_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NAMECHEAP_PROPAGATION_TIMEOUT", 60*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("NAMECHEAP_POLLING_INTERVAL", 15*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NAMECHEAP_HTTP_TIMEOUT", 60*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the ChallengeProviderTimeout interface
|
||||
// that uses Namecheap's tool API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for namecheap.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NAMECHEAP_API_USER and NAMECHEAP_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NAMECHEAP_API_USER", "NAMECHEAP_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = values["NAMECHEAP_API_USER"]
|
||||
config.APIKey = values["NAMECHEAP_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for namecheap.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("namecheap: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIUser == "" || config.APIKey == "" {
|
||||
return nil, fmt.Errorf("namecheap: credentials missing")
|
||||
}
|
||||
|
||||
if len(config.ClientIP) == 0 {
|
||||
clientIP, err := getClientIP(config.HTTPClient, config.Debug)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
config.ClientIP = clientIP
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
// Namecheap can sometimes take a long time to complete an update, so wait up to 60 minutes for the update to propagate.
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// Present installs a TXT record for the DNS challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
tlds, err := d.getTLDs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
ch, err := newChallenge(domain, keyAuth, tlds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.getHosts(ch.sld, ch.tld)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
record := Record{
|
||||
Name: ch.key,
|
||||
Type: "TXT",
|
||||
Address: ch.keyValue,
|
||||
MXPref: "10",
|
||||
TTL: strconv.Itoa(d.config.TTL),
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
|
||||
if d.config.Debug {
|
||||
for _, h := range records {
|
||||
log.Printf("%-5.5s %-30.30s %-6s %-70.70s", h.Type, h.Name, h.TTL, h.Address)
|
||||
}
|
||||
}
|
||||
|
||||
err = d.setHosts(ch.sld, ch.tld, records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes a TXT record used for a previous DNS challenge.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
tlds, err := d.getTLDs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
ch, err := newChallenge(domain, keyAuth, tlds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
records, err := d.getHosts(ch.sld, ch.tld)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
|
||||
// Find the challenge TXT record and remove it if found.
|
||||
var found bool
|
||||
var newRecords []Record
|
||||
for _, h := range records {
|
||||
if h.Name == ch.key && h.Type == "TXT" {
|
||||
found = true
|
||||
} else {
|
||||
newRecords = append(newRecords, h)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = d.setHosts(ch.sld, ch.tld, newRecords)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namecheap: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getClientIP returns the client's public IP address.
|
||||
// It uses namecheap's IP discovery service to perform the lookup.
|
||||
func getClientIP(client *http.Client, debug bool) (addr string, err error) {
|
||||
resp, err := client.Get(getIPURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
clientIP, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Println("Client IP:", string(clientIP))
|
||||
}
|
||||
return string(clientIP), nil
|
||||
}
|
||||
|
||||
// newChallenge builds a challenge record from a domain name, a challenge
|
||||
// authentication key, and a map of available TLDs.
|
||||
func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, error) {
|
||||
domain = dns01.UnFqdn(domain)
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// Find the longest matching TLD.
|
||||
longest := -1
|
||||
for i := len(parts); i > 0; i-- {
|
||||
t := strings.Join(parts[i-1:], ".")
|
||||
if _, found := tlds[t]; found {
|
||||
longest = i - 1
|
||||
}
|
||||
}
|
||||
if longest < 1 {
|
||||
return nil, fmt.Errorf("invalid domain name %q", domain)
|
||||
}
|
||||
|
||||
tld := strings.Join(parts[longest:], ".")
|
||||
sld := parts[longest-1]
|
||||
|
||||
var host string
|
||||
if longest >= 1 {
|
||||
host = strings.Join(parts[:longest-1], ".")
|
||||
}
|
||||
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
return &challenge{
|
||||
domain: domain,
|
||||
key: "_acme-challenge." + host,
|
||||
keyFqdn: fqdn,
|
||||
keyValue: value,
|
||||
tld: tld,
|
||||
sld: sld,
|
||||
host: host,
|
||||
}, nil
|
||||
}
|
170
vendor/github.com/go-acme/lego/providers/dns/namedotcom/namedotcom.go
generated
vendored
Normal file
170
vendor/github.com/go-acme/lego/providers/dns/namedotcom/namedotcom.go
generated
vendored
Normal file
|
@ -0,0 +1,170 @@
|
|||
// Package namedotcom implements a DNS provider for solving the DNS-01 challenge using Name.com's DNS service.
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/namedotcom/go/namecom"
|
||||
)
|
||||
|
||||
// according to https://www.name.com/api-docs/DNS#CreateRecord
|
||||
const minTTL = 300
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Username string
|
||||
APIToken string
|
||||
Server string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NAMECOM_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NAMECOM_PROPAGATION_TIMEOUT", 15*time.Minute),
|
||||
PollingInterval: env.GetOrDefaultSecond("NAMECOM_POLLING_INTERVAL", 20*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NAMECOM_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *namecom.NameCom
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for namedotcom.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NAMECOM_USERNAME and NAMECOM_API_TOKEN
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NAMECOM_USERNAME", "NAMECOM_API_TOKEN")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namedotcom: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Username = values["NAMECOM_USERNAME"]
|
||||
config.APIToken = values["NAMECOM_API_TOKEN"]
|
||||
config.Server = env.GetOrFile("NAMECOM_SERVER")
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for namedotcom.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("namedotcom: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.Username == "" {
|
||||
return nil, fmt.Errorf("namedotcom: username is required")
|
||||
}
|
||||
|
||||
if config.APIToken == "" {
|
||||
return nil, fmt.Errorf("namedotcom: API token is required")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("namedotcom: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
client := namecom.New(config.Username, config.APIToken)
|
||||
client.Client = config.HTTPClient
|
||||
|
||||
if config.Server != "" {
|
||||
client.Server = config.Server
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
request := &namecom.Record{
|
||||
DomainName: domain,
|
||||
Host: d.extractRecordName(fqdn, domain),
|
||||
Type: "TXT",
|
||||
TTL: uint32(d.config.TTL),
|
||||
Answer: value,
|
||||
}
|
||||
|
||||
_, err := d.client.CreateRecord(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namedotcom: API call failed: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.getRecords(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namedotcom: %v", err)
|
||||
}
|
||||
|
||||
for _, rec := range records {
|
||||
if rec.Fqdn == fqdn && rec.Type == "TXT" {
|
||||
request := &namecom.DeleteRecordRequest{
|
||||
DomainName: domain,
|
||||
ID: rec.ID,
|
||||
}
|
||||
_, err := d.client.DeleteRecord(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("namedotcom: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) {
|
||||
request := &namecom.ListRecordsRequest{
|
||||
DomainName: domain,
|
||||
Page: 1,
|
||||
}
|
||||
|
||||
var records []*namecom.Record
|
||||
for request.Page > 0 {
|
||||
response, err := d.client.ListRecords(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records = append(records, response.Records...)
|
||||
request.Page = response.NextPage
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
327
vendor/github.com/go-acme/lego/providers/dns/netcup/internal/client.go
generated
vendored
Normal file
327
vendor/github.com/go-acme/lego/providers/dns/netcup/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// defaultBaseURL for reaching the jSON-based API-Endpoint of netcup
|
||||
const defaultBaseURL = "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON"
|
||||
|
||||
// success response status
|
||||
const success = "success"
|
||||
|
||||
// Request wrapper as specified in netcup wiki
|
||||
// needed for every request to netcup API around *Msg
|
||||
// https://www.netcup-wiki.de/wiki/CCP_API#Anmerkungen_zu_JSON-Requests
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Param interface{} `json:"param"`
|
||||
}
|
||||
|
||||
// LoginRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#login
|
||||
type LoginRequest struct {
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APIPassword string `json:"apipassword"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// LogoutRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#logout
|
||||
type LogoutRequest struct {
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDNSRecordsRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#updateDnsRecords
|
||||
type UpdateDNSRecordsRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
DNSRecordSet DNSRecordSet `json:"dnsrecordset"`
|
||||
}
|
||||
|
||||
// DNSRecordSet as specified in netcup WSDL
|
||||
// needed in UpdateDNSRecordsRequest
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecordset
|
||||
type DNSRecordSet struct {
|
||||
DNSRecords []DNSRecord `json:"dnsrecords"`
|
||||
}
|
||||
|
||||
// InfoDNSRecordsRequest as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#infoDnsRecords
|
||||
type InfoDNSRecordsRequest struct {
|
||||
DomainName string `json:"domainname"`
|
||||
CustomerNumber string `json:"customernumber"`
|
||||
APIKey string `json:"apikey"`
|
||||
APISessionID string `json:"apisessionid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
}
|
||||
|
||||
// DNSRecord as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Dnsrecord
|
||||
type DNSRecord struct {
|
||||
ID int `json:"id,string,omitempty"`
|
||||
Hostname string `json:"hostname"`
|
||||
RecordType string `json:"type"`
|
||||
Priority string `json:"priority,omitempty"`
|
||||
Destination string `json:"destination"`
|
||||
DeleteRecord bool `json:"deleterecord,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
// ResponseMsg as specified in netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php#Responsemessage
|
||||
type ResponseMsg struct {
|
||||
ServerRequestID string `json:"serverrequestid"`
|
||||
ClientRequestID string `json:"clientrequestid,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statuscode"`
|
||||
ShortMessage string `json:"shortmessage"`
|
||||
LongMessage string `json:"longmessage"`
|
||||
ResponseData json.RawMessage `json:"responsedata,omitempty"`
|
||||
}
|
||||
|
||||
func (r *ResponseMsg) Error() string {
|
||||
return fmt.Sprintf("an error occurred during the action %s: [Status=%s, StatusCode=%d, ShortMessage=%s, LongMessage=%s]",
|
||||
r.Action, r.Status, r.StatusCode, r.ShortMessage, r.LongMessage)
|
||||
}
|
||||
|
||||
// LoginResponse response to login action.
|
||||
type LoginResponse struct {
|
||||
APISessionID string `json:"apisessionid"`
|
||||
}
|
||||
|
||||
// InfoDNSRecordsResponse response to infoDnsRecords action.
|
||||
type InfoDNSRecordsResponse struct {
|
||||
APISessionID string `json:"apisessionid"`
|
||||
DNSRecords []DNSRecord `json:"dnsrecords,omitempty"`
|
||||
}
|
||||
|
||||
// Client netcup DNS client
|
||||
type Client struct {
|
||||
customerNumber string
|
||||
apiKey string
|
||||
apiPassword string
|
||||
HTTPClient *http.Client
|
||||
BaseURL string
|
||||
}
|
||||
|
||||
// NewClient creates a netcup DNS client
|
||||
func NewClient(customerNumber string, apiKey string, apiPassword string) (*Client, error) {
|
||||
if customerNumber == "" || apiKey == "" || apiPassword == "" {
|
||||
return nil, fmt.Errorf("credentials missing")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
customerNumber: customerNumber,
|
||||
apiKey: apiKey,
|
||||
apiPassword: apiPassword,
|
||||
BaseURL: defaultBaseURL,
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Login performs the login as specified by the netcup WSDL
|
||||
// returns sessionID needed to perform remaining actions
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) Login() (string, error) {
|
||||
payload := &Request{
|
||||
Action: "login",
|
||||
Param: &LoginRequest{
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APIPassword: c.apiPassword,
|
||||
ClientRequestID: "",
|
||||
},
|
||||
}
|
||||
|
||||
var responseData LoginResponse
|
||||
err := c.doRequest(payload, &responseData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("loging error: %v", err)
|
||||
}
|
||||
|
||||
return responseData.APISessionID, nil
|
||||
}
|
||||
|
||||
// Logout performs the logout with the supplied sessionID as specified by the netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) Logout(sessionID string) error {
|
||||
payload := &Request{
|
||||
Action: "logout",
|
||||
Param: &LogoutRequest{
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: sessionID,
|
||||
ClientRequestID: "",
|
||||
},
|
||||
}
|
||||
|
||||
err := c.doRequest(payload, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("logout error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDNSRecord performs an update of the DNSRecords as specified by the netcup WSDL
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) UpdateDNSRecord(sessionID, domainName string, records []DNSRecord) error {
|
||||
payload := &Request{
|
||||
Action: "updateDnsRecords",
|
||||
Param: UpdateDNSRecordsRequest{
|
||||
DomainName: domainName,
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: sessionID,
|
||||
ClientRequestID: "",
|
||||
DNSRecordSet: DNSRecordSet{DNSRecords: records},
|
||||
},
|
||||
}
|
||||
|
||||
err := c.doRequest(payload, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when sending the request: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDNSRecords retrieves all dns records of an DNS-Zone as specified by the netcup WSDL
|
||||
// returns an array of DNSRecords
|
||||
// https://ccp.netcup.net/run/webservice/servers/endpoint.php
|
||||
func (c *Client) GetDNSRecords(hostname, apiSessionID string) ([]DNSRecord, error) {
|
||||
payload := &Request{
|
||||
Action: "infoDnsRecords",
|
||||
Param: InfoDNSRecordsRequest{
|
||||
DomainName: hostname,
|
||||
CustomerNumber: c.customerNumber,
|
||||
APIKey: c.apiKey,
|
||||
APISessionID: apiSessionID,
|
||||
ClientRequestID: "",
|
||||
},
|
||||
}
|
||||
|
||||
var responseData InfoDNSRecordsResponse
|
||||
err := c.doRequest(payload, &responseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when sending the request: %v", err)
|
||||
}
|
||||
|
||||
return responseData.DNSRecords, nil
|
||||
|
||||
}
|
||||
|
||||
// doRequest marshals given body to JSON, send the request to netcup API
|
||||
// and returns body of response
|
||||
func (c *Client) doRequest(payload interface{}, responseData interface{}) error {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, c.BaseURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Close = true
|
||||
req.Header.Set("content-type", "application/json")
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checkResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
respMsg, err := decodeResponseMsg(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if respMsg.Status != success {
|
||||
return respMsg
|
||||
}
|
||||
|
||||
if responseData != nil {
|
||||
err = json.Unmarshal(respMsg.ResponseData, responseData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: unmarshaling %T error: %v: %s",
|
||||
respMsg, responseData, err, string(respMsg.ResponseData))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode > 299 {
|
||||
if resp.Body == nil {
|
||||
return fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("status code=%d: %s", resp.StatusCode, string(raw))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeResponseMsg(resp *http.Response) (*ResponseMsg, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil, status code=%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read body: status code=%d, error=%v", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
var respMsg ResponseMsg
|
||||
err = json.Unmarshal(raw, &respMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling %T error [status code=%d]: %v: %s", respMsg, resp.StatusCode, err, string(raw))
|
||||
}
|
||||
|
||||
return &respMsg, nil
|
||||
}
|
||||
|
||||
// GetDNSRecordIdx searches a given array of DNSRecords for a given DNSRecord
|
||||
// equivalence is determined by Destination and RecortType attributes
|
||||
// returns index of given DNSRecord in given array of DNSRecords
|
||||
func GetDNSRecordIdx(records []DNSRecord, record DNSRecord) (int, error) {
|
||||
for index, element := range records {
|
||||
if record.Destination == element.Destination && record.RecordType == element.RecordType {
|
||||
return index, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("no DNS Record found")
|
||||
}
|
182
vendor/github.com/go-acme/lego/providers/dns/netcup/netcup.go
generated
vendored
Normal file
182
vendor/github.com/go-acme/lego/providers/dns/netcup/netcup.go
generated
vendored
Normal file
|
@ -0,0 +1,182 @@
|
|||
// Package netcup implements a DNS Provider for solving the DNS-01 challenge using the netcup DNS API.
|
||||
package netcup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/providers/dns/netcup/internal"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
Key string
|
||||
Password string
|
||||
Customer string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NETCUP_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NETCUP_PROPAGATION_TIMEOUT", 120*time.Second),
|
||||
PollingInterval: env.GetOrDefaultSecond("NETCUP_POLLING_INTERVAL", 5*time.Second),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NETCUP_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *internal.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for netcup.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NETCUP_CUSTOMER_NUMBER, NETCUP_API_KEY, NETCUP_API_PASSWORD
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NETCUP_CUSTOMER_NUMBER", "NETCUP_API_KEY", "NETCUP_API_PASSWORD")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.Customer = values["NETCUP_CUSTOMER_NUMBER"]
|
||||
config.Key = values["NETCUP_API_KEY"]
|
||||
config.Password = values["NETCUP_API_PASSWORD"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for netcup.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("netcup: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.Customer, config.Key, config.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
|
||||
}
|
||||
|
||||
sessionID, err := d.client.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
log.Print("netcup: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
record := internal.DNSRecord{
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Destination: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
zone = dns01.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
// skip no existing records
|
||||
log.Infof("no existing records, error ignored: %v", err)
|
||||
}
|
||||
|
||||
records = append(records, record)
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to add TXT-Record: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
|
||||
}
|
||||
|
||||
sessionID, err := d.client.Login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = d.client.Logout(sessionID)
|
||||
if err != nil {
|
||||
log.Print("netcup: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
|
||||
zone = dns01.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
record := internal.DNSRecord{
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Destination: value,
|
||||
}
|
||||
|
||||
idx, err := internal.GetDNSRecordIdx(records, record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
records[idx].DeleteRecord = true
|
||||
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, []internal.DNSRecord{records[idx]})
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
234
vendor/github.com/go-acme/lego/providers/dns/nifcloud/internal/client.go
generated
vendored
Normal file
234
vendor/github.com/go-acme/lego/providers/dns/nifcloud/internal/client.go
generated
vendored
Normal file
|
@ -0,0 +1,234 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://dns.api.nifcloud.com"
|
||||
apiVersion = "2012-12-12N2013-12-16"
|
||||
// XMLNs XML NS of Route53
|
||||
XMLNs = "https://route53.amazonaws.com/doc/2012-12-12/"
|
||||
)
|
||||
|
||||
// ChangeResourceRecordSetsRequest is a complex type that contains change information for the resource record set.
|
||||
type ChangeResourceRecordSetsRequest struct {
|
||||
XMLNs string `xml:"xmlns,attr"`
|
||||
ChangeBatch ChangeBatch `xml:"ChangeBatch"`
|
||||
}
|
||||
|
||||
// ChangeResourceRecordSetsResponse is a complex type containing the response for the request.
|
||||
type ChangeResourceRecordSetsResponse struct {
|
||||
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
||||
}
|
||||
|
||||
// GetChangeResponse is a complex type that contains the ChangeInfo element.
|
||||
type GetChangeResponse struct {
|
||||
ChangeInfo ChangeInfo `xml:"ChangeInfo"`
|
||||
}
|
||||
|
||||
// ErrorResponse is the information for any errors.
|
||||
type ErrorResponse struct {
|
||||
Error struct {
|
||||
Type string `xml:"Type"`
|
||||
Message string `xml:"Message"`
|
||||
Code string `xml:"Code"`
|
||||
} `xml:"Error"`
|
||||
RequestID string `xml:"RequestId"`
|
||||
}
|
||||
|
||||
// ChangeBatch is the information for a change request.
|
||||
type ChangeBatch struct {
|
||||
Changes Changes `xml:"Changes"`
|
||||
Comment string `xml:"Comment"`
|
||||
}
|
||||
|
||||
// Changes is array of Change.
|
||||
type Changes struct {
|
||||
Change []Change `xml:"Change"`
|
||||
}
|
||||
|
||||
// Change is the information for each resource record set that you want to change.
|
||||
type Change struct {
|
||||
Action string `xml:"Action"`
|
||||
ResourceRecordSet ResourceRecordSet `xml:"ResourceRecordSet"`
|
||||
}
|
||||
|
||||
// ResourceRecordSet is the information about the resource record set to create or delete.
|
||||
type ResourceRecordSet struct {
|
||||
Name string `xml:"Name"`
|
||||
Type string `xml:"Type"`
|
||||
TTL int `xml:"TTL"`
|
||||
ResourceRecords ResourceRecords `xml:"ResourceRecords"`
|
||||
}
|
||||
|
||||
// ResourceRecords is array of ResourceRecord.
|
||||
type ResourceRecords struct {
|
||||
ResourceRecord []ResourceRecord `xml:"ResourceRecord"`
|
||||
}
|
||||
|
||||
// ResourceRecord is the information specific to the resource record.
|
||||
type ResourceRecord struct {
|
||||
Value string `xml:"Value"`
|
||||
}
|
||||
|
||||
// ChangeInfo is A complex type that describes change information about changes made to your hosted zone.
|
||||
type ChangeInfo struct {
|
||||
ID string `xml:"Id"`
|
||||
Status string `xml:"Status"`
|
||||
SubmittedAt string `xml:"SubmittedAt"`
|
||||
}
|
||||
|
||||
// NewClient Creates a new client of NIFCLOUD DNS
|
||||
func NewClient(accessKey string, secretKey string) (*Client, error) {
|
||||
if len(accessKey) == 0 || len(secretKey) == 0 {
|
||||
return nil, errors.New("credentials missing")
|
||||
}
|
||||
|
||||
return &Client{
|
||||
accessKey: accessKey,
|
||||
secretKey: secretKey,
|
||||
BaseURL: defaultBaseURL,
|
||||
HTTPClient: &http.Client{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Client client of NIFCLOUD DNS
|
||||
type Client struct {
|
||||
accessKey string
|
||||
secretKey string
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// ChangeResourceRecordSets Call ChangeResourceRecordSets API and return response.
|
||||
func (c *Client) ChangeResourceRecordSets(hostedZoneID string, input ChangeResourceRecordSetsRequest) (*ChangeResourceRecordSetsResponse, error) {
|
||||
requestURL := fmt.Sprintf("%s/%s/hostedzone/%s/rrset", c.BaseURL, apiVersion, hostedZoneID)
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
body.Write([]byte(xml.Header))
|
||||
err := xml.NewEncoder(body).Encode(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, requestURL, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
|
||||
|
||||
err = c.sign(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred during the creation of the signature: %v", err)
|
||||
}
|
||||
|
||||
res, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Body == nil {
|
||||
return nil, errors.New("the response body is nil")
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errResp := &ErrorResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(errResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the error body to XML: %v", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("an error occurred: %s", errResp.Error.Message)
|
||||
}
|
||||
|
||||
output := &ChangeResourceRecordSetsResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the response body to XML: %v", err)
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
// GetChange Call GetChange API and return response.
|
||||
func (c *Client) GetChange(statusID string) (*GetChangeResponse, error) {
|
||||
requestURL := fmt.Sprintf("%s/%s/change/%s", c.BaseURL, apiVersion, statusID)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.sign(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred during the creation of the signature: %v", err)
|
||||
}
|
||||
|
||||
res, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Body == nil {
|
||||
return nil, errors.New("the response body is nil")
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
errResp := &ErrorResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(errResp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the error body to XML: %v", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("an error occurred: %s", errResp.Error.Message)
|
||||
}
|
||||
|
||||
output := &GetChangeResponse{}
|
||||
err = xml.NewDecoder(res.Body).Decode(output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("an error occurred while unmarshaling the response body to XML: %v", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (c *Client) sign(req *http.Request) error {
|
||||
if req.Header.Get("Date") == "" {
|
||||
location, err := time.LoadLocation("GMT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Date", time.Now().In(location).Format(time.RFC1123))
|
||||
}
|
||||
|
||||
if req.URL.Path == "" {
|
||||
req.URL.Path += "/"
|
||||
}
|
||||
|
||||
mac := hmac.New(sha1.New, []byte(c.secretKey))
|
||||
_, err := mac.Write([]byte(req.Header.Get("Date")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashed := mac.Sum(nil)
|
||||
signature := base64.StdEncoding.EncodeToString(hashed)
|
||||
|
||||
auth := fmt.Sprintf("NIFTY3-HTTPS NiftyAccessKeyId=%s,Algorithm=HmacSHA1,Signature=%s", c.accessKey, signature)
|
||||
req.Header.Set("X-Nifty-Authorization", auth)
|
||||
|
||||
return nil
|
||||
}
|
156
vendor/github.com/go-acme/lego/providers/dns/nifcloud/nifcloud.go
generated
vendored
Normal file
156
vendor/github.com/go-acme/lego/providers/dns/nifcloud/nifcloud.go
generated
vendored
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Package nifcloud implements a DNS provider for solving the DNS-01 challenge using NIFCLOUD DNS.
|
||||
package nifcloud
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/providers/dns/nifcloud/internal"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/go-acme/lego/platform/wait"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NIFCLOUD_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NIFCLOUD_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NIFCLOUD_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NIFCLOUD_HTTP_TIMEOUT", 30*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *internal.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for the NIFCLOUD DNS service.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// NIFCLOUD_ACCESS_KEY_ID and NIFCLOUD_SECRET_ACCESS_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NIFCLOUD_ACCESS_KEY_ID", "NIFCLOUD_SECRET_ACCESS_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = env.GetOrFile("NIFCLOUD_DNS_ENDPOINT")
|
||||
config.AccessKey = values["NIFCLOUD_ACCESS_KEY_ID"]
|
||||
config.SecretKey = values["NIFCLOUD_SECRET_ACCESS_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for NIFCLOUD.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("nifcloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(config.AccessKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
|
||||
if config.HTTPClient != nil {
|
||||
client.HTTPClient = config.HTTPClient
|
||||
}
|
||||
|
||||
if len(config.BaseURL) > 0 {
|
||||
client.BaseURL = config.BaseURL
|
||||
}
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("CREATE", fqdn, value, domain, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("DELETE", fqdn, value, domain, d.config.TTL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) changeRecord(action, fqdn, value, domain string, ttl int) error {
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
|
||||
reqParams := internal.ChangeResourceRecordSetsRequest{
|
||||
XMLNs: internal.XMLNs,
|
||||
ChangeBatch: internal.ChangeBatch{
|
||||
Comment: "Managed by Lego",
|
||||
Changes: internal.Changes{
|
||||
Change: []internal.Change{
|
||||
{
|
||||
Action: action,
|
||||
ResourceRecordSet: internal.ResourceRecordSet{
|
||||
Name: name,
|
||||
Type: "TXT",
|
||||
TTL: ttl,
|
||||
ResourceRecords: internal.ResourceRecords{
|
||||
ResourceRecord: []internal.ResourceRecord{
|
||||
{
|
||||
Value: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := d.client.ChangeResourceRecordSets(domain, reqParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to change NIFCLOUD record set: %v", err)
|
||||
}
|
||||
|
||||
statusID := resp.ChangeInfo.ID
|
||||
|
||||
return wait.For("nifcloud", 120*time.Second, 4*time.Second, func() (bool, error) {
|
||||
resp, err := d.client.GetChange(statusID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to query NIFCLOUD DNS change status: %v", err)
|
||||
}
|
||||
return resp.ChangeInfo.Status == "INSYNC", nil
|
||||
})
|
||||
}
|
162
vendor/github.com/go-acme/lego/providers/dns/ns1/ns1.go
generated
vendored
Normal file
162
vendor/github.com/go-acme/lego/providers/dns/ns1/ns1.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
// Package ns1 implements a DNS provider for solving the DNS-01 challenge using NS1 DNS.
|
||||
package ns1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/log"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NS1_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NS1_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NS1_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("NS1_HTTP_TIMEOUT", 10*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *rest.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for NS1.
|
||||
// Credentials must be passed in the environment variables: NS1_API_KEY.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("NS1_API_KEY")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = values["NS1_API_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for NS1.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("ns1: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, fmt.Errorf("ns1: credentials missing")
|
||||
}
|
||||
|
||||
client := rest.NewClient(config.HTTPClient, rest.SetAPIKey(config.APIKey))
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
record, _, err := d.client.Records.Get(zone.Zone, dns01.UnFqdn(fqdn), "TXT")
|
||||
|
||||
// Create a new record
|
||||
if err == rest.ErrRecordMissing || record == nil {
|
||||
log.Infof("Create a new record for [zone: %s, fqdn: %s, domain: %s]", zone.Zone, fqdn)
|
||||
|
||||
record = dns.NewRecord(zone.Zone, dns01.UnFqdn(fqdn), "TXT")
|
||||
record.TTL = d.config.TTL
|
||||
record.Answers = []*dns.Answer{{Rdata: []string{value}}}
|
||||
|
||||
_, err = d.client.Records.Create(record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to create record [zone: %q, fqdn: %q]: %v", zone.Zone, fqdn, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to get the existing record: %v", err)
|
||||
}
|
||||
|
||||
// Update the existing records
|
||||
record.Answers = append(record.Answers, &dns.Answer{Rdata: []string{value}})
|
||||
|
||||
log.Infof("Update an existing record for [zone: %s, fqdn: %s, domain: %s]", zone.Zone, fqdn, domain)
|
||||
|
||||
_, err = d.client.Records.Update(record)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to update record [zone: %q, fqdn: %q]: %v", zone.Zone, fqdn, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
_, err = d.client.Records.Delete(zone.Zone, name, "TXT")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: failed to delete record [zone: %q, domain: %q]: %v", zone.Zone, name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(fqdn string) (*dns.Zone, error) {
|
||||
authZone, err := getAuthZone(fqdn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to extract auth zone from fqdn %q: %v", fqdn, err)
|
||||
}
|
||||
|
||||
zone, _, err := d.client.Zones.Get(authZone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get zone [authZone: %q, fqdn: %q]: %v", authZone, fqdn, err)
|
||||
}
|
||||
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
func getAuthZone(fqdn string) (string, error) {
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(authZone, "."), nil
|
||||
}
|
101
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/configprovider.go
generated
vendored
Normal file
101
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/configprovider.go
generated
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
package oraclecloud
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
)
|
||||
|
||||
const (
|
||||
ociPrivkey = "OCI_PRIVKEY"
|
||||
ociPrivkeyPass = "OCI_PRIVKEY_PASS"
|
||||
ociTenancyOCID = "OCI_TENANCY_OCID"
|
||||
ociUserOCID = "OCI_USER_OCID"
|
||||
ociPubkeyFingerprint = "OCI_PUBKEY_FINGERPRINT"
|
||||
ociRegion = "OCI_REGION"
|
||||
)
|
||||
|
||||
type configProvider struct {
|
||||
values map[string]string
|
||||
privateKeyPassphrase string
|
||||
}
|
||||
|
||||
func newConfigProvider(values map[string]string) *configProvider {
|
||||
return &configProvider{
|
||||
values: values,
|
||||
privateKeyPassphrase: env.GetOrFile(ociPrivkeyPass),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *configProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
||||
privateKey, err := getPrivateKey(ociPrivkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return common.PrivateKeyFromBytes(privateKey, common.String(p.privateKeyPassphrase))
|
||||
}
|
||||
|
||||
func (p *configProvider) KeyID() (string, error) {
|
||||
tenancy, err := p.TenancyOCID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
user, err := p.UserOCID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fingerprint, err := p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
|
||||
}
|
||||
|
||||
func (p *configProvider) TenancyOCID() (value string, err error) {
|
||||
return p.values[ociTenancyOCID], nil
|
||||
}
|
||||
|
||||
func (p *configProvider) UserOCID() (string, error) {
|
||||
return p.values[ociUserOCID], nil
|
||||
}
|
||||
|
||||
func (p *configProvider) KeyFingerprint() (string, error) {
|
||||
return p.values[ociPubkeyFingerprint], nil
|
||||
}
|
||||
|
||||
func (p *configProvider) Region() (string, error) {
|
||||
return p.values[ociRegion], nil
|
||||
}
|
||||
|
||||
func getPrivateKey(envVar string) ([]byte, error) {
|
||||
envVarValue := os.Getenv(envVar)
|
||||
if envVarValue != "" {
|
||||
bytes, err := base64.StdEncoding.DecodeString(envVarValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read base64 value %s (defined by env var %s): %s", envVarValue, envVar, err)
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
fileVar := envVar + "_FILE"
|
||||
fileVarValue := os.Getenv(fileVar)
|
||||
if fileVarValue == "" {
|
||||
return nil, fmt.Errorf("no value provided for: %s or %s", envVar, fileVar)
|
||||
}
|
||||
|
||||
fileContents, err := ioutil.ReadFile(fileVarValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
||||
}
|
||||
|
||||
return fileContents, nil
|
||||
}
|
175
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/oraclecloud.go
generated
vendored
Normal file
175
vendor/github.com/go-acme/lego/providers/dns/oraclecloud/oraclecloud.go
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
package oraclecloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
"github.com/oracle/oci-go-sdk/dns"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
CompartmentID string
|
||||
OCIConfigProvider common.ConfigurationProvider
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("OCI_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("OCI_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("OCI_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("OCI_HTTP_TIMEOUT", 60*time.Second),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
client *dns.DnsClient
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for OracleCloud.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(ociPrivkey, ociTenancyOCID, ociUserOCID, ociPubkeyFingerprint, ociRegion, "OCI_COMPARTMENT_OCID")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.CompartmentID = values["OCI_COMPARTMENT_OCID"]
|
||||
config.OCIConfigProvider = newConfigProvider(values)
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for OracleCloud.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("oraclecloud: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.CompartmentID == "" {
|
||||
return nil, errors.New("oraclecloud: CompartmentID is missing")
|
||||
}
|
||||
|
||||
if config.OCIConfigProvider == nil {
|
||||
return nil, errors.New("oraclecloud: OCIConfigProvider is missing")
|
||||
}
|
||||
|
||||
client, err := dns.NewDnsClientWithConfigurationProvider(config.OCIConfigProvider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
if config.HTTPClient != nil {
|
||||
client.HTTPClient = config.HTTPClient
|
||||
}
|
||||
|
||||
return &DNSProvider{client: &client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// generate request to dns.PatchDomainRecordsRequest
|
||||
recordOperation := dns.RecordOperation{
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
Rdata: common.String(value),
|
||||
Rtype: common.String("TXT"),
|
||||
Ttl: common.Int(d.config.TTL),
|
||||
IsProtected: common.Bool(false),
|
||||
}
|
||||
|
||||
request := dns.PatchDomainRecordsRequest{
|
||||
CompartmentId: common.String(d.config.CompartmentID),
|
||||
ZoneNameOrId: common.String(domain),
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
PatchDomainRecordsDetails: dns.PatchDomainRecordsDetails{
|
||||
Items: []dns.RecordOperation{recordOperation},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := d.client.PatchDomainRecords(context.Background(), request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// search to TXT record's hash to delete
|
||||
getRequest := dns.GetDomainRecordsRequest{
|
||||
ZoneNameOrId: common.String(domain),
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
CompartmentId: common.String(d.config.CompartmentID),
|
||||
Rtype: common.String("TXT"),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
domainRecords, err := d.client.GetDomainRecords(ctx, getRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
if *domainRecords.OpcTotalItems == 0 {
|
||||
return fmt.Errorf("oraclecloud: no record to CleanUp")
|
||||
}
|
||||
|
||||
var deleteHash *string
|
||||
for _, record := range domainRecords.RecordCollection.Items {
|
||||
if record.Rdata != nil && *record.Rdata == `"`+value+`"` {
|
||||
deleteHash = record.RecordHash
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if deleteHash == nil {
|
||||
return fmt.Errorf("oraclecloud: no record to CleanUp")
|
||||
}
|
||||
|
||||
recordOperation := dns.RecordOperation{
|
||||
RecordHash: deleteHash,
|
||||
Operation: dns.RecordOperationOperationRemove,
|
||||
}
|
||||
|
||||
patchRequest := dns.PatchDomainRecordsRequest{
|
||||
ZoneNameOrId: common.String(domain),
|
||||
Domain: common.String(dns01.UnFqdn(fqdn)),
|
||||
PatchDomainRecordsDetails: dns.PatchDomainRecordsDetails{
|
||||
Items: []dns.RecordOperation{recordOperation},
|
||||
},
|
||||
CompartmentId: common.String(d.config.CompartmentID),
|
||||
}
|
||||
|
||||
_, err = d.client.PatchDomainRecords(ctx, patchRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("oraclecloud: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
263
vendor/github.com/go-acme/lego/providers/dns/otc/client.go
generated
vendored
Normal file
263
vendor/github.com/go-acme/lego/providers/dns/otc/client.go
generated
vendored
Normal file
|
@ -0,0 +1,263 @@
|
|||
package otc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type recordset struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
TTL int `json:"ttl"`
|
||||
Records []string `json:"records"`
|
||||
}
|
||||
|
||||
type nameResponse struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type userResponse struct {
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Domain nameResponse `json:"domain"`
|
||||
}
|
||||
|
||||
type passwordResponse struct {
|
||||
User userResponse `json:"user"`
|
||||
}
|
||||
|
||||
type identityResponse struct {
|
||||
Methods []string `json:"methods"`
|
||||
Password passwordResponse `json:"password"`
|
||||
}
|
||||
|
||||
type scopeResponse struct {
|
||||
Project nameResponse `json:"project"`
|
||||
}
|
||||
|
||||
type authResponse struct {
|
||||
Identity identityResponse `json:"identity"`
|
||||
Scope scopeResponse `json:"scope"`
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
Auth authResponse `json:"auth"`
|
||||
}
|
||||
|
||||
type endpointResponse struct {
|
||||
Token token `json:"token"`
|
||||
}
|
||||
|
||||
type token struct {
|
||||
Catalog []catalog `json:"catalog"`
|
||||
}
|
||||
|
||||
type catalog struct {
|
||||
Type string `json:"type"`
|
||||
Endpoints []endpoint `json:"endpoints"`
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type zoneItem struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type zonesResponse struct {
|
||||
Zones []zoneItem `json:"zones"`
|
||||
}
|
||||
|
||||
type recordSet struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type recordSetsResponse struct {
|
||||
RecordSets []recordSet `json:"recordsets"`
|
||||
}
|
||||
|
||||
// Starts a new OTC API Session. Authenticates using userName, password
|
||||
// and receives a token to be used in for subsequent requests.
|
||||
func (d *DNSProvider) login() error {
|
||||
return d.loginRequest()
|
||||
}
|
||||
|
||||
func (d *DNSProvider) loginRequest() error {
|
||||
userResp := userResponse{
|
||||
Name: d.config.UserName,
|
||||
Password: d.config.Password,
|
||||
Domain: nameResponse{
|
||||
Name: d.config.DomainName,
|
||||
},
|
||||
}
|
||||
|
||||
loginResp := loginResponse{
|
||||
Auth: authResponse{
|
||||
Identity: identityResponse{
|
||||
Methods: []string{"password"},
|
||||
Password: passwordResponse{
|
||||
User: userResp,
|
||||
},
|
||||
},
|
||||
Scope: scopeResponse{
|
||||
Project: nameResponse{
|
||||
Name: d.config.ProjectName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := json.Marshal(loginResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, d.config.IdentityEndpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{Timeout: d.config.HTTPClient.Timeout}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("OTC API request failed with HTTP status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
d.token = resp.Header.Get("X-Subject-Token")
|
||||
|
||||
if d.token == "" {
|
||||
return fmt.Errorf("unable to get auth token")
|
||||
}
|
||||
|
||||
var endpointResp endpointResponse
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&endpointResp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var endpoints []endpoint
|
||||
for _, v := range endpointResp.Token.Catalog {
|
||||
if v.Type == "dns" {
|
||||
endpoints = append(endpoints, v.Endpoints...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(endpoints) > 0 {
|
||||
d.baseURL = fmt.Sprintf("%s/v2", endpoints[0].URL)
|
||||
} else {
|
||||
return fmt.Errorf("unable to get dns endpoint")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getZoneID(zone string) (string, error) {
|
||||
resource := fmt.Sprintf("zones?name=%s", zone)
|
||||
resp, err := d.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var zonesRes zonesResponse
|
||||
err = json.NewDecoder(resp).Decode(&zonesRes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(zonesRes.Zones) < 1 {
|
||||
return "", fmt.Errorf("zone %s not found", zone)
|
||||
}
|
||||
|
||||
if len(zonesRes.Zones) > 1 {
|
||||
return "", fmt.Errorf("to many zones found")
|
||||
}
|
||||
|
||||
if zonesRes.Zones[0].ID == "" {
|
||||
return "", fmt.Errorf("id not found")
|
||||
}
|
||||
|
||||
return zonesRes.Zones[0].ID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) getRecordSetID(zoneID string, fqdn string) (string, error) {
|
||||
resource := fmt.Sprintf("zones/%s/recordsets?type=TXT&name=%s", zoneID, fqdn)
|
||||
resp, err := d.sendRequest(http.MethodGet, resource, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var recordSetsRes recordSetsResponse
|
||||
err = json.NewDecoder(resp).Decode(&recordSetsRes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(recordSetsRes.RecordSets) < 1 {
|
||||
return "", fmt.Errorf("record not found")
|
||||
}
|
||||
|
||||
if len(recordSetsRes.RecordSets) > 1 {
|
||||
return "", fmt.Errorf("to many records found")
|
||||
}
|
||||
|
||||
if recordSetsRes.RecordSets[0].ID == "" {
|
||||
return "", fmt.Errorf("id not found")
|
||||
}
|
||||
|
||||
return recordSetsRes.RecordSets[0].ID, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) deleteRecordSet(zoneID, recordID string) error {
|
||||
resource := fmt.Sprintf("zones/%s/recordsets/%s", zoneID, recordID)
|
||||
|
||||
_, err := d.sendRequest(http.MethodDelete, resource, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DNSProvider) sendRequest(method, resource string, payload interface{}) (io.Reader, error) {
|
||||
url := fmt.Sprintf("%s/%s", d.baseURL, resource)
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if len(d.token) > 0 {
|
||||
req.Header.Set("X-Auth-Token", d.token)
|
||||
}
|
||||
|
||||
resp, err := d.config.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("OTC API request %s failed with HTTP status code %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
body1, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(body1), nil
|
||||
}
|
179
vendor/github.com/go-acme/lego/providers/dns/otc/otc.go
generated
vendored
Normal file
179
vendor/github.com/go-acme/lego/providers/dns/otc/otc.go
generated
vendored
Normal file
|
@ -0,0 +1,179 @@
|
|||
// Package otc implements a DNS provider for solving the DNS-01 challenge using Open Telekom Cloud Managed DNS.
|
||||
package otc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/challenge/dns01"
|
||||
"github.com/go-acme/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultIdentityEndpoint = "https://iam.eu-de.otc.t-systems.com:443/v3/auth/tokens"
|
||||
|
||||
// minTTL 300 is otc minimum value for ttl
|
||||
const minTTL = 300
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
IdentityEndpoint string
|
||||
DomainName string
|
||||
ProjectName string
|
||||
UserName string
|
||||
Password string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
IdentityEndpoint: env.GetOrDefaultString("OTC_IDENTITY_ENDPOINT", defaultIdentityEndpoint),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("OTC_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("OTC_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("OTC_TTL", minTTL),
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: env.GetOrDefaultSecond("OTC_HTTP_TIMEOUT", 10*time.Second),
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
|
||||
// Workaround for keep alive bug in otc api
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface that uses
|
||||
// OTC's Managed DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
baseURL string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for OTC DNS.
|
||||
// Credentials must be passed in the environment variables: OTC_USER_NAME,
|
||||
// OTC_DOMAIN_NAME, OTC_PASSWORD OTC_PROJECT_NAME and OTC_IDENTITY_ENDPOINT.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("OTC_DOMAIN_NAME", "OTC_USER_NAME", "OTC_PASSWORD", "OTC_PROJECT_NAME")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.DomainName = values["OTC_DOMAIN_NAME"]
|
||||
config.UserName = values["OTC_USER_NAME"]
|
||||
config.Password = values["OTC_PASSWORD"]
|
||||
config.ProjectName = values["OTC_PROJECT_NAME"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for OTC DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("otc: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if config.DomainName == "" || config.UserName == "" || config.Password == "" || config.ProjectName == "" {
|
||||
return nil, fmt.Errorf("otc: credentials missing")
|
||||
}
|
||||
|
||||
if config.TTL < minTTL {
|
||||
return nil, fmt.Errorf("otc: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
|
||||
}
|
||||
|
||||
if config.IdentityEndpoint == "" {
|
||||
config.IdentityEndpoint = defaultIdentityEndpoint
|
||||
}
|
||||
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: unable to get zone: %s", err)
|
||||
}
|
||||
|
||||
resource := fmt.Sprintf("zones/%s/recordsets", zoneID)
|
||||
|
||||
r1 := &recordset{
|
||||
Name: fqdn,
|
||||
Description: "Added TXT record for ACME dns-01 challenge using lego client",
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Records: []string{fmt.Sprintf("\"%s\"", value)},
|
||||
}
|
||||
|
||||
_, err = d.sendRequest(http.MethodPost, resource, r1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.getZoneID(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
|
||||
recordID, err := d.getRecordSetID(zoneID, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: unable go get record %s for zone %s: %s", fqdn, domain, err)
|
||||
}
|
||||
|
||||
err = d.deleteRecordSet(zoneID, recordID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("otc: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue