Update Lego
This commit is contained in:
parent
fc8c24e987
commit
9b2423aaba
192 changed files with 11105 additions and 8535 deletions
69
vendor/github.com/xenolf/lego/acme/api/account.go
generated
vendored
Normal file
69
vendor/github.com/xenolf/lego/acme/api/account.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/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
|
||||
}
|
151
vendor/github.com/xenolf/lego/acme/api/api.go
generated
vendored
Normal file
151
vendor/github.com/xenolf/lego/acme/api/api.go
generated
vendored
Normal file
|
@ -0,0 +1,151 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/acme/api/internal/nonces"
|
||||
"github.com/xenolf/lego/acme/api/internal/secure"
|
||||
"github.com/xenolf/lego/acme/api/internal/sender"
|
||||
"github.com/xenolf/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}
|
||||
|
||||
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, 0)
|
||||
}
|
||||
|
||||
// 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, 0)
|
||||
}
|
||||
|
||||
func (a *Core) retrievablePost(uri string, content []byte, response interface{}, retry int) (*http.Response, error) {
|
||||
resp, err := a.signedPost(uri, content, response)
|
||||
if err != nil {
|
||||
// during tests, 5 retries allow to support ~50% of bad nonce.
|
||||
if retry >= 5 {
|
||||
log.Infof("too many retry on a nonce error, retry count: %d", retry)
|
||||
return resp, err
|
||||
}
|
||||
switch err.(type) {
|
||||
// Retry once if the nonce was invalidated
|
||||
case *acme.NonceError:
|
||||
log.Infof("nonce error retry: %s", err)
|
||||
resp, err = a.retrievablePost(uri, content, response, retry+1)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
default:
|
||||
return resp, 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/xenolf/lego/acme/api/authorization.go
generated
vendored
Normal file
34
vendor/github.com/xenolf/lego/acme/api/authorization.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/xenolf/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/xenolf/lego/acme/api/certificate.go
generated
vendored
Normal file
99
vendor/github.com/xenolf/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/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/certcrypto"
|
||||
"github.com/xenolf/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/xenolf/lego/acme/api/challenge.go
generated
vendored
Normal file
45
vendor/github.com/xenolf/lego/acme/api/challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/xenolf/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/xenolf/lego/acme/api/internal/nonces/nonce_manager.go
generated
vendored
Normal file
78
vendor/github.com/xenolf/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/xenolf/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/xenolf/lego/acme/api/internal/secure/jws.go
generated
vendored
Normal file
134
vendor/github.com/xenolf/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/xenolf/lego/acme/api/internal/nonces"
|
||||
"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/xenolf/lego/acme/api/internal/sender/sender.go
generated
vendored
Normal file
146
vendor/github.com/xenolf/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/xenolf/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/xenolf/lego/acme/api/internal/sender/useragent.go
generated
vendored
Normal file
14
vendor/github.com/xenolf/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/1.2.1"
|
||||
|
||||
// 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/xenolf/lego/acme/api/order.go
generated
vendored
Normal file
65
vendor/github.com/xenolf/lego/acme/api/order.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/xenolf/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/xenolf/lego/acme/api/service.go
generated
vendored
Normal file
45
vendor/github.com/xenolf/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")
|
||||
}
|
17
vendor/github.com/xenolf/lego/acme/challenges.go
generated
vendored
17
vendor/github.com/xenolf/lego/acme/challenges.go
generated
vendored
|
@ -1,17 +0,0 @@
|
|||
package acme
|
||||
|
||||
// Challenge is a string that identifies a particular type and version of ACME challenge.
|
||||
type Challenge string
|
||||
|
||||
const (
|
||||
// HTTP01 is the "http-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#http
|
||||
// Note: HTTP01ChallengePath returns the URL path to fulfill this challenge
|
||||
HTTP01 = Challenge("http-01")
|
||||
|
||||
// DNS01 is the "dns-01" ACME challenge https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md#dns
|
||||
// Note: DNS01Record returns a DNS record which will fulfill this challenge
|
||||
DNS01 = Challenge("dns-01")
|
||||
|
||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-01
|
||||
TLSALPN01 = Challenge("tls-alpn-01")
|
||||
)
|
957
vendor/github.com/xenolf/lego/acme/client.go
generated
vendored
957
vendor/github.com/xenolf/lego/acme/client.go
generated
vendored
|
@ -1,957 +0,0 @@
|
|||
// Package acme implements the ACME protocol for Let's Encrypt and other conforming providers.
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxBodySize is the maximum size of body that we will read.
|
||||
maxBodySize = 1024 * 1024
|
||||
|
||||
// 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
|
||||
|
||||
statusValid = "valid"
|
||||
statusInvalid = "invalid"
|
||||
)
|
||||
|
||||
// User interface is to be implemented by users of this library.
|
||||
// It is used by the client type to get user specific information.
|
||||
type User interface {
|
||||
GetEmail() string
|
||||
GetRegistration() *RegistrationResource
|
||||
GetPrivateKey() crypto.PrivateKey
|
||||
}
|
||||
|
||||
// Interface for all challenge solvers to implement.
|
||||
type solver interface {
|
||||
Solve(challenge challenge, domain string) 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(challenge challenge, domain string) error
|
||||
}
|
||||
|
||||
// Interface for challenges like dns, where we can solve all the challenges before to delete them.
|
||||
type cleanup interface {
|
||||
CleanUp(challenge challenge, domain string) error
|
||||
}
|
||||
|
||||
type validateFunc func(j *jws, domain, uri string, chlng challenge) error
|
||||
|
||||
// Client is the user-friendy way to ACME
|
||||
type Client struct {
|
||||
directory directory
|
||||
user User
|
||||
jws *jws
|
||||
keyType KeyType
|
||||
solvers map[Challenge]solver
|
||||
}
|
||||
|
||||
// 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 contants) will be generated when requesting a new
|
||||
// certificate if one isn't provided.
|
||||
func NewClient(caDirURL string, user User, keyType KeyType) (*Client, error) {
|
||||
privKey := user.GetPrivateKey()
|
||||
if privKey == nil {
|
||||
return nil, errors.New("private key was nil")
|
||||
}
|
||||
|
||||
var dir directory
|
||||
if _, err := getJSON(caDirURL, &dir); err != nil {
|
||||
return nil, fmt.Errorf("get directory at '%s': %v", caDirURL, err)
|
||||
}
|
||||
|
||||
if dir.NewAccountURL == "" {
|
||||
return nil, errors.New("directory missing new registration URL")
|
||||
}
|
||||
if dir.NewOrderURL == "" {
|
||||
return nil, errors.New("directory missing new order URL")
|
||||
}
|
||||
|
||||
jws := &jws{privKey: privKey, getNonceURL: dir.NewNonceURL}
|
||||
if reg := user.GetRegistration(); reg != nil {
|
||||
jws.kid = reg.URI
|
||||
}
|
||||
|
||||
// REVIEW: best possibility?
|
||||
// Add all available solvers with the right index as per ACME
|
||||
// spec to this map. Otherwise they won`t be found.
|
||||
solvers := map[Challenge]solver{
|
||||
HTTP01: &httpChallenge{jws: jws, validate: validate, provider: &HTTPProviderServer{}},
|
||||
TLSALPN01: &tlsALPNChallenge{jws: jws, validate: validate, provider: &TLSALPNProviderServer{}},
|
||||
}
|
||||
|
||||
return &Client{directory: dir, user: user, jws: jws, keyType: keyType, solvers: solvers}, nil
|
||||
}
|
||||
|
||||
// SetChallengeProvider specifies a custom provider p that can solve the given challenge type.
|
||||
func (c *Client) SetChallengeProvider(challenge Challenge, p ChallengeProvider) error {
|
||||
switch challenge {
|
||||
case HTTP01:
|
||||
c.solvers[challenge] = &httpChallenge{jws: c.jws, validate: validate, provider: p}
|
||||
case DNS01:
|
||||
c.solvers[challenge] = &dnsChallenge{jws: c.jws, validate: validate, provider: p}
|
||||
case TLSALPN01:
|
||||
c.solvers[challenge] = &tlsALPNChallenge{jws: c.jws, validate: validate, provider: p}
|
||||
default:
|
||||
return fmt.Errorf("unknown challenge %v", challenge)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHTTPAddress specifies a custom interface:port to be used for HTTP based challenges.
|
||||
// If this option is not used, the default port 80 and all interfaces will be used.
|
||||
// To only specify a port and no interface use the ":port" notation.
|
||||
//
|
||||
// NOTE: This REPLACES any custom HTTP provider previously set by calling
|
||||
// c.SetChallengeProvider with the default HTTP challenge provider.
|
||||
func (c *Client) SetHTTPAddress(iface string) error {
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chlng, ok := c.solvers[HTTP01]; ok {
|
||||
chlng.(*httpChallenge).provider = NewHTTPProviderServer(host, port)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTLSAddress specifies a custom interface:port to be used for TLS based challenges.
|
||||
// If this option is not used, the default port 443 and all interfaces will be used.
|
||||
// To only specify a port and no interface use the ":port" notation.
|
||||
//
|
||||
// NOTE: This REPLACES any custom TLS-ALPN provider previously set by calling
|
||||
// c.SetChallengeProvider with the default TLS-ALPN challenge provider.
|
||||
func (c *Client) SetTLSAddress(iface string) error {
|
||||
host, port, err := net.SplitHostPort(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if chlng, ok := c.solvers[TLSALPN01]; ok {
|
||||
chlng.(*tlsALPNChallenge).provider = NewTLSALPNProviderServer(host, port)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExcludeChallenges explicitly removes challenges from the pool for solving.
|
||||
func (c *Client) ExcludeChallenges(challenges []Challenge) {
|
||||
// Loop through all challenges and delete the requested one if found.
|
||||
for _, challenge := range challenges {
|
||||
delete(c.solvers, challenge)
|
||||
}
|
||||
}
|
||||
|
||||
// GetToSURL returns the current ToS URL from the Directory
|
||||
func (c *Client) GetToSURL() string {
|
||||
return c.directory.Meta.TermsOfService
|
||||
}
|
||||
|
||||
// GetExternalAccountRequired returns the External Account Binding requirement of the Directory
|
||||
func (c *Client) GetExternalAccountRequired() bool {
|
||||
return c.directory.Meta.ExternalAccountRequired
|
||||
}
|
||||
|
||||
// Register the current account to the ACME server.
|
||||
func (c *Client) Register(tosAgreed bool) (*RegistrationResource, error) {
|
||||
if c == nil || c.user == nil {
|
||||
return nil, errors.New("acme: cannot register a nil client or user")
|
||||
}
|
||||
log.Infof("acme: Registering account for %s", c.user.GetEmail())
|
||||
|
||||
accMsg := accountMessage{}
|
||||
if c.user.GetEmail() != "" {
|
||||
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
||||
} else {
|
||||
accMsg.Contact = []string{}
|
||||
}
|
||||
accMsg.TermsOfServiceAgreed = tosAgreed
|
||||
|
||||
var serverReg accountMessage
|
||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
|
||||
if err != nil {
|
||||
remoteErr, ok := err.(RemoteError)
|
||||
if ok && remoteErr.StatusCode == 409 {
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
reg := &RegistrationResource{
|
||||
URI: hdr.Get("Location"),
|
||||
Body: serverReg,
|
||||
}
|
||||
c.jws.kid = reg.URI
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
// RegisterWithExternalAccountBinding Register the current account to the ACME server.
|
||||
func (c *Client) RegisterWithExternalAccountBinding(tosAgreed bool, kid string, hmacEncoded string) (*RegistrationResource, error) {
|
||||
if c == nil || c.user == nil {
|
||||
return nil, errors.New("acme: cannot register a nil client or user")
|
||||
}
|
||||
log.Infof("acme: Registering account (EAB) for %s", c.user.GetEmail())
|
||||
|
||||
accMsg := accountMessage{}
|
||||
if c.user.GetEmail() != "" {
|
||||
accMsg.Contact = []string{"mailto:" + c.user.GetEmail()}
|
||||
} else {
|
||||
accMsg.Contact = []string{}
|
||||
}
|
||||
accMsg.TermsOfServiceAgreed = tosAgreed
|
||||
|
||||
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: could not decode hmac key: %s", err.Error())
|
||||
}
|
||||
|
||||
eabJWS, err := c.jws.signEABContent(c.directory.NewAccountURL, kid, hmac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: error signing eab content: %s", err.Error())
|
||||
}
|
||||
|
||||
eabPayload := eabJWS.FullSerialize()
|
||||
|
||||
accMsg.ExternalAccountBinding = []byte(eabPayload)
|
||||
|
||||
var serverReg accountMessage
|
||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, accMsg, &serverReg)
|
||||
if err != nil {
|
||||
remoteErr, ok := err.(RemoteError)
|
||||
if ok && remoteErr.StatusCode == 409 {
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
reg := &RegistrationResource{
|
||||
URI: hdr.Get("Location"),
|
||||
Body: serverReg,
|
||||
}
|
||||
c.jws.kid = reg.URI
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
// ResolveAccountByKey will attempt to look up an account using the given account key
|
||||
// and return its registration resource.
|
||||
func (c *Client) ResolveAccountByKey() (*RegistrationResource, error) {
|
||||
log.Infof("acme: Trying to resolve account by key")
|
||||
|
||||
acc := accountMessage{OnlyReturnExisting: true}
|
||||
hdr, err := postJSON(c.jws, c.directory.NewAccountURL, acc, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountLink := hdr.Get("Location")
|
||||
if accountLink == "" {
|
||||
return nil, errors.New("Server did not return the account link")
|
||||
}
|
||||
|
||||
var retAccount accountMessage
|
||||
c.jws.kid = accountLink
|
||||
_, err = postJSON(c.jws, accountLink, accountMessage{}, &retAccount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RegistrationResource{URI: accountLink, Body: retAccount}, nil
|
||||
}
|
||||
|
||||
// DeleteRegistration deletes the client's user registration from the ACME
|
||||
// server.
|
||||
func (c *Client) DeleteRegistration() error {
|
||||
if c == nil || c.user == nil {
|
||||
return errors.New("acme: cannot unregister a nil client or user")
|
||||
}
|
||||
log.Infof("acme: Deleting account for %s", c.user.GetEmail())
|
||||
|
||||
accMsg := accountMessage{
|
||||
Status: "deactivated",
|
||||
}
|
||||
|
||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// QueryRegistration runs a POST request on the client's registration and
|
||||
// returns the result.
|
||||
//
|
||||
// This is similar to the Register function, but acting on an existing
|
||||
// registration link and resource.
|
||||
func (c *Client) QueryRegistration() (*RegistrationResource, error) {
|
||||
if c == nil || c.user == nil {
|
||||
return nil, errors.New("acme: cannot query the registration of a nil client or user")
|
||||
}
|
||||
// Log the URL here instead of the email as the email may not be set
|
||||
log.Infof("acme: Querying account for %s", c.user.GetRegistration().URI)
|
||||
|
||||
accMsg := accountMessage{}
|
||||
|
||||
var serverReg accountMessage
|
||||
_, err := postJSON(c.jws, c.user.GetRegistration().URI, accMsg, &serverReg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reg := &RegistrationResource{Body: serverReg}
|
||||
|
||||
// Location: header is not returned so this needs to be populated off of
|
||||
// existing URI
|
||||
reg.URI = c.user.GetRegistration().URI
|
||||
|
||||
return reg, nil
|
||||
}
|
||||
|
||||
// ObtainCertificateForCSR 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 *Client) ObtainCertificateForCSR(csr x509.CertificateRequest, bundle bool) (*CertificateResource, error) {
|
||||
// figure out what domains it concerns
|
||||
// start with the common name
|
||||
domains := []string{csr.Subject.CommonName}
|
||||
|
||||
// loop over the SubjectAltName DNS names
|
||||
DNSNames:
|
||||
for _, sanName := range csr.DNSNames {
|
||||
for _, existingName := range domains {
|
||||
if existingName == sanName {
|
||||
// duplicate; skip this name
|
||||
continue DNSNames
|
||||
}
|
||||
}
|
||||
|
||||
// name is unique
|
||||
domains = append(domains, sanName)
|
||||
}
|
||||
|
||||
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.createOrderForIdentifiers(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authz, err := c.getAuthzForOrder(order)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
/*for _, auth := range authz {
|
||||
c.disableAuthz(auth)
|
||||
}*/
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.solveChallengeForAuthz(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.requestCertificateForCsr(order, bundle, csr.Raw, nil)
|
||||
if err != nil {
|
||||
for _, chln := range authz {
|
||||
failures[chln.Identifier.Value] = err
|
||||
}
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
// Add the CSR to the certificate so that it can be used for renewals.
|
||||
cert.CSR = 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
|
||||
}
|
||||
|
||||
// ObtainCertificate tries to obtain a single certificate using all domains passed into it.
|
||||
// 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 this function. If you do not want that you can supply your own private key
|
||||
// in the privKey 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.
|
||||
// This function will never return a partial certificate. If one domain in the list fails,
|
||||
// the whole certificate will fail.
|
||||
func (c *Client) ObtainCertificate(domains []string, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
|
||||
if len(domains) == 0 {
|
||||
return nil, errors.New("no domains to obtain a certificate for")
|
||||
}
|
||||
|
||||
if 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.createOrderForIdentifiers(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
authz, err := c.getAuthzForOrder(order)
|
||||
if err != nil {
|
||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||
/*for _, auth := range authz {
|
||||
c.disableAuthz(auth)
|
||||
}*/
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.solveChallengeForAuthz(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.requestCertificateForOrder(order, bundle, privKey, mustStaple)
|
||||
if err != nil {
|
||||
for _, auth := range authz {
|
||||
failures[auth.Identifier.Value] = 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
|
||||
}
|
||||
|
||||
// RevokeCertificate takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||
func (c *Client) RevokeCertificate(certificate []byte) error {
|
||||
certificates, err := parsePEMBundle(certificate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
x509Cert := certificates[0]
|
||||
if x509Cert.IsCA {
|
||||
return fmt.Errorf("Certificate bundle starts with a CA certificate")
|
||||
}
|
||||
|
||||
encodedCert := base64.URLEncoding.EncodeToString(x509Cert.Raw)
|
||||
|
||||
_, err = postJSON(c.jws, c.directory.RevokeCertURL, revokeCertMessage{Certificate: encodedCert}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// RenewCertificate takes a CertificateResource 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 CertificateResource should be non-nil.
|
||||
func (c *Client) RenewCertificate(cert CertificateResource, bundle, mustStaple bool) (*CertificateResource, error) {
|
||||
// Input certificate is PEM encoded. Decode it here as we may need the decoded
|
||||
// cert later on in the renewal process. The input may be a bundle or a single certificate.
|
||||
certificates, err := parsePEMBundle(cert.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", cert.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", cert.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(cert.CSR) > 0 {
|
||||
csr, errP := pemDecodeTox509CSR(cert.CSR)
|
||||
if errP != nil {
|
||||
return nil, errP
|
||||
}
|
||||
newCert, failures := c.ObtainCertificateForCSR(*csr, bundle)
|
||||
return newCert, failures
|
||||
}
|
||||
|
||||
var privKey crypto.PrivateKey
|
||||
if cert.PrivateKey != nil {
|
||||
privKey, err = parsePEMPrivateKey(cert.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var domains []string
|
||||
// check for SAN certificate
|
||||
if len(x509Cert.DNSNames) > 1 {
|
||||
domains = append(domains, x509Cert.Subject.CommonName)
|
||||
for _, sanDomain := range x509Cert.DNSNames {
|
||||
if sanDomain == x509Cert.Subject.CommonName {
|
||||
continue
|
||||
}
|
||||
domains = append(domains, sanDomain)
|
||||
}
|
||||
} else {
|
||||
domains = append(domains, x509Cert.Subject.CommonName)
|
||||
}
|
||||
|
||||
newCert, err := c.ObtainCertificate(domains, bundle, privKey, mustStaple)
|
||||
return newCert, err
|
||||
}
|
||||
|
||||
func (c *Client) createOrderForIdentifiers(domains []string) (orderResource, error) {
|
||||
var identifiers []identifier
|
||||
for _, domain := range domains {
|
||||
identifiers = append(identifiers, identifier{Type: "dns", Value: domain})
|
||||
}
|
||||
|
||||
order := orderMessage{
|
||||
Identifiers: identifiers,
|
||||
}
|
||||
|
||||
var response orderMessage
|
||||
hdr, err := postJSON(c.jws, c.directory.NewOrderURL, order, &response)
|
||||
if err != nil {
|
||||
return orderResource{}, err
|
||||
}
|
||||
|
||||
orderRes := orderResource{
|
||||
URL: hdr.Get("Location"),
|
||||
Domains: domains,
|
||||
orderMessage: response,
|
||||
}
|
||||
return orderRes, nil
|
||||
}
|
||||
|
||||
// an authz with the solver we have chosen and the index of the challenge associated with it
|
||||
type selectedAuthSolver struct {
|
||||
authz authorization
|
||||
challengeIndex int
|
||||
solver solver
|
||||
}
|
||||
|
||||
// Looks through the challenge combinations to find a solvable match.
|
||||
// Then solves the challenges in series and returns.
|
||||
func (c *Client) solveChallengeForAuthz(authorizations []authorization) error {
|
||||
failures := make(ObtainError)
|
||||
|
||||
authSolvers := []*selectedAuthSolver{}
|
||||
|
||||
// loop through the resources, basically through the domains. First pass just selects a solver for each authz.
|
||||
for _, authz := range authorizations {
|
||||
if authz.Status == statusValid {
|
||||
// Boulder might recycle recent validated authz (see issue #267)
|
||||
log.Infof("[%s] acme: Authorization already valid; skipping challenge", authz.Identifier.Value)
|
||||
continue
|
||||
}
|
||||
if i, solvr := c.chooseSolver(authz, authz.Identifier.Value); solvr != nil {
|
||||
authSolvers = append(authSolvers, &selectedAuthSolver{
|
||||
authz: authz,
|
||||
challengeIndex: i,
|
||||
solver: solvr,
|
||||
})
|
||||
} else {
|
||||
failures[authz.Identifier.Value] = fmt.Errorf("[%s] acme: Could not determine solvers", authz.Identifier.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// for all valid presolvers, first submit the challenges so they have max time to propagate
|
||||
for _, item := range authSolvers {
|
||||
authz := item.authz
|
||||
i := item.challengeIndex
|
||||
if presolver, ok := item.solver.(preSolver); ok {
|
||||
if err := presolver.PreSolve(authz.Challenges[i], authz.Identifier.Value); err != nil {
|
||||
failures[authz.Identifier.Value] = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// clean all created TXT records
|
||||
for _, item := range authSolvers {
|
||||
if clean, ok := item.solver.(cleanup); ok {
|
||||
if failures[item.authz.Identifier.Value] != nil {
|
||||
// already failed in previous loop
|
||||
continue
|
||||
}
|
||||
err := clean.CleanUp(item.authz.Challenges[item.challengeIndex], item.authz.Identifier.Value)
|
||||
if err != nil {
|
||||
log.Warnf("Error cleaning up %s: %v ", item.authz.Identifier.Value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// finally solve all challenges for real
|
||||
for _, item := range authSolvers {
|
||||
authz := item.authz
|
||||
i := item.challengeIndex
|
||||
if failures[authz.Identifier.Value] != nil {
|
||||
// already failed in previous loop
|
||||
continue
|
||||
}
|
||||
if err := item.solver.Solve(authz.Challenges[i], authz.Identifier.Value); err != nil {
|
||||
failures[authz.Identifier.Value] = err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Checks all challenges from the server in order and returns the first matching solver.
|
||||
func (c *Client) chooseSolver(auth authorization, domain string) (int, solver) {
|
||||
for i, challenge := range auth.Challenges {
|
||||
if solver, ok := c.solvers[Challenge(challenge.Type)]; ok {
|
||||
return i, solver
|
||||
}
|
||||
log.Infof("[%s] acme: Could not find solver for: %s", domain, challenge.Type)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Get the challenges needed to proof our identifier to the ACME server.
|
||||
func (c *Client) getAuthzForOrder(order orderResource) ([]authorization, error) {
|
||||
resc, errc := make(chan authorization), make(chan domainError)
|
||||
|
||||
delay := time.Second / overallRequestLimit
|
||||
|
||||
for _, authzURL := range order.Authorizations {
|
||||
time.Sleep(delay)
|
||||
|
||||
go func(authzURL string) {
|
||||
var authz authorization
|
||||
_, err := postAsGet(c.jws, authzURL, &authz)
|
||||
if err != nil {
|
||||
errc <- domainError{Domain: authz.Identifier.Value, Error: err}
|
||||
return
|
||||
}
|
||||
|
||||
resc <- authz
|
||||
}(authzURL)
|
||||
}
|
||||
|
||||
var responses []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
|
||||
}
|
||||
}
|
||||
|
||||
logAuthz(order)
|
||||
|
||||
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 logAuthz(order orderResource) {
|
||||
for i, auth := range order.Authorizations {
|
||||
log.Infof("[%s] AuthURL: %s", order.Identifiers[i].Value, auth)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanAuthz loops through the passed in slice and disables any auths which are not "valid"
|
||||
func (c *Client) disableAuthz(authURL string) error {
|
||||
var disabledAuth authorization
|
||||
_, err := postJSON(c.jws, authURL, deactivateAuthMessage{Status: "deactivated"}, &disabledAuth)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) requestCertificateForOrder(order orderResource, bundle bool, privKey crypto.PrivateKey, mustStaple bool) (*CertificateResource, error) {
|
||||
|
||||
var err error
|
||||
if privKey == nil {
|
||||
privKey, err = generatePrivateKey(c.keyType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// determine certificate name(s) based on the authorization resources
|
||||
commonName := order.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 := generateCsr(privKey, commonName, san, mustStaple)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.requestCertificateForCsr(order, bundle, csr, pemEncode(privKey))
|
||||
}
|
||||
|
||||
func (c *Client) requestCertificateForCsr(order orderResource, bundle bool, csr []byte, privateKeyPem []byte) (*CertificateResource, error) {
|
||||
commonName := order.Domains[0]
|
||||
|
||||
csrString := base64.RawURLEncoding.EncodeToString(csr)
|
||||
var retOrder orderMessage
|
||||
_, err := postJSON(c.jws, order.Finalize, csrMessage{Csr: csrString}, &retOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if retOrder.Status == statusInvalid {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certRes := CertificateResource{
|
||||
Domain: commonName,
|
||||
CertURL: retOrder.Certificate,
|
||||
PrivateKey: privateKeyPem,
|
||||
}
|
||||
|
||||
if retOrder.Status == statusValid {
|
||||
// if the certificate is available right away, short cut!
|
||||
ok, err := c.checkCertResponse(retOrder, &certRes, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return &certRes, nil
|
||||
}
|
||||
}
|
||||
|
||||
stopTimer := time.NewTimer(30 * time.Second)
|
||||
defer stopTimer.Stop()
|
||||
retryTick := time.NewTicker(500 * time.Millisecond)
|
||||
defer retryTick.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopTimer.C:
|
||||
return nil, errors.New("certificate polling timed out")
|
||||
case <-retryTick.C:
|
||||
_, err := postAsGet(c.jws, order.URL, &retOrder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
done, err := c.checkCertResponse(retOrder, &certRes, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if done {
|
||||
return &certRes, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkCertResponse 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 *Client) checkCertResponse(order orderMessage, certRes *CertificateResource, bundle bool) (bool, error) {
|
||||
switch order.Status {
|
||||
case statusValid:
|
||||
resp, err := postAsGet(c.jws, order.Certificate, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cert, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return false, 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
|
||||
links := parseLinks(resp.Header["Link"])
|
||||
if link, ok := links["up"]; ok {
|
||||
issuerCert, err := c.getIssuerCertificate(link)
|
||||
|
||||
if err != nil {
|
||||
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||
log.Warnf("[%s] acme: Could not bundle issuer certificate: %v", certRes.Domain, err)
|
||||
} else {
|
||||
issuerCert = pemEncode(derCertificateBytes(issuerCert))
|
||||
|
||||
// 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, issuerCert...)
|
||||
}
|
||||
|
||||
certRes.IssuerCertificate = issuerCert
|
||||
}
|
||||
} else {
|
||||
// Get issuerCert from bundled response from Let's Encrypt
|
||||
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
||||
_, rest := pem.Decode(cert)
|
||||
if rest != nil {
|
||||
certRes.IssuerCertificate = rest
|
||||
}
|
||||
}
|
||||
|
||||
certRes.Certificate = cert
|
||||
certRes.CertURL = order.Certificate
|
||||
certRes.CertStableURL = order.Certificate
|
||||
log.Infof("[%s] Server responded with a certificate.", certRes.Domain)
|
||||
return true, nil
|
||||
|
||||
case "processing":
|
||||
return false, nil
|
||||
case statusInvalid:
|
||||
return false, errors.New("order has invalid state: invalid")
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getIssuerCertificate requests the issuer certificate
|
||||
func (c *Client) getIssuerCertificate(url string) ([]byte, error) {
|
||||
log.Infof("acme: Requesting issuer cert from %s", url)
|
||||
resp, err := postAsGet(c.jws, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = x509.ParseCertificate(issuerBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return issuerBytes, err
|
||||
}
|
||||
|
||||
func parseLinks(links []string) map[string]string {
|
||||
aBrkt := regexp.MustCompile("[<>]")
|
||||
slver := regexp.MustCompile("(.+) *= *\"(.+)\"")
|
||||
linkMap := make(map[string]string)
|
||||
|
||||
for _, link := range links {
|
||||
|
||||
link = aBrkt.ReplaceAllString(link, "")
|
||||
parts := strings.Split(link, ";")
|
||||
|
||||
matches := slver.FindStringSubmatch(parts[1])
|
||||
if len(matches) > 0 {
|
||||
linkMap[matches[2]] = parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
return linkMap
|
||||
}
|
||||
|
||||
// validate makes the ACME server start validating a
|
||||
// challenge response, only returning once it is done.
|
||||
func validate(j *jws, domain, uri string, c challenge) error {
|
||||
var chlng challenge
|
||||
|
||||
// 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.
|
||||
hdr, err := postJSON(j, uri, struct{}{}, &chlng)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// After the path is sent, the ACME server will access our server.
|
||||
// Repeatedly check the server for an updated status on our request.
|
||||
for {
|
||||
switch chlng.Status {
|
||||
case statusValid:
|
||||
log.Infof("[%s] The server validated our request", domain)
|
||||
return nil
|
||||
case "pending":
|
||||
case "processing":
|
||||
case statusInvalid:
|
||||
return handleChallengeError(chlng)
|
||||
default:
|
||||
return errors.New("the server returned an unexpected state")
|
||||
}
|
||||
|
||||
ra, err := strconv.Atoi(hdr.Get("Retry-After"))
|
||||
if err != nil {
|
||||
// The ACME server MUST return a Retry-After.
|
||||
// If it doesn't, we'll just poll hard.
|
||||
ra = 5
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(ra) * time.Second)
|
||||
|
||||
resp, err := postAsGet(j, uri, &chlng)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp != nil {
|
||||
hdr = resp.Header
|
||||
}
|
||||
}
|
||||
}
|
284
vendor/github.com/xenolf/lego/acme/commons.go
generated
vendored
Normal file
284
vendor/github.com/xenolf/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"`
|
||||
}
|
334
vendor/github.com/xenolf/lego/acme/crypto.go
generated
vendored
334
vendor/github.com/xenolf/lego/acme/crypto.go
generated
vendored
|
@ -1,334 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ocsp"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// KeyType represents the key algo as well as the key size or curve to use.
|
||||
type KeyType string
|
||||
type derCertificateBytes []byte
|
||||
|
||||
// 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}
|
||||
)
|
||||
|
||||
// GetOCSPForCert 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 GetOCSPForCert(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||
certificates, err := 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 := httpGet(issuedCert.IssuingCertificateURL[0])
|
||||
if errC != nil {
|
||||
return nil, nil, errC
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
issuerBytes, errC := ioutil.ReadAll(limitReader(resp.Body, 1024*1024))
|
||||
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
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(ocspReq)
|
||||
req, err := httpPost(issuedCert.OCSPServer[0], "application/ocsp-request", reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer req.Body.Close()
|
||||
|
||||
ocspResBytes, err := ioutil.ReadAll(limitReader(req.Body, 1024*1024))
|
||||
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 getKeyAuthorization(token string, key interface{}) (string, error) {
|
||||
var publicKey crypto.PublicKey
|
||||
switch k := key.(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
|
||||
}
|
||||
|
||||
// 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},
|
||||
}
|
||||
|
||||
if len(san) > 0 {
|
||||
template.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 {
|
||||
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 pem.EncodeToMemory(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)
|
||||
}
|
||||
|
||||
// GetPEMCertExpiration returns the "NotAfter" date of a PEM encoded certificate.
|
||||
// The certificate has to be PEM encoded. Any other encodings like DER will fail.
|
||||
func GetPEMCertExpiration(cert []byte) (time.Time, error) {
|
||||
pemBlock, err := pemDecode(cert)
|
||||
if pemBlock == nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return getCertExpiration(pemBlock.Bytes)
|
||||
}
|
||||
|
||||
// getCertExpiration returns the "NotAfter" date of a DER encoded certificate.
|
||||
func getCertExpiration(cert []byte) (time.Time, error) {
|
||||
pCert, err := x509.ParseCertificate(cert)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
return pCert.NotAfter, nil
|
||||
}
|
||||
|
||||
func generatePemCert(privKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||
derBytes, err := generateDerCert(privKey, time.Time{}, domain, extensions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), nil
|
||||
}
|
||||
|
||||
func generateDerCert(privKey *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, &privKey.PublicKey, privKey)
|
||||
}
|
||||
|
||||
func limitReader(rd io.ReadCloser, numBytes int64) io.ReadCloser {
|
||||
return http.MaxBytesReader(nil, rd, numBytes)
|
||||
}
|
343
vendor/github.com/xenolf/lego/acme/dns_challenge.go
generated
vendored
343
vendor/github.com/xenolf/lego/acme/dns_challenge.go
generated
vendored
|
@ -1,343 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
type preCheckDNSFunc func(fqdn, value string) (bool, error)
|
||||
|
||||
var (
|
||||
// PreCheckDNS checks DNS propagation before notifying ACME that
|
||||
// the DNS challenge is ready.
|
||||
PreCheckDNS preCheckDNSFunc = checkDNSPropagation
|
||||
fqdnToZone = map[string]string{}
|
||||
muFqdnToZone sync.Mutex
|
||||
)
|
||||
|
||||
const defaultResolvConf = "/etc/resolv.conf"
|
||||
|
||||
const (
|
||||
// DefaultPropagationTimeout default propagation timeout
|
||||
DefaultPropagationTimeout = 60 * time.Second
|
||||
|
||||
// DefaultPollingInterval default polling interval
|
||||
DefaultPollingInterval = 2 * time.Second
|
||||
|
||||
// DefaultTTL default TTL
|
||||
DefaultTTL = 120
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
// DNSTimeout is used to override the default DNS timeout of 10 seconds.
|
||||
var DNSTimeout = 10 * time.Second
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
systemNameservers := []string{}
|
||||
for _, server := range config.Servers {
|
||||
// ensure all servers have a port number
|
||||
if _, _, err := net.SplitHostPort(server); err != nil {
|
||||
systemNameservers = append(systemNameservers, net.JoinHostPort(server, "53"))
|
||||
} else {
|
||||
systemNameservers = append(systemNameservers, server)
|
||||
}
|
||||
}
|
||||
return systemNameservers
|
||||
}
|
||||
|
||||
// DNS01Record returns a DNS record which will fulfill the `dns-01` challenge
|
||||
func DNS01Record(domain, keyAuth string) (fqdn string, value string, ttl int) {
|
||||
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||||
// base64URL encoding without padding
|
||||
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||||
ttl = DefaultTTL
|
||||
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
||||
return
|
||||
}
|
||||
|
||||
// dnsChallenge implements the dns-01 challenge according to ACME 7.5
|
||||
type dnsChallenge struct {
|
||||
jws *jws
|
||||
validate validateFunc
|
||||
provider ChallengeProvider
|
||||
}
|
||||
|
||||
// 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 (s *dnsChallenge) PreSolve(chlng challenge, domain string) error {
|
||||
log.Infof("[%s] acme: Preparing to solve DNS-01", domain)
|
||||
|
||||
if s.provider == nil {
|
||||
return errors.New("no DNS Provider configured")
|
||||
}
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error presenting token: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *dnsChallenge) Solve(chlng challenge, domain string) error {
|
||||
log.Infof("[%s] acme: Trying to solve DNS-01", domain)
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fqdn, value, _ := DNS01Record(domain, keyAuth)
|
||||
|
||||
log.Infof("[%s] Checking DNS record propagation using %+v", domain, RecursiveNameservers)
|
||||
|
||||
var timeout, interval time.Duration
|
||||
switch provider := s.provider.(type) {
|
||||
case ChallengeProviderTimeout:
|
||||
timeout, interval = provider.Timeout()
|
||||
default:
|
||||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
||||
}
|
||||
|
||||
err = WaitFor(timeout, interval, func() (bool, error) {
|
||||
return PreCheckDNS(fqdn, value)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||
}
|
||||
|
||||
// CleanUp cleans the challenge
|
||||
func (s *dnsChallenge) CleanUp(chlng challenge, domain string) error {
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
}
|
||||
|
||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||
func 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 r.Rcode == dns.RcodeSuccess {
|
||||
// If we see a CNAME here then use the alias
|
||||
for _, rr := range r.Answer {
|
||||
if cn, ok := rr.(*dns.CNAME); ok {
|
||||
if cn.Hdr.Name == fqdn {
|
||||
fqdn = cn.Target
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 found bool
|
||||
for _, rr := range r.Answer {
|
||||
if txt, ok := rr.(*dns.TXT); ok {
|
||||
if strings.Join(txt.Txt, "") == value {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, fmt.Errorf("NS %s did not return the expected TXT record [fqdn: %s]", ns, fqdn)
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// dnsQuery will query a nameserver, iterating through the supplied servers as it retries
|
||||
// The nameserver should include a port, to facilitate testing where we talk to a mock dns server.
|
||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) {
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion(fqdn, rtype)
|
||||
m.SetEdns0(4096, false)
|
||||
|
||||
if !recursive {
|
||||
m.RecursionDesired = false
|
||||
}
|
||||
|
||||
// Will retry the request based on the number of servers (n+1)
|
||||
for i := 1; i <= len(nameservers)+1; i++ {
|
||||
ns := nameservers[i%len(nameservers)]
|
||||
udp := &dns.Client{Net: "udp", Timeout: DNSTimeout}
|
||||
in, _, err = udp.Exchange(m, ns)
|
||||
|
||||
if err == dns.ErrTruncated {
|
||||
tcp := &dns.Client{Net: "tcp", Timeout: DNSTimeout}
|
||||
// If the TCP request succeeds, the err will reset to nil
|
||||
in, _, err = tcp.Exchange(m, ns)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// lookupNameservers returns the authoritative nameservers for the given fqdn.
|
||||
func lookupNameservers(fqdn string) ([]string, error) {
|
||||
var authoritativeNss []string
|
||||
|
||||
zone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
||||
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, nameservers []string) (string, error) {
|
||||
muFqdnToZone.Lock()
|
||||
defer muFqdnToZone.Unlock()
|
||||
|
||||
// Do we have it cached?
|
||||
if zone, ok := fqdnToZone[fqdn]; ok {
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
labelIndexes := dns.Split(fqdn)
|
||||
for _, index := range labelIndexes {
|
||||
domain := fqdn[index:]
|
||||
|
||||
in, err := dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||
if in.Rcode != dns.RcodeNameError && in.Rcode != dns.RcodeSuccess {
|
||||
return "", fmt.Errorf("unexpected response code '%s' for %s",
|
||||
dns.RcodeToString[in.Rcode], domain)
|
||||
}
|
||||
|
||||
// Check if we got a SOA RR in the answer section
|
||||
if in.Rcode == dns.RcodeSuccess {
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find the start of authority")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||
func ClearFqdnCache() {
|
||||
fqdnToZone = map[string]string{}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
55
vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go
generated
vendored
55
vendor/github.com/xenolf/lego/acme/dns_challenge_manual.go
generated
vendored
|
@ -1,55 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
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, ttl := DNS01Record(domain, keyAuth)
|
||||
dnsRecord := fmt.Sprintf(dnsTemplate, fqdn, ttl, value)
|
||||
|
||||
authZone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("acme: Please create the following TXT record in your %s zone:", authZone)
|
||||
log.Infof("acme: %s", dnsRecord)
|
||||
log.Infof("acme: Press 'Enter' when you are done")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
_, _ = reader.ReadString('\n')
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanUp prints instructions for manually removing the TXT record
|
||||
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, ttl := DNS01Record(domain, keyAuth)
|
||||
dnsRecord := fmt.Sprintf(dnsTemplate, fqdn, ttl, "...")
|
||||
|
||||
authZone, err := FindZoneByFqdn(fqdn, RecursiveNameservers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("acme: You can now remove this TXT record from your %s zone:", authZone)
|
||||
log.Infof("acme: %s", dnsRecord)
|
||||
return nil
|
||||
}
|
91
vendor/github.com/xenolf/lego/acme/error.go
generated
vendored
91
vendor/github.com/xenolf/lego/acme/error.go
generated
vendored
|
@ -1,91 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
tosAgreementError = "Terms of service have changed"
|
||||
invalidNonceError = "urn:ietf:params:acme:error:badNonce"
|
||||
)
|
||||
|
||||
// RemoteError is the base type for all errors specific to the ACME protocol.
|
||||
type RemoteError struct {
|
||||
StatusCode int `json:"status,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Detail string `json:"detail"`
|
||||
}
|
||||
|
||||
func (e RemoteError) Error() string {
|
||||
return fmt.Sprintf("acme: Error %d - %s - %s", e.StatusCode, e.Type, e.Detail)
|
||||
}
|
||||
|
||||
// TOSError represents the error which is returned if the user needs to
|
||||
// accept the TOS.
|
||||
// TODO: include the new TOS url if we can somehow obtain it.
|
||||
type TOSError struct {
|
||||
RemoteError
|
||||
}
|
||||
|
||||
// NonceError represents the error which is returned if the
|
||||
// nonce sent by the client was not accepted by the server.
|
||||
type NonceError struct {
|
||||
RemoteError
|
||||
}
|
||||
|
||||
type domainError struct {
|
||||
Domain string
|
||||
Error error
|
||||
}
|
||||
|
||||
// ObtainError is returned when there are specific errors available
|
||||
// per domain. For example in ObtainCertificate
|
||||
type ObtainError map[string]error
|
||||
|
||||
func (e ObtainError) Error() string {
|
||||
buffer := bytes.NewBufferString("acme: Error -> One or more domains had a problem:\n")
|
||||
for dom, err := range e {
|
||||
buffer.WriteString(fmt.Sprintf("[%s] %s\n", dom, err))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func handleHTTPError(resp *http.Response) error {
|
||||
var errorDetail RemoteError
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType == "application/json" || strings.HasPrefix(contentType, "application/problem+json") {
|
||||
err := json.NewDecoder(resp.Body).Decode(&errorDetail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
detailBytes, err := ioutil.ReadAll(limitReader(resp.Body, maxBodySize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
errorDetail.Detail = string(detailBytes)
|
||||
}
|
||||
|
||||
errorDetail.StatusCode = resp.StatusCode
|
||||
|
||||
// Check for errors we handle specifically
|
||||
if errorDetail.StatusCode == http.StatusForbidden && errorDetail.Detail == tosAgreementError {
|
||||
return TOSError{errorDetail}
|
||||
}
|
||||
|
||||
if errorDetail.StatusCode == http.StatusBadRequest && errorDetail.Type == invalidNonceError {
|
||||
return NonceError{errorDetail}
|
||||
}
|
||||
|
||||
return errorDetail
|
||||
}
|
||||
|
||||
func handleChallengeError(chlng challenge) error {
|
||||
return chlng.Error
|
||||
}
|
58
vendor/github.com/xenolf/lego/acme/errors.go
generated
vendored
Normal file
58
vendor/github.com/xenolf/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
|
||||
}
|
212
vendor/github.com/xenolf/lego/acme/http.go
generated
vendored
212
vendor/github.com/xenolf/lego/acme/http.go
generated
vendored
|
@ -1,212 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// UserAgent (if non-empty) will be tacked onto the User-Agent string in requests.
|
||||
UserAgent string
|
||||
|
||||
// HTTPClient is an HTTP client with a reasonable timeout value and
|
||||
// potentially a custom *x509.CertPool based on the caCertificatesEnvVar
|
||||
// environment variable (see the `initCertPool` function)
|
||||
HTTPClient = 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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// ourUserAgent is the User-Agent of this underlying library package.
|
||||
// NOTE: Update this with each tagged release.
|
||||
ourUserAgent = "xenolf-acme/1.2.1"
|
||||
|
||||
// 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"
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// httpHead performs a HEAD request with a proper User-Agent string.
|
||||
// The response body (resp.Body) is already closed when this function returns.
|
||||
func httpHead(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest(http.MethodHead, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to head %q: %v", url, err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
resp, err = HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return resp, fmt.Errorf("failed to do head %q: %v", url, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// httpPost performs a POST request with a proper User-Agent string.
|
||||
// Callers should close resp.Body when done reading from it.
|
||||
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest(http.MethodPost, url, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to post %q: %v", url, err)
|
||||
}
|
||||
req.Header.Set("Content-Type", bodyType)
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
return HTTPClient.Do(req)
|
||||
}
|
||||
|
||||
// httpGet performs a GET request with a proper User-Agent string.
|
||||
// Callers should close resp.Body when done reading from it.
|
||||
func httpGet(url string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get %q: %v", url, err)
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent())
|
||||
|
||||
return HTTPClient.Do(req)
|
||||
}
|
||||
|
||||
// getJSON performs an HTTP GET request and parses the response body
|
||||
// as JSON, into the provided respBody object.
|
||||
func getJSON(uri string, respBody interface{}) (http.Header, error) {
|
||||
resp, err := httpGet(uri)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get json %q: %v", uri, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
return resp.Header, handleHTTPError(resp)
|
||||
}
|
||||
|
||||
return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
|
||||
}
|
||||
|
||||
// postJSON performs an HTTP POST request and parses the response body
|
||||
// as JSON, into the provided respBody object.
|
||||
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
|
||||
jsonBytes, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to marshal network message")
|
||||
}
|
||||
|
||||
resp, err := post(j, uri, jsonBytes, respBody)
|
||||
if resp == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.Header, err
|
||||
}
|
||||
|
||||
func postAsGet(j *jws, uri string, respBody interface{}) (*http.Response, error) {
|
||||
return post(j, uri, []byte{}, respBody)
|
||||
}
|
||||
|
||||
func post(j *jws, uri string, reqBody []byte, respBody interface{}) (*http.Response, error) {
|
||||
resp, err := j.post(uri, reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to post JWS message. -> %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
err = handleHTTPError(resp)
|
||||
switch err.(type) {
|
||||
case NonceError:
|
||||
// Retry once if the nonce was invalidated
|
||||
|
||||
retryResp, errP := j.post(uri, reqBody)
|
||||
if errP != nil {
|
||||
return nil, fmt.Errorf("failed to post JWS message. -> %v", errP)
|
||||
}
|
||||
|
||||
if retryResp.StatusCode >= http.StatusBadRequest {
|
||||
return retryResp, handleHTTPError(retryResp)
|
||||
}
|
||||
|
||||
if respBody == nil {
|
||||
return retryResp, nil
|
||||
}
|
||||
|
||||
return retryResp, json.NewDecoder(retryResp.Body).Decode(respBody)
|
||||
default:
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
if respBody == nil {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return resp, json.NewDecoder(resp.Body).Decode(respBody)
|
||||
}
|
||||
|
||||
// userAgent builds and returns the User-Agent string to use in requests.
|
||||
func userAgent() string {
|
||||
ua := fmt.Sprintf("%s %s (%s; %s; %s)", UserAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
|
||||
return strings.TrimSpace(ua)
|
||||
}
|
42
vendor/github.com/xenolf/lego/acme/http_challenge.go
generated
vendored
42
vendor/github.com/xenolf/lego/acme/http_challenge.go
generated
vendored
|
@ -1,42 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
type httpChallenge struct {
|
||||
jws *jws
|
||||
validate validateFunc
|
||||
provider ChallengeProvider
|
||||
}
|
||||
|
||||
// HTTP01ChallengePath returns the URL path for the `http-01` challenge
|
||||
func HTTP01ChallengePath(token string) string {
|
||||
return "/.well-known/acme-challenge/" + token
|
||||
}
|
||||
|
||||
func (s *httpChallenge) Solve(chlng challenge, domain string) error {
|
||||
|
||||
log.Infof("[%s] acme: Trying to solve HTTP-01", domain)
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, s.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := s.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] error cleaning up: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return s.validate(s.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||
}
|
167
vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
167
vendor/github.com/xenolf/lego/acme/jws.go
generated
vendored
|
@ -1,167 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
type jws struct {
|
||||
getNonceURL string
|
||||
privKey crypto.PrivateKey
|
||||
kid string
|
||||
nonces nonceManager
|
||||
}
|
||||
|
||||
// Posts a JWS signed message to the specified URL.
|
||||
// It does NOT close the response body, so the caller must
|
||||
// do that if no error was returned.
|
||||
func (j *jws) post(url string, content []byte) (*http.Response, error) {
|
||||
signedContent, err := j.signContent(url, content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
|
||||
}
|
||||
|
||||
data := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
||||
resp, err := httpPost(url, "application/jose+json", data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to HTTP POST to %s -> %s", url, err.Error())
|
||||
}
|
||||
|
||||
nonce, nonceErr := getNonceFromResponse(resp)
|
||||
if nonceErr == nil {
|
||||
j.nonces.Push(nonce)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
jsonKey := jose.JSONWebKey{
|
||||
Key: j.privKey,
|
||||
KeyID: j.kid,
|
||||
}
|
||||
|
||||
signKey := jose.SigningKey{
|
||||
Algorithm: alg,
|
||||
Key: jsonKey,
|
||||
}
|
||||
options := jose.SignerOptions{
|
||||
NonceSource: j,
|
||||
ExtraHeaders: make(map[jose.HeaderKey]interface{}),
|
||||
}
|
||||
options.ExtraHeaders["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 -> %s", err.Error())
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign content -> %s", err.Error())
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) signEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
|
||||
jwk := jose.JSONWebKey{Key: j.privKey}
|
||||
jwkJSON, err := jwk.Public().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("acme: error encoding eab jwk key: %s", err.Error())
|
||||
}
|
||||
|
||||
signer, err := jose.NewSigner(
|
||||
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
|
||||
&jose.SignerOptions{
|
||||
EmbedJWK: false,
|
||||
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||
"kid": kid,
|
||||
"url": url,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create External Account Binding jose signer -> %s", err.Error())
|
||||
}
|
||||
|
||||
signed, err := signer.Sign(jwkJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to External Account Binding sign content -> %s", err.Error())
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
func (j *jws) Nonce() (string, error) {
|
||||
if nonce, ok := j.nonces.Pop(); ok {
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
return getNonce(j.getNonceURL)
|
||||
}
|
||||
|
||||
type nonceManager struct {
|
||||
nonces []string
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (n *nonceManager) 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
|
||||
}
|
||||
|
||||
func (n *nonceManager) Push(nonce string) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
n.nonces = append(n.nonces, nonce)
|
||||
}
|
||||
|
||||
func getNonce(url string) (string, error) {
|
||||
resp, err := httpHead(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get nonce from HTTP HEAD -> %s", err.Error())
|
||||
}
|
||||
|
||||
return getNonceFromResponse(resp)
|
||||
}
|
||||
|
||||
func getNonceFromResponse(resp *http.Response) (string, error) {
|
||||
nonce := resp.Header.Get("Replay-Nonce")
|
||||
if nonce == "" {
|
||||
return "", fmt.Errorf("server did not respond with a proper nonce header")
|
||||
}
|
||||
|
||||
return nonce, nil
|
||||
}
|
103
vendor/github.com/xenolf/lego/acme/messages.go
generated
vendored
103
vendor/github.com/xenolf/lego/acme/messages.go
generated
vendored
|
@ -1,103 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RegistrationResource represents all important informations about a registration
|
||||
// of which the client needs to keep track itself.
|
||||
type RegistrationResource struct {
|
||||
Body accountMessage `json:"body,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
type directory struct {
|
||||
NewNonceURL string `json:"newNonce"`
|
||||
NewAccountURL string `json:"newAccount"`
|
||||
NewOrderURL string `json:"newOrder"`
|
||||
RevokeCertURL string `json:"revokeCert"`
|
||||
KeyChangeURL string `json:"keyChange"`
|
||||
Meta struct {
|
||||
TermsOfService string `json:"termsOfService"`
|
||||
Website string `json:"website"`
|
||||
CaaIdentities []string `json:"caaIdentities"`
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||
} `json:"meta"`
|
||||
}
|
||||
|
||||
type accountMessage struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed,omitempty"`
|
||||
Orders string `json:"orders,omitempty"`
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting,omitempty"`
|
||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding,omitempty"`
|
||||
}
|
||||
|
||||
type orderResource struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Domains []string `json:"domains,omitempty"`
|
||||
orderMessage `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
type orderMessage struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Expires string `json:"expires,omitempty"`
|
||||
Identifiers []identifier `json:"identifiers"`
|
||||
NotBefore string `json:"notBefore,omitempty"`
|
||||
NotAfter string `json:"notAfter,omitempty"`
|
||||
Authorizations []string `json:"authorizations,omitempty"`
|
||||
Finalize string `json:"finalize,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
}
|
||||
|
||||
type authorization struct {
|
||||
Status string `json:"status"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Identifier identifier `json:"identifier"`
|
||||
Challenges []challenge `json:"challenges"`
|
||||
}
|
||||
|
||||
type identifier struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type challenge struct {
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Token string `json:"token"`
|
||||
Validated time.Time `json:"validated"`
|
||||
KeyAuthorization string `json:"keyAuthorization"`
|
||||
Error RemoteError `json:"error"`
|
||||
}
|
||||
|
||||
type csrMessage struct {
|
||||
Csr string `json:"csr"`
|
||||
}
|
||||
|
||||
type revokeCertMessage struct {
|
||||
Certificate string `json:"certificate"`
|
||||
}
|
||||
|
||||
type deactivateAuthMessage struct {
|
||||
Status string `jsom:"status"`
|
||||
}
|
||||
|
||||
// CertificateResource 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 CertificateResource struct {
|
||||
Domain string `json:"domain"`
|
||||
CertURL string `json:"certUrl"`
|
||||
CertStableURL string `json:"certStableUrl"`
|
||||
AccountRef string `json:"accountRef,omitempty"`
|
||||
PrivateKey []byte `json:"-"`
|
||||
Certificate []byte `json:"-"`
|
||||
IssuerCertificate []byte `json:"-"`
|
||||
CSR []byte `json:"-"`
|
||||
}
|
104
vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
generated
vendored
104
vendor/github.com/xenolf/lego/acme/tls_alpn_challenge.go
generated
vendored
|
@ -1,104 +0,0 @@
|
|||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/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 tlsALPNChallenge struct {
|
||||
jws *jws
|
||||
validate validateFunc
|
||||
provider ChallengeProvider
|
||||
}
|
||||
|
||||
// Solve manages the provider to validate and solve the challenge.
|
||||
func (t *tlsALPNChallenge) Solve(chlng challenge, domain string) error {
|
||||
log.Infof("[%s] acme: Trying to solve TLS-ALPN-01", domain)
|
||||
|
||||
// Generate the Key Authorization for the challenge
|
||||
keyAuth, err := getKeyAuthorization(chlng.Token, t.jws.privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.provider.Present(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error presenting token: %v", domain, err)
|
||||
}
|
||||
defer func() {
|
||||
err := t.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||
if err != nil {
|
||||
log.Warnf("[%s] error cleaning up: %v", domain, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return t.validate(t.jws, domain, chlng.URL, challenge{Type: chlng.Type, Token: chlng.Token, KeyAuthorization: keyAuth})
|
||||
}
|
||||
|
||||
// TLSALPNChallengeBlocks returns PEM blocks (certPEMBlock, keyPEMBlock) with the acmeValidation-v1 extension
|
||||
// and domain name for the `tls-alpn-01` challenge.
|
||||
func TLSALPNChallengeBlocks(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.
|
||||
tempPrivKey, err := generatePrivateKey(RSA2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rsaPrivKey := tempPrivKey.(*rsa.PrivateKey)
|
||||
|
||||
// Generate the PEM certificate using the provided private key, domain, and extra extensions.
|
||||
tempCertPEM, err := generatePemCert(rsaPrivKey, 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.
|
||||
rsaPrivPEM := pemEncode(rsaPrivKey)
|
||||
|
||||
return tempCertPEM, rsaPrivPEM, nil
|
||||
}
|
||||
|
||||
// TLSALPNChallengeCert returns a certificate with the acmeValidation-v1 extension
|
||||
// and domain name for the `tls-alpn-01` challenge.
|
||||
func TLSALPNChallengeCert(domain, keyAuth string) (*tls.Certificate, error) {
|
||||
tempCertPEM, rsaPrivPEM, err := TLSALPNChallengeBlocks(domain, keyAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certificate, err := tls.X509KeyPair(tempCertPEM, rsaPrivPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &certificate, nil
|
||||
}
|
252
vendor/github.com/xenolf/lego/certcrypto/crypto.go
generated
vendored
Normal file
252
vendor/github.com/xenolf/lego/certcrypto/crypto.go
generated
vendored
Normal file
|
@ -0,0 +1,252 @@
|
|||
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 {
|
||||
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 pem.EncodeToMemory(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/xenolf/lego/certificate/authorization.go
generated
vendored
Normal file
69
vendor/github.com/xenolf/lego/certificate/authorization.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package certificate
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/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/xenolf/lego/certificate/certificates.go
generated
vendored
Normal file
493
vendor/github.com/xenolf/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/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/acme/api"
|
||||
"github.com/xenolf/lego/certcrypto"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/lego/log"
|
||||
"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 Certifier struct {
|
||||
core *api.Core
|
||||
keyType certcrypto.KeyType
|
||||
resolver resolver
|
||||
}
|
||||
|
||||
func NewCertifier(core *api.Core, keyType certcrypto.KeyType, resolver resolver) *Certifier {
|
||||
return &Certifier{
|
||||
core: core,
|
||||
keyType: keyType,
|
||||
resolver: resolver,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.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, err := c.checkResponse(respOrder, certRes, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
return certRes, nil
|
||||
}
|
||||
}
|
||||
|
||||
return c.waitForCertificate(certRes, order.Location, bundle)
|
||||
}
|
||||
|
||||
func (c *Certifier) waitForCertificate(certRes *Resource, orderURL string, bundle bool) (*Resource, error) {
|
||||
stopTimer := time.NewTimer(30 * time.Second)
|
||||
defer stopTimer.Stop()
|
||||
retryTick := time.NewTicker(500 * time.Millisecond)
|
||||
defer retryTick.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopTimer.C:
|
||||
return nil, errors.New("certificate polling timed out")
|
||||
case <-retryTick.C:
|
||||
order, err := c.core.Orders.Get(orderURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
done, err := c.checkResponse(order, certRes, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if done {
|
||||
return certRes, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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/xenolf/lego/certificate/errors.go
generated
vendored
Normal file
30
vendor/github.com/xenolf/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/xenolf/lego/challenge/challenges.go
generated
vendored
Normal file
44
vendor/github.com/xenolf/lego/challenge/challenges.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package challenge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/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
|
||||
}
|
176
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
generated
vendored
Normal file
176
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/acme/api"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/wait"
|
||||
)
|
||||
|
||||
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(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, authz.Identifier.Value, 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)
|
||||
return
|
||||
}
|
52
vendor/github.com/xenolf/lego/challenge/dns01/dns_challenge_manual.go
generated
vendored
Normal file
52
vendor/github.com/xenolf/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/xenolf/lego/challenge/dns01/fqdn.go
generated
vendored
Normal file
19
vendor/github.com/xenolf/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/xenolf/lego/challenge/dns01/nameserver.go
generated
vendored
Normal file
232
vendor/github.com/xenolf/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 ""
|
||||
}
|
114
vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
generated
vendored
Normal file
114
vendor/github.com/xenolf/lego/challenge/dns01/precheck.go
generated
vendored
Normal file
|
@ -0,0 +1,114 @@
|
|||
package dns01
|
||||
|
||||
import (
|
||||
"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)
|
||||
|
||||
func AddPreCheck(preCheck PreCheckFunc) ChallengeOption {
|
||||
// Prevent race condition
|
||||
check := preCheck
|
||||
return func(chlg *Challenge) error {
|
||||
chlg.preCheck.checkFunc = check
|
||||
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 PreCheckFunc
|
||||
// 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(fqdn, value string) (bool, error) {
|
||||
if p.checkFunc == nil {
|
||||
return p.checkDNSPropagation(fqdn, value)
|
||||
}
|
||||
return p.checkFunc(fqdn, value)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// If we see a CNAME here then use the alias
|
||||
for _, rr := range r.Answer {
|
||||
if cn, ok := rr.(*dns.CNAME); ok {
|
||||
if cn.Hdr.Name == fqdn {
|
||||
fqdn = cn.Target
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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/xenolf/lego/challenge/http01/http_challenge.go
generated
vendored
Normal file
65
vendor/github.com/xenolf/lego/challenge/http01/http_challenge.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/acme/api"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/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, authz.Identifier.Value, chlng)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package acme
|
||||
package http01
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -9,31 +9,31 @@ import (
|
|||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
// HTTPProviderServer implements ChallengeProvider for `http-01` challenge
|
||||
// It may be instantiated without using the NewHTTPProviderServer function if
|
||||
// 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 HTTPProviderServer struct {
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
done chan bool
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewHTTPProviderServer creates a new HTTPProviderServer on the selected interface and port.
|
||||
// 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 NewHTTPProviderServer(iface, port string) *HTTPProviderServer {
|
||||
return &HTTPProviderServer{iface: iface, port: port}
|
||||
func NewProviderServer(iface, port string) *ProviderServer {
|
||||
return &ProviderServer{iface: iface, port: port}
|
||||
}
|
||||
|
||||
// Present starts a web server and makes the token available at `HTTP01ChallengePath(token)` for web requests.
|
||||
func (s *HTTPProviderServer) Present(domain, token, keyAuth string) error {
|
||||
// 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", net.JoinHostPort(s.iface, s.port))
|
||||
s.listener, err = net.Listen("tcp", s.GetAddress())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start HTTP server for challenge -> %v", err)
|
||||
}
|
||||
|
@ -43,8 +43,12 @@ func (s *HTTPProviderServer) Present(domain, token, keyAuth string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CleanUp closes the HTTP server and removes the token from `HTTP01ChallengePath(token)`
|
||||
func (s *HTTPProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
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
|
||||
}
|
||||
|
@ -53,8 +57,8 @@ func (s *HTTPProviderServer) CleanUp(domain, token, keyAuth string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
|
||||
path := HTTP01ChallengePath(token)
|
||||
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
|
||||
|
@ -80,12 +84,12 @@ func (s *HTTPProviderServer) serve(domain, token, keyAuth string) {
|
|||
|
||||
httpServer := &http.Server{Handler: mux}
|
||||
|
||||
// Once httpServer is shut down we don't want any lingering
|
||||
// connections, so disable KeepAlives.
|
||||
// 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 {
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
log.Println(err)
|
||||
}
|
||||
s.done <- true
|
|
@ -1,28 +1,28 @@
|
|||
package acme
|
||||
package challenge
|
||||
|
||||
import "time"
|
||||
|
||||
// ChallengeProvider enables implementing a custom challenge
|
||||
// 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 ChallengeProvider interface {
|
||||
type Provider interface {
|
||||
Present(domain, token, keyAuth string) error
|
||||
CleanUp(domain, token, keyAuth string) error
|
||||
}
|
||||
|
||||
// ChallengeProviderTimeout allows for implementing a
|
||||
// ChallengeProvider where an unusually long timeout is required when
|
||||
// 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 progagation. If an implementor of a
|
||||
// ChallengeProvider provides a Timeout method, then the return values
|
||||
// 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 ChallengeProvider.
|
||||
type ChallengeProviderTimeout interface {
|
||||
ChallengeProvider
|
||||
// defined for the Provider.
|
||||
type ProviderTimeout interface {
|
||||
Provider
|
||||
Timeout() (timeout, interval time.Duration)
|
||||
}
|
25
vendor/github.com/xenolf/lego/challenge/resolver/errors.go
generated
vendored
Normal file
25
vendor/github.com/xenolf/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/xenolf/lego/challenge/resolver/prober.go
generated
vendored
Normal file
173
vendor/github.com/xenolf/lego/challenge/resolver/prober.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/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)
|
||||
}
|
||||
}
|
||||
}
|
154
vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
generated
vendored
Normal file
154
vendor/github.com/xenolf/lego/challenge/resolver/solver_manager.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/acme/api"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/challenge/http01"
|
||||
"github.com/xenolf/lego/challenge/tlsalpn01"
|
||||
"github.com/xenolf/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
|
||||
}
|
||||
|
||||
// After the path is sent, the ACME server will access our server.
|
||||
// Repeatedly check the server for an updated status on our request.
|
||||
for {
|
||||
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
valid, err := checkAuthorizationStatus(authz)
|
||||
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
|
||||
}
|
||||
time.Sleep(time.Duration(ra) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
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/xenolf/lego/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
Normal file
129
vendor/github.com/xenolf/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/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/acme/api"
|
||||
"github.com/xenolf/lego/certcrypto"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/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
|
||||
}
|
|
@ -1,49 +1,54 @@
|
|||
package acme
|
||||
package tlsalpn01
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/xenolf/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 TLSALPNProviderServer will default to
|
||||
// defaultTLSPort is the port that the ProviderServer will default to
|
||||
// when no other port is provided.
|
||||
defaultTLSPort = "443"
|
||||
)
|
||||
|
||||
// TLSALPNProviderServer implements ChallengeProvider for `TLS-ALPN-01`
|
||||
// challenge. It may be instantiated without using the NewTLSALPNProviderServer
|
||||
// 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 TLSALPNProviderServer struct {
|
||||
type ProviderServer struct {
|
||||
iface string
|
||||
port string
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
// NewTLSALPNProviderServer creates a new TLSALPNProviderServer 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 NewTLSALPNProviderServer(iface, port string) *TLSALPNProviderServer {
|
||||
return &TLSALPNProviderServer{iface: iface, port: port}
|
||||
// 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 (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
|
||||
if t.port == "" {
|
||||
// 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.
|
||||
t.port = defaultTLSPort
|
||||
s.port = defaultTLSPort
|
||||
}
|
||||
|
||||
// Generate the challenge certificate using the provided keyAuth and domain.
|
||||
cert, err := TLSALPNChallengeCert(domain, keyAuth)
|
||||
cert, err := ChallengeCert(domain, keyAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -59,15 +64,15 @@ func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
|
|||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||
|
||||
// Create the listener with the created tls.Config.
|
||||
t.listener, err = tls.Listen("tcp", net.JoinHostPort(t.iface, t.port), tlsConf)
|
||||
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(t.listener, nil)
|
||||
if err != nil {
|
||||
err := http.Serve(s.listener, nil)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
|
@ -76,13 +81,13 @@ func (t *TLSALPNProviderServer) Present(domain, token, keyAuth string) error {
|
|||
}
|
||||
|
||||
// CleanUp closes the HTTPS server.
|
||||
func (t *TLSALPNProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if t.listener == nil {
|
||||
func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||
if s.listener == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server was created, close it.
|
||||
if err := t.listener.Close(); err != nil && err != http.ErrServerClosed {
|
||||
if err := s.listener.Close(); err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
73
vendor/github.com/xenolf/lego/lego/client.go
generated
vendored
Normal file
73
vendor/github.com/xenolf/lego/lego/client.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
|||
package lego
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/xenolf/lego/acme/api"
|
||||
"github.com/xenolf/lego/certificate"
|
||||
"github.com/xenolf/lego/challenge/resolver"
|
||||
"github.com/xenolf/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)
|
||||
|
||||
return &Client{
|
||||
Certificate: certificate.NewCertifier(core, config.KeyType, prober),
|
||||
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
|
||||
}
|
96
vendor/github.com/xenolf/lego/lego/client_config.go
generated
vendored
Normal file
96
vendor/github.com/xenolf/lego/lego/client_config.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
package lego
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/certcrypto"
|
||||
"github.com/xenolf/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
|
||||
KeyType certcrypto.KeyType
|
||||
UserAgent string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func NewConfig(user registration.User) *Config {
|
||||
return &Config{
|
||||
CADirURL: LEDirectoryProduction,
|
||||
User: user,
|
||||
KeyType: certcrypto.RSA2048,
|
||||
HTTPClient: createDefaultHTTPClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package acme
|
||||
package wait
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,9 +7,9 @@ import (
|
|||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
// WaitFor polls the given function 'f', once every 'interval', up to 'timeout'.
|
||||
func WaitFor(timeout, interval time.Duration, f func() (bool, error)) error {
|
||||
log.Infof("Wait [timeout: %s, interval: %s]", timeout, interval)
|
||||
// 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)
|
63
vendor/github.com/xenolf/lego/providers/dns/acmedns/acmedns.go
generated
vendored
63
vendor/github.com/xenolf/lego/providers/dns/acmedns/acmedns.go
generated
vendored
|
@ -7,7 +7,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/cpu/goacmedns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -17,21 +17,19 @@ const (
|
|||
// 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 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.
|
||||
// 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 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 registers and returns a new account
|
||||
// with the given allowFrom restriction or returns an error.
|
||||
RegisterAccount([]string) (goacmedns.Account, error)
|
||||
}
|
||||
|
||||
|
@ -43,8 +41,7 @@ type DNSProvider struct {
|
|||
}
|
||||
|
||||
// NewDNSProvider creates an ACME-DNS provider using file based account storage.
|
||||
// Its configuration is loaded from the environment by reading apiBaseEnvVar and
|
||||
// storagePathEnvVar.
|
||||
// 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 {
|
||||
|
@ -56,8 +53,7 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderClient(client, storage)
|
||||
}
|
||||
|
||||
// NewDNSProviderClient creates an ACME-DNS DNSProvider with the given
|
||||
// acmeDNSClient and goacmedns.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")
|
||||
|
@ -76,8 +72,7 @@ func NewDNSProviderClient(client acmeDNSClient, storage goacmedns.Storage) (*DNS
|
|||
// 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.
|
||||
// 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
|
||||
|
@ -100,18 +95,16 @@ func (e ErrCNAMERequired) Error() string {
|
|||
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.
|
||||
// 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, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// Check if credentials were previously saved for this domain.
|
||||
account, err := d.storage.Fetch(domain)
|
||||
|
@ -132,15 +125,15 @@ func (d *DNSProvider) Present(domain, _, keyAuth string) error {
|
|||
// 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.
|
||||
// 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.
|
||||
// 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)
|
||||
|
@ -158,9 +151,9 @@ func (d *DNSProvider) register(domain, fqdn string) error {
|
|||
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.
|
||||
// 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,
|
||||
|
|
55
vendor/github.com/xenolf/lego/providers/dns/alidns/alidns.go
generated
vendored
55
vendor/github.com/xenolf/lego/providers/dns/alidns/alidns.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package alidns implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Alibaba Cloud DNS.
|
||||
// Package alidns implements a DNS provider for solving the DNS-01 challenge using Alibaba Cloud DNS.
|
||||
package alidns
|
||||
|
||||
import (
|
||||
|
@ -12,7 +11,7 @@ import (
|
|||
"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/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -33,8 +32,8 @@ type Config struct {
|
|||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("ALICLOUD_TTL", 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("ALICLOUD_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("ALICLOUD_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -61,18 +60,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for alidns.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiKey, secretKey, regionID string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = apiKey
|
||||
config.SecretKey = secretKey
|
||||
config.RegionID = regionID
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for alidns.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -106,7 +93,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
_, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
|
@ -124,7 +111,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
|
@ -149,19 +136,35 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
||||
request := alidns.CreateDescribeDomainsRequest()
|
||||
zones, err := d.client.DescribeDomains(request)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("API call failed: %v", err)
|
||||
|
||||
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 {
|
||||
break
|
||||
}
|
||||
|
||||
startPage++
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone alidns.Domain
|
||||
for _, zone := range zones.Domains.Domain {
|
||||
if zone.DomainName == acme.UnFqdn(authZone) {
|
||||
for _, zone := range domains {
|
||||
if zone.DomainName == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +212,7 @@ func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]alidns.Record, erro
|
|||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
|
|
30
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
30
vendor/github.com/xenolf/lego/providers/dns/auroradns/auroradns.go
generated
vendored
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ldez/go-auroradns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -28,8 +28,8 @@ type Config struct {
|
|||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("AURORA_TTL", 300),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AURORA_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AURORA_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AURORA_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AURORA_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,18 +58,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for AuroraDNS.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(baseURL string, userID string, key string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = baseURL
|
||||
config.UserID = userID
|
||||
config.Key = key
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for AuroraDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -103,9 +91,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a record with a secret
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("aurora: could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
@ -119,7 +107,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
subdomain := fqdn[0 : len(fqdn)-len(authZone)-1]
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
|
@ -147,7 +135,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes a given record that was generated by Present
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
d.recordIDsMu.Lock()
|
||||
recordID, ok := d.recordIDs[fqdn]
|
||||
|
@ -157,12 +145,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
return fmt.Errorf("unknown recordID for %q", fqdn)
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not determine zone for domain: %q. %v", domain, err)
|
||||
}
|
||||
|
||||
authZone = acme.UnFqdn(authZone)
|
||||
authZone = dns01.UnFqdn(authZone)
|
||||
|
||||
zone, err := d.getZoneInformationByName(authZone)
|
||||
if err != nil {
|
||||
|
|
67
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
67
vendor/github.com/xenolf/lego/providers/dns/azure/azure.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package azure implements a DNS provider for solving the DNS-01
|
||||
// challenge using azure DNS.
|
||||
// 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
|
||||
|
||||
|
@ -18,7 +17,7 @@ import (
|
|||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -72,20 +71,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for azure.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(clientID, clientSecret, subscriptionID, tenantID, resourceGroup string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.ClientID = clientID
|
||||
config.ClientSecret = clientSecret
|
||||
config.TenantID = tenantID
|
||||
config.SubscriptionID = subscriptionID
|
||||
config.ResourceGroup = resourceGroup
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Azure.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -128,8 +113,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
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.
|
||||
// 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
|
||||
}
|
||||
|
@ -137,7 +122,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
// 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, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneID(ctx, fqdn)
|
||||
if err != nil {
|
||||
|
@ -147,12 +132,38 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
|
||||
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: &[]dns.TxtRecord{{Value: &[]string{value}}},
|
||||
TxtRecords: &txtRecords,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -166,14 +177,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
ctx := context.Background()
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneID(ctx, fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("azure: %v", err)
|
||||
}
|
||||
|
||||
relative := toRelativeRecord(fqdn, acme.ToFqdn(zone))
|
||||
relative := toRelativeRecord(fqdn, dns01.ToFqdn(zone))
|
||||
rsc := dns.NewRecordSetsClient(d.config.SubscriptionID)
|
||||
rsc.Authorizer = d.authorizer
|
||||
|
||||
|
@ -186,7 +197,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
// Checks that azure has a zone for this domain name.
|
||||
func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -194,7 +205,7 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string,
|
|||
dc := dns.NewZonesClient(d.config.SubscriptionID)
|
||||
dc.Authorizer = d.authorizer
|
||||
|
||||
zone, err := dc.Get(ctx, d.config.ResourceGroup, acme.UnFqdn(authZone))
|
||||
zone, err := dc.Get(ctx, d.config.ResourceGroup, dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -205,7 +216,7 @@ func (d *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string,
|
|||
|
||||
// Returns the relative record to the domain
|
||||
func toRelativeRecord(domain, zone string) string {
|
||||
return acme.UnFqdn(strings.TrimSuffix(domain, zone))
|
||||
return dns01.UnFqdn(strings.TrimSuffix(domain, zone))
|
||||
}
|
||||
|
||||
func getAuthorizer(config *Config) (autorest.Authorizer, error) {
|
||||
|
@ -259,5 +270,5 @@ func getMetadata(config *Config, field string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
return string(respBody[:]), nil
|
||||
return string(respBody), nil
|
||||
}
|
||||
|
|
270
vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go
generated
vendored
270
vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go
generated
vendored
|
@ -1,27 +1,25 @@
|
|||
// Package bluecat implements a DNS provider for solving the DNS-01 challenge
|
||||
// using a self-hosted Bluecat Address Manager.
|
||||
// Package bluecat implements a DNS provider for solving the DNS-01 challenge using a self-hosted Bluecat Address Manager.
|
||||
package bluecat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const configType = "Configuration"
|
||||
const viewType = "View"
|
||||
const txtType = "TXTRecord"
|
||||
const zoneType = "Zone"
|
||||
const (
|
||||
configType = "Configuration"
|
||||
viewType = "View"
|
||||
zoneType = "Zone"
|
||||
txtType = "TXTRecord"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
|
@ -39,9 +37,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("BLUECAT_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("BLUECAT_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("BLUECAT_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -58,8 +56,8 @@ type DNSProvider struct {
|
|||
// 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
|
||||
// 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 {
|
||||
|
@ -76,24 +74,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Bluecat DNS.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(baseURL, userName, password, configName, dnsView string, httpClient *http.Client) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = baseURL
|
||||
config.UserName = userName
|
||||
config.Password = password
|
||||
config.ConfigName = configName
|
||||
config.DNSView = dnsView
|
||||
|
||||
if httpClient != nil {
|
||||
config.HTTPClient = httpClient
|
||||
}
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Bluecat DNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -111,7 +91,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
// 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, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
|
@ -162,7 +142,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
|
@ -219,223 +199,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 == 404 {
|
||||
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
|
||||
}
|
||||
|
|
233
vendor/github.com/xenolf/lego/providers/dns/bluecat/client.go
generated
vendored
233
vendor/github.com/xenolf/lego/providers/dns/bluecat/client.go
generated
vendored
|
@ -1,5 +1,16 @@
|
|||
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"`
|
||||
|
@ -14,3 +25,225 @@ type entityResponse struct {
|
|||
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
|
||||
}
|
||||
|
|
38
vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
38
vendor/github.com/xenolf/lego/providers/dns/cloudflare/cloudflare.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package cloudflare implements a DNS provider for solving the DNS-01
|
||||
// challenge using cloudflare DNS.
|
||||
// Package cloudflare implements a DNS provider for solving the DNS-01 challenge using cloudflare DNS.
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
|
@ -9,14 +8,11 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// CloudFlareAPIURL represents the API endpoint to call.
|
||||
const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4" // Deprecated
|
||||
|
||||
const (
|
||||
minTTL = 120
|
||||
)
|
||||
|
@ -67,17 +63,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Cloudflare.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(email, key string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.AuthEmail = email
|
||||
config.AuthKey = key
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Cloudflare.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -93,9 +78,6 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: must be remove. keep only for compatibility reason.
|
||||
client.BaseURL = CloudFlareAPIURL
|
||||
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
|
@ -107,21 +89,21 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(acme.UnFqdn(authZone))
|
||||
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: acme.UnFqdn(fqdn),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
@ -142,21 +124,21 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(acme.UnFqdn(authZone))
|
||||
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: acme.UnFqdn(fqdn),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
}
|
||||
|
||||
records, err := d.client.DNSRecords(zoneID, dnsRecord)
|
||||
|
|
37
vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
37
vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package cloudxns implements a DNS provider for solving the DNS-01 challenge
|
||||
// using CloudXNS DNS.
|
||||
// Package cloudxns implements a DNS provider for solving the DNS-01 challenge using CloudXNS DNS.
|
||||
package cloudxns
|
||||
|
||||
import (
|
||||
|
@ -8,8 +7,9 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"github.com/xenolf/lego/providers/dns/cloudxns/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
|
@ -24,21 +24,20 @@ type Config struct {
|
|||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
client := acme.HTTPClient
|
||||
client.Timeout = time.Second * time.Duration(env.GetOrDefaultInt("CLOUDXNS_HTTP_TIMEOUT", 30))
|
||||
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CLOUDXNS_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("CLOUDXNS_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("CLOUDXNS_TTL", 120),
|
||||
HTTPClient: &client,
|
||||
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: time.Second * time.Duration(env.GetOrDefaultInt("CLOUDXNS_HTTP_TIMEOUT", 30)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *Client
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for CloudXNS.
|
||||
|
@ -50,15 +49,9 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return nil, fmt.Errorf("CloudXNS: %v", err)
|
||||
}
|
||||
|
||||
return NewDNSProviderCredentials(values["CLOUDXNS_API_KEY"], values["CLOUDXNS_SECRET_KEY"])
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials to return a
|
||||
// DNSProvider instance configured for CloudXNS.
|
||||
func NewDNSProviderCredentials(apiKey, secretKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = apiKey
|
||||
config.SecretKey = secretKey
|
||||
config.APIKey = values["CLOUDXNS_API_KEY"]
|
||||
config.SecretKey = values["CLOUDXNS_SECRET_KEY"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
@ -69,7 +62,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return nil, errors.New("CloudXNS: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
client, err := NewClient(config.APIKey, config.SecretKey)
|
||||
client, err := internal.NewClient(config.APIKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -81,7 +74,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
info, err := d.client.GetDomainInformation(fqdn)
|
||||
if err != nil {
|
||||
|
@ -93,7 +86,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
info, err := d.client.GetDomainInformation(fqdn)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package cloudxns
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -12,7 +12,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://www.cloudxns.net/api2/"
|
||||
|
@ -70,7 +70,7 @@ type Client struct {
|
|||
|
||||
// GetDomainInformation Get domain name information for a FQDN
|
||||
func (c *Client) GetDomainInformation(fqdn string) (*Data, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func (c *Client) FindTxtRecord(zoneID, fqdn string) (*TXTRecord, error) {
|
|||
}
|
||||
|
||||
for _, record := range records {
|
||||
if record.Host == acme.UnFqdn(fqdn) && record.Type == "TXT" {
|
||||
if record.Host == dns01.UnFqdn(fqdn) && record.Type == "TXT" {
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ func (c *Client) AddTxtRecord(info *Data, fqdn, value string, ttl int) error {
|
|||
|
||||
payload := TXTRecord{
|
||||
ID: id,
|
||||
Host: acme.UnFqdn(strings.TrimSuffix(fqdn, info.Domain)),
|
||||
Host: dns01.UnFqdn(strings.TrimSuffix(fqdn, info.Domain)),
|
||||
Value: value,
|
||||
Type: "TXT",
|
||||
LineID: 1,
|
28
vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go
generated
vendored
28
vendor/github.com/xenolf/lego/providers/dns/conoha/conoha.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package conoha implements a DNS provider for solving the DNS-01 challenge
|
||||
// using ConoHa DNS.
|
||||
// Package conoha implements a DNS provider for solving the DNS-01 challenge using ConoHa DNS.
|
||||
package conoha
|
||||
|
||||
import (
|
||||
|
@ -8,8 +7,9 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"github.com/xenolf/lego/providers/dns/conoha/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
|
@ -29,8 +29,8 @@ func NewDefaultConfig() *Config {
|
|||
return &Config{
|
||||
Region: env.GetOrDefaultString("CONOHA_REGION", "tyo1"),
|
||||
TTL: env.GetOrDefaultInt("CONOHA_TTL", 60),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("CONOHA_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("CONOHA_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ func NewDefaultConfig() *Config {
|
|||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *Client
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for ConoHa DNS.
|
||||
|
@ -69,15 +69,15 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return nil, errors.New("conoha: some credentials information are missing")
|
||||
}
|
||||
|
||||
auth := Auth{
|
||||
auth := internal.Auth{
|
||||
TenantID: config.TenantID,
|
||||
PasswordCredentials: PasswordCredentials{
|
||||
PasswordCredentials: internal.PasswordCredentials{
|
||||
Username: config.Username,
|
||||
Password: config.Password,
|
||||
},
|
||||
}
|
||||
|
||||
client, err := NewClient(config.Region, auth, config.HTTPClient)
|
||||
client, err := internal.NewClient(config.Region, auth, config.HTTPClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("conoha: failed to create client: %v", err)
|
||||
}
|
||||
|
@ -87,9 +87,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
return fmt.Errorf("conoha: failed to get domain ID: %v", err)
|
||||
}
|
||||
|
||||
record := Record{
|
||||
record := internal.Record{
|
||||
Name: fqdn,
|
||||
Type: "TXT",
|
||||
Data: value,
|
||||
|
@ -116,9 +116,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp clears ConoHa DNS TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package conoha
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
136
vendor/github.com/xenolf/lego/providers/dns/digitalocean/client.go
generated
vendored
136
vendor/github.com/xenolf/lego/providers/dns/digitalocean/client.go
generated
vendored
|
@ -1,26 +1,132 @@
|
|||
package digitalocean
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.digitalocean.com"
|
||||
|
||||
// txtRecordRequest represents the request body to DO's API to make a TXT record
|
||||
type txtRecordRequest struct {
|
||||
RecordType string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
TTL int `json:"ttl"`
|
||||
}
|
||||
|
||||
// txtRecordResponse represents a response from DO's API after making a TXT record
|
||||
type txtRecordResponse struct {
|
||||
DomainRecord struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Data string `json:"data"`
|
||||
} `json:"domain_record"`
|
||||
DomainRecord record `json:"domain_record"`
|
||||
}
|
||||
|
||||
type digitalOceanAPIError struct {
|
||||
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))
|
||||
}
|
||||
|
|
122
vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
122
vendor/github.com/xenolf/lego/providers/dns/digitalocean/digitalocean.go
generated
vendored
|
@ -1,19 +1,14 @@
|
|||
// Package digitalocean implements a DNS provider for solving the DNS-01
|
||||
// challenge using digitalocean DNS.
|
||||
// Package digitalocean implements a DNS provider for solving the DNS-01 challenge using digitalocean DNS.
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -63,16 +58,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Digital Ocean.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiAuthToken string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.AuthToken = apiAuthToken
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Digital Ocean.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -101,7 +86,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
respData, err := d.addTxtRecord(domain, fqdn, value)
|
||||
if err != nil {
|
||||
|
@ -117,7 +102,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
|
@ -139,102 +124,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) removeTxtRecord(domain string, recordID int) error {
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
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, acme.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 := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not determine zone for domain: '%s'. %s", domain, err)
|
||||
}
|
||||
|
||||
reqData := txtRecordRequest{RecordType: "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, acme.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 digitalOceanAPIError
|
||||
err = json.Unmarshal(content, &errInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("digitalOceanAPIError 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))
|
||||
}
|
||||
|
|
7
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
7
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
|
@ -3,7 +3,8 @@ package dns
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/providers/dns/acmedns"
|
||||
"github.com/xenolf/lego/providers/dns/alidns"
|
||||
"github.com/xenolf/lego/providers/dns/auroradns"
|
||||
|
@ -56,7 +57,7 @@ import (
|
|||
)
|
||||
|
||||
// NewDNSChallengeProviderByName Factory for DNS providers
|
||||
func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error) {
|
||||
func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) {
|
||||
switch name {
|
||||
case "acme-dns":
|
||||
return acmedns.NewDNSProvider()
|
||||
|
@ -119,7 +120,7 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
|
|||
case "linodev4":
|
||||
return linodev4.NewDNSProvider()
|
||||
case "manual":
|
||||
return acme.NewDNSProviderManual()
|
||||
return dns01.NewDNSProviderManual()
|
||||
case "mydnsjp":
|
||||
return mydnsjp.NewDNSProvider()
|
||||
case "namecheap":
|
||||
|
|
38
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
38
vendor/github.com/xenolf/lego/providers/dns/dnsimple/dnsimple.go
generated
vendored
|
@ -1,8 +1,8 @@
|
|||
// Package dnsimple implements a DNS provider for solving the DNS-01 challenge
|
||||
// using dnsimple DNS.
|
||||
// Package dnsimple implements a DNS provider for solving the DNS-01 challenge using dnsimple DNS.
|
||||
package dnsimple
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
@ -10,8 +10,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
|
@ -26,9 +27,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSIMPLE_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSIMPLE_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSIMPLE_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("DNSIMPLE_TTL", dns01.DefaultTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSIMPLE_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSIMPLE_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,17 +51,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for DNSimple.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(accessToken, baseURL string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.AccessToken = accessToken
|
||||
config.BaseURL = baseURL
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DNSimple.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -71,8 +61,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return nil, fmt.Errorf("dnsimple: OAuth token is missing")
|
||||
}
|
||||
|
||||
client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(config.AccessToken))
|
||||
client.UserAgent = acme.UserAgent
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: config.AccessToken})
|
||||
client := dnsimple.NewClient(oauth2.NewClient(context.Background(), ts))
|
||||
|
||||
if config.BaseURL != "" {
|
||||
client.BaseURL = config.BaseURL
|
||||
|
@ -83,7 +73,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
|
@ -106,7 +96,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
|
@ -136,7 +126,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
}
|
||||
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -146,7 +136,7 @@ func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
zoneName := acme.UnFqdn(authZone)
|
||||
zoneName := dns01.UnFqdn(authZone)
|
||||
|
||||
zones, err := d.client.Zones.ListZones(accountID, &dnsimple.ZoneListOptions{NameLike: zoneName})
|
||||
if err != nil {
|
||||
|
@ -200,7 +190,7 @@ func newTxtRecord(zoneName, fqdn, value string, ttl int) dnsimple.ZoneRecord {
|
|||
}
|
||||
|
||||
func extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
|
|
35
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
35
vendor/github.com/xenolf/lego/providers/dns/dnsmadeeasy/dnsmadeeasy.go
generated
vendored
|
@ -9,8 +9,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"github.com/xenolf/lego/providers/dns/dnsmadeeasy/internal"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
|
@ -28,9 +29,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSMADEEASY_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSMADEEASY_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSMADEEASY_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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{
|
||||
|
@ -44,7 +45,7 @@ func NewDefaultConfig() *Config {
|
|||
// DNSMadeEasy's DNS API to manage TXT records for a domain.
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
client *Client
|
||||
client *internal.Client
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for DNSMadeEasy DNS.
|
||||
|
@ -64,18 +65,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for DNS Made Easy.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(baseURL, apiKey, apiSecret string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.BaseURL = baseURL
|
||||
config.APIKey = apiKey
|
||||
config.APISecret = apiSecret
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DNS Made Easy.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -93,7 +82,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
}
|
||||
}
|
||||
|
||||
client, err := NewClient(config.APIKey, config.APISecret)
|
||||
client, err := internal.NewClient(config.APIKey, config.APISecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dnsmadeeasy: %v", err)
|
||||
}
|
||||
|
@ -109,9 +98,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domainName, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
@ -124,7 +113,7 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
|
||||
// create the TXT record
|
||||
name := strings.Replace(fqdn, "."+authZone, "", 1)
|
||||
record := &Record{Type: "TXT", Name: name, Value: value, TTL: d.config.TTL}
|
||||
record := &internal.Record{Type: "TXT", Name: name, Value: value, TTL: d.config.TTL}
|
||||
|
||||
err = d.client.CreateRecord(domain, record)
|
||||
if err != nil {
|
||||
|
@ -135,9 +124,9 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT records matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domainName, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dnsmadeeasy: unable to find zone for %s: %v", fqdn, err)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package dnsmadeeasy
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
29
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
29
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package dnspod implements a DNS provider for solving the DNS-01 challenge
|
||||
// using dnspod DNS.
|
||||
// Package dnspod implements a DNS provider for solving the DNS-01 challenge using dnspod DNS.
|
||||
package dnspod
|
||||
|
||||
import (
|
||||
|
@ -11,7 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/decker502/dnspod-go"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -28,8 +27,8 @@ type Config struct {
|
|||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DNSPOD_TTL", 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DNSPOD_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DNSPOD_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -56,16 +55,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for dnspod.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.LoginToken = key
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for dnspod.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -86,7 +75,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zoneID, zoneName, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -103,7 +92,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.findTxtRecords(domain, fqdn)
|
||||
if err != nil {
|
||||
|
@ -136,14 +125,14 @@ func (d *DNSProvider) getHostedZone(domain string) (string, string, error) {
|
|||
return "", "", fmt.Errorf("API call failed: %v", err)
|
||||
}
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var hostedZone dnspod.Domain
|
||||
for _, zone := range zones {
|
||||
if zone.Name == acme.UnFqdn(authZone) {
|
||||
if zone.Name == dns01.UnFqdn(authZone) {
|
||||
hostedZone = zone
|
||||
}
|
||||
}
|
||||
|
@ -192,7 +181,7 @@ func (d *DNSProvider) findTxtRecords(domain, fqdn string) ([]dnspod.Record, erro
|
|||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
|
|
10
vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
10
vendor/github.com/xenolf/lego/providers/dns/dreamhost/dreamhost.go
generated
vendored
|
@ -9,7 +9,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -72,8 +72,8 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
record := acme.UnFqdn(fqdn)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
record := dns01.UnFqdn(fqdn)
|
||||
|
||||
u, err := d.buildQuery(cmdAddRecord, record, value)
|
||||
if err != nil {
|
||||
|
@ -89,8 +89,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp clears DreamHost TXT record
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
record := acme.UnFqdn(fqdn)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
record := dns01.UnFqdn(fqdn)
|
||||
|
||||
u, err := d.buildQuery(cmdRemoveRecord, record, value)
|
||||
if err != nil {
|
||||
|
|
68
vendor/github.com/xenolf/lego/providers/dns/duckdns/client.go
generated
vendored
Normal file
68
vendor/github.com/xenolf/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/miekg/dns"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
)
|
||||
|
||||
// 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/xenolf/lego/providers/dns/duckdns/duckdns.go
generated
vendored
89
vendor/github.com/xenolf/lego/providers/dns/duckdns/duckdns.go
generated
vendored
|
@ -5,15 +5,10 @@ package duckdns
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -22,18 +17,19 @@ 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 {
|
||||
client := acme.HTTPClient
|
||||
client.Timeout = env.GetOrDefaultSecond("DUCKDNS_HTTP_TIMEOUT", 30*time.Second)
|
||||
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DUCKDNS_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DUCKDNS_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
HTTPClient: &client,
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,16 +52,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for http://duckdns.org
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(token string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.Token = token
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for DuckDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -81,13 +67,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, txtRecord, _ := acme.DNS01Record(domain, keyAuth)
|
||||
return updateTxtRecord(domain, d.config.Token, txtRecord, false)
|
||||
_, 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 updateTxtRecord(domain, d.config.Token, "", true)
|
||||
return d.updateTxtRecord(domain, d.config.Token, "", true)
|
||||
}
|
||||
|
||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||
|
@ -96,53 +82,8 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// 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 updateTxtRecord(domain, token, txt string, clear bool) error {
|
||||
u, _ := url.Parse("https://www.duckdns.org/update")
|
||||
|
||||
query := u.Query()
|
||||
query.Set("domains", getMainDomain(domain))
|
||||
query.Set("token", token)
|
||||
query.Set("clear", strconv.FormatBool(clear))
|
||||
query.Set("txt", txt)
|
||||
u.RawQuery = query.Encode()
|
||||
|
||||
response, err := acme.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 = acme.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]:]
|
||||
// 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
|
||||
}
|
||||
|
|
115
vendor/github.com/xenolf/lego/providers/dns/dyn/client.go
generated
vendored
115
vendor/github.com/xenolf/lego/providers/dns/dyn/client.go
generated
vendored
|
@ -1,6 +1,11 @@
|
|||
package dyn
|
||||
|
||||
import "encoding/json"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://api.dynect.net/REST"
|
||||
|
||||
|
@ -18,7 +23,7 @@ type dynResponse struct {
|
|||
Messages json.RawMessage `json:"msgs"`
|
||||
}
|
||||
|
||||
type creds struct {
|
||||
type credentials struct {
|
||||
Customer string `json:"customer_name"`
|
||||
User string `json:"user_name"`
|
||||
Pass string `json:"password"`
|
||||
|
@ -33,3 +38,109 @@ 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
|
||||
}
|
||||
|
|
139
vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go
generated
vendored
139
vendor/github.com/xenolf/lego/providers/dns/dyn/dyn.go
generated
vendored
|
@ -1,17 +1,14 @@
|
|||
// Package dyn implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Dyn Managed DNS.
|
||||
// Package dyn implements a DNS provider for solving the DNS-01 challenge using Dyn Managed DNS.
|
||||
package dyn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -29,9 +26,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("DYN_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("DYN_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("DYN_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -62,18 +59,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Dyn DNS.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(customerName, userName, password string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.CustomerName = customerName
|
||||
config.UserName = userName
|
||||
config.Password = password
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Dyn DNS
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -89,9 +74,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
@ -124,9 +109,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dyn: %v", err)
|
||||
}
|
||||
|
@ -170,109 +155,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// 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 := &creds{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
|
||||
}
|
||||
|
|
42
vendor/github.com/xenolf/lego/providers/dns/exec/doc.go
generated
vendored
42
vendor/github.com/xenolf/lego/providers/dns/exec/doc.go
generated
vendored
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
Package exec implements a manual DNS provider which runs a program for adding/removing the DNS record.
|
||||
|
||||
The file name of the external program is specified in the environment variable `EXEC_PATH`.
|
||||
When it is run by lego, three command-line parameters are passed to it:
|
||||
The action ("present" or "cleanup"), the fully-qualified domain name, the value for the record and the TTL.
|
||||
|
||||
For example, requesting a certificate for the domain 'foo.example.com' can be achieved by calling lego as follows:
|
||||
|
||||
EXEC_PATH=./update-dns.sh \
|
||||
lego --dns exec \
|
||||
--domains foo.example.com \
|
||||
--email invalid@example.com run
|
||||
|
||||
It will then call the program './update-dns.sh' with like this:
|
||||
|
||||
./update-dns.sh "present" "_acme-challenge.foo.example.com." "MsijOYZxqyjGnFGwhjrhfg-Xgbl5r68WPda0J9EgqqI" "120"
|
||||
|
||||
The program then needs to make sure the record is inserted.
|
||||
When it returns an error via a non-zero exit code, lego aborts.
|
||||
|
||||
When the record is to be removed again,
|
||||
the program is called with the first command-line parameter set to "cleanup" instead of "present".
|
||||
|
||||
If you want to use the raw domain, token, and keyAuth values with your program, you can set `EXEC_MODE=RAW`:
|
||||
|
||||
EXEC_MODE=RAW \
|
||||
EXEC_PATH=./update-dns.sh \
|
||||
lego --dns exec \
|
||||
--domains foo.example.com \
|
||||
--email invalid@example.com run
|
||||
|
||||
It will then call the program './update-dns.sh' like this:
|
||||
|
||||
./update-dns.sh "present" "foo.example.com." "--" "some-token" "KxAy-J3NwUmg9ZQuM-gP_Mq1nStaYSaP9tYQs5_-YsE.ksT-qywTd8058G-SHHWA3RAN72Pr0yWtPYmmY5UBpQ8"
|
||||
|
||||
NOTE:
|
||||
The `--` is because the token MAY start with a `-`, and the called program may try and interpret a - as indicating a flag.
|
||||
In the case of urfave, which is commonly used,
|
||||
you can use the `--` delimiter to specify the start of positional arguments, and handle such a string safely.
|
||||
*/
|
||||
package exec
|
53
vendor/github.com/xenolf/lego/providers/dns/exec/exec.go
generated
vendored
53
vendor/github.com/xenolf/lego/providers/dns/exec/exec.go
generated
vendored
|
@ -1,3 +1,4 @@
|
|||
// Package exec implements a DNS provider which runs a program for adding/removing the DNS record.
|
||||
package exec
|
||||
|
||||
import (
|
||||
|
@ -5,17 +6,27 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
// Config Provider configuration.
|
||||
type Config struct {
|
||||
Program string
|
||||
Mode string
|
||||
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
|
||||
|
@ -32,10 +43,11 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return nil, fmt.Errorf("exec: %v", err)
|
||||
}
|
||||
|
||||
return NewDNSProviderConfig(&Config{
|
||||
Program: values["EXEC_PATH"],
|
||||
Mode: os.Getenv("EXEC_MODE"),
|
||||
})
|
||||
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
|
||||
|
@ -48,25 +60,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
// NewDNSProviderProgram returns a new DNS provider which runs the given program
|
||||
// for adding and removing the DNS record.
|
||||
// Deprecated: use NewDNSProviderConfig instead
|
||||
func NewDNSProviderProgram(program string) (*DNSProvider, error) {
|
||||
if len(program) == 0 {
|
||||
return nil, errors.New("the program is undefined")
|
||||
}
|
||||
|
||||
return NewDNSProviderConfig(&Config{Program: program})
|
||||
}
|
||||
|
||||
// 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, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
args = []string{"present", fqdn, value, strconv.Itoa(ttl)}
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
args = []string{"present", fqdn, value}
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.config.Program, args...)
|
||||
|
@ -85,8 +86,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
if d.config.Mode == "RAW" {
|
||||
args = []string{"cleanup", "--", domain, token, keyAuth}
|
||||
} else {
|
||||
fqdn, value, ttl := acme.DNS01Record(domain, keyAuth)
|
||||
args = []string{"cleanup", fqdn, value, strconv.Itoa(ttl)}
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
args = []string{"cleanup", fqdn, value}
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.config.Program, args...)
|
||||
|
@ -98,3 +99,9 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
33
vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
generated
vendored
33
vendor/github.com/xenolf/lego/providers/dns/exoscale/exoscale.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package exoscale implements a DNS provider for solving the DNS-01 challenge
|
||||
// using exoscale DNS.
|
||||
// Package exoscale implements a DNS provider for solving the DNS-01 challenge using exoscale DNS.
|
||||
package exoscale
|
||||
|
||||
import (
|
||||
|
@ -9,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/exoscale/egoscale"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -29,9 +28,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("EXOSCALE_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("EXOSCALE_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("EXOSCALE_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -60,18 +59,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderClient Uses the supplied parameters
|
||||
// to return a DNSProvider instance configured for Exoscale.
|
||||
// Deprecated
|
||||
func NewDNSProviderClient(key, secret, endpoint string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = key
|
||||
config.APISecret = secret
|
||||
config.Endpoint = endpoint
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Exoscale.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -94,7 +81,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -137,7 +124,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
zone, recordName, err := d.FindZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -181,12 +168,12 @@ func (d *DNSProvider) FindExistingRecordID(zone, recordName string) (int64, erro
|
|||
|
||||
// FindZoneAndRecordName Extract DNS zone and DNS entry name
|
||||
func (d *DNSProvider) FindZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = acme.UnFqdn(zone)
|
||||
name := acme.UnFqdn(fqdn)
|
||||
zone = dns01.UnFqdn(zone)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
|
|
35
vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
generated
vendored
35
vendor/github.com/xenolf/lego/providers/dns/fastdns/fastdns.go
generated
vendored
|
@ -1,3 +1,4 @@
|
|||
// Package fastdns implements a DNS provider for solving the DNS-01 challenge using FastDNS.
|
||||
package fastdns
|
||||
|
||||
import (
|
||||
|
@ -8,7 +9,7 @@ import (
|
|||
|
||||
configdns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v1"
|
||||
"github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -23,9 +24,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AKAMAI_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AKAMAI_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("AKAMAI_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("AKAMAI_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("AKAMAI_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
TTL: env.GetOrDefaultInt("AKAMAI_TTL", dns01.DefaultTTL),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,22 +55,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderClient uses the supplied parameters
|
||||
// to return a DNSProvider instance configured for FastDNS.
|
||||
// Deprecated
|
||||
func NewDNSProviderClient(host, clientToken, clientSecret, accessToken string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.Config = edgegrid.Config{
|
||||
Host: host,
|
||||
ClientToken: clientToken,
|
||||
ClientSecret: clientSecret,
|
||||
AccessToken: accessToken,
|
||||
MaxBody: 131072,
|
||||
}
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for FastDNS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -85,7 +70,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fullfil the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
|
@ -121,7 +106,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
zoneName, recordName, err := d.findZoneAndRecordName(fqdn, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastdns: %v", err)
|
||||
|
@ -154,12 +139,12 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
}
|
||||
|
||||
func (d *DNSProvider) findZoneAndRecordName(fqdn, domain string) (string, string, error) {
|
||||
zone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
zone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
zone = acme.UnFqdn(zone)
|
||||
name := acme.UnFqdn(fqdn)
|
||||
zone = dns01.UnFqdn(zone)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
name = name[:len(name)-len("."+zone)]
|
||||
|
||||
return zone, name, nil
|
||||
|
|
224
vendor/github.com/xenolf/lego/providers/dns/gandi/client.go
generated
vendored
224
vendor/github.com/xenolf/lego/providers/dns/gandi/client.go
generated
vendored
|
@ -1,8 +1,11 @@
|
|||
package gandi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// types for XML-RPC method calls and parameters
|
||||
|
@ -90,3 +93,224 @@ type rpcError struct {
|
|||
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
|
||||
}
|
||||
|
|
255
vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go
generated
vendored
255
vendor/github.com/xenolf/lego/providers/dns/gandi/gandi.go
generated
vendored
|
@ -1,20 +1,15 @@
|
|||
// Package gandi implements a DNS provider for solving the DNS-01
|
||||
// challenge using Gandi DNS.
|
||||
// Package gandi implements a DNS provider for solving the DNS-01 challenge using Gandi DNS.
|
||||
package gandi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -27,10 +22,6 @@ const (
|
|||
minTTL = 300
|
||||
)
|
||||
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn.
|
||||
// It is overridden during tests.
|
||||
var findZoneByFqdn = acme.FindZoneByFqdn
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
|
@ -68,6 +59,8 @@ type DNSProvider struct {
|
|||
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.
|
||||
|
@ -84,16 +77,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Gandi.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = apiKey
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -112,6 +95,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
config: config,
|
||||
inProgressFQDNs: make(map[string]inProgressInfo),
|
||||
inProgressAuthZones: make(map[string]struct{}),
|
||||
findZoneByFqdn: dns01.FindZoneByFqdn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -119,14 +103,14 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
// 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, _ := acme.DNS01Record(domain, keyAuth)
|
||||
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 := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := d.findZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandi: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
@ -154,7 +138,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// perform API actions to create and activate new gandi zone
|
||||
// containing the required TXT record
|
||||
newZoneName := fmt.Sprintf("%s [ACME Challenge %s]", acme.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
|
||||
newZoneName := fmt.Sprintf("%s [ACME Challenge %s]", dns01.UnFqdn(authZone), time.Now().Format(time.RFC822Z))
|
||||
|
||||
newZoneID, err := d.cloneZone(zoneID, newZoneName)
|
||||
if err != nil {
|
||||
|
@ -196,7 +180,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
// 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, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve zoneID, newZoneID and authZone
|
||||
d.inProgressMu.Lock()
|
||||
|
@ -228,224 +212,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
76
vendor/github.com/xenolf/lego/providers/dns/gandiv5/client.go
generated
vendored
76
vendor/github.com/xenolf/lego/providers/dns/gandiv5/client.go
generated
vendored
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
const apiKeyHeader = "X-Api-Key"
|
||||
|
@ -24,6 +26,80 @@ type Record struct {
|
|||
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)
|
||||
|
||||
|
|
109
vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
109
vendor/github.com/xenolf/lego/providers/dns/gandiv5/gandiv5.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package gandiv5 implements a DNS provider for solving the DNS-01
|
||||
// challenge using Gandi LiveDNS api.
|
||||
// Package gandiv5 implements a DNS provider for solving the DNS-01 challenge using Gandi LiveDNS api.
|
||||
package gandiv5
|
||||
|
||||
import (
|
||||
|
@ -10,8 +9,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -23,10 +21,6 @@ const (
|
|||
minTTL = 300
|
||||
)
|
||||
|
||||
// findZoneByFqdn determines the DNS zone of an fqdn.
|
||||
// It is overridden during tests.
|
||||
var findZoneByFqdn = acme.FindZoneByFqdn
|
||||
|
||||
// inProgressInfo contains information about an in-progress challenge
|
||||
type inProgressInfo struct {
|
||||
fieldName string
|
||||
|
@ -62,6 +56,8 @@ 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.
|
||||
|
@ -78,16 +74,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Gandi.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = apiKey
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Gandi.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -109,15 +95,16 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
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, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// find authZone
|
||||
authZone, err := findZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := d.findZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandiv5: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
@ -135,7 +122,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
defer d.inProgressMu.Unlock()
|
||||
|
||||
// add TXT record into authZone
|
||||
err = d.addTXTRecord(acme.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
err = d.addTXTRecord(dns01.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -150,7 +137,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve authZone
|
||||
d.inProgressMu.Lock()
|
||||
|
@ -165,7 +152,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
delete(d.inProgressFQDNs, fqdn)
|
||||
|
||||
// delete TXT record from authZone
|
||||
err := d.deleteTXTRecord(acme.UnFqdn(authZone), fieldName)
|
||||
err := d.deleteTXTRecord(dns01.UnFqdn(authZone), fieldName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gandiv5: %v", err)
|
||||
}
|
||||
|
@ -178,79 +165,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// functions to perform API actions
|
||||
|
||||
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
|
||||
}
|
||||
|
|
131
vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go
generated
vendored
131
vendor/github.com/xenolf/lego/providers/dns/gcloud/googlecloud.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package gcloud implements a DNS provider for solving the DNS-01
|
||||
// challenge using Google Cloud DNS.
|
||||
// Package gcloud implements a DNS provider for solving the DNS-01 challenge using Google Cloud DNS.
|
||||
package gcloud
|
||||
|
||||
import (
|
||||
|
@ -9,17 +8,26 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"github.com/xenolf/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
|
||||
|
@ -30,7 +38,8 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("GCE_TTL", 120),
|
||||
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),
|
||||
}
|
||||
|
@ -124,7 +133,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
|
@ -132,11 +141,32 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
}
|
||||
|
||||
// Look for existing records.
|
||||
existing, err := d.findTxtRecords(zone, fqdn)
|
||||
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},
|
||||
|
@ -144,41 +174,74 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
Type: "TXT",
|
||||
}
|
||||
|
||||
change := &dns.Change{}
|
||||
|
||||
if len(existing) > 0 {
|
||||
// Attempt to delete the existing records when adding our new one.
|
||||
change.Deletions = existing
|
||||
|
||||
// Append existing TXT record data to the new TXT record data
|
||||
for _, value := range existing {
|
||||
rec.Rrdatas = append(rec.Rrdatas, value.Rrdatas...)
|
||||
// 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.Additions = []*dns.ResourceRecordSet{rec}
|
||||
change := &dns.Change{
|
||||
Additions: []*dns.ResourceRecordSet{rec},
|
||||
}
|
||||
|
||||
chg, err := d.client.Changes.Create(d.config.Project, zone, change).Do()
|
||||
if err != nil {
|
||||
if err = d.applyChanges(zone, change); err != nil {
|
||||
return fmt.Errorf("googlecloud: %v", err)
|
||||
}
|
||||
|
||||
// wait for change to be acknowledged
|
||||
for chg.Status == "pending" {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
chg, err = d.client.Changes.Get(d.config.Project, zone, chg.Id).Do()
|
||||
if 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, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(domain)
|
||||
if err != nil {
|
||||
|
@ -209,7 +272,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// getHostedZone returns the managed-zone
|
||||
func (d *DNSProvider) getHostedZone(domain string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(acme.ToFqdn(domain), acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -237,3 +300,11 @@ func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSe
|
|||
|
||||
return recs.Rrsets, nil
|
||||
}
|
||||
|
||||
func mustUnquote(raw string) string {
|
||||
clean, err := strconv.Unquote(raw)
|
||||
if err != nil {
|
||||
return raw
|
||||
}
|
||||
return clean
|
||||
}
|
||||
|
|
67
vendor/github.com/xenolf/lego/providers/dns/glesys/client.go
generated
vendored
67
vendor/github.com/xenolf/lego/providers/dns/glesys/client.go
generated
vendored
|
@ -1,5 +1,14 @@
|
|||
package glesys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/xenolf/lego/log"
|
||||
)
|
||||
|
||||
// types for JSON method calls, parameters, and responses
|
||||
|
||||
type addRecordRequest struct {
|
||||
|
@ -22,3 +31,61 @@ type responseStruct struct {
|
|||
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
|
||||
}
|
||||
|
|
87
vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go
generated
vendored
87
vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go
generated
vendored
|
@ -1,10 +1,7 @@
|
|||
// Package glesys implements a DNS provider for solving the DNS-01
|
||||
// challenge using GleSYS api.
|
||||
// Package glesys implements a DNS provider for solving the DNS-01 challenge using GleSYS api.
|
||||
package glesys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -12,8 +9,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -72,17 +68,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for GleSYS.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiUser string, apiKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = apiUser
|
||||
config.APIKey = apiKey
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for GleSYS.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -105,10 +90,10 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// find authZone
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("glesys: findZoneByFqdn failure: %v", err)
|
||||
}
|
||||
|
@ -126,7 +111,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
defer d.inProgressMu.Unlock()
|
||||
|
||||
// add TXT record into authZone
|
||||
recordID, err := d.addTXTRecord(domain, acme.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
recordID, err := d.addTXTRecord(domain, dns01.UnFqdn(authZone), name, value, d.config.TTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -138,7 +123,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// acquire lock and retrieve authZone
|
||||
d.inProgressMu.Lock()
|
||||
|
@ -161,63 +146,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// functions to perform API actions
|
||||
|
||||
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
|
||||
}
|
||||
|
|
53
vendor/github.com/xenolf/lego/providers/dns/godaddy/client.go
generated
vendored
Normal file
53
vendor/github.com/xenolf/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)
|
||||
}
|
92
vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go
generated
vendored
92
vendor/github.com/xenolf/lego/providers/dns/godaddy/godaddy.go
generated
vendored
|
@ -2,17 +2,13 @@
|
|||
package godaddy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -28,6 +24,7 @@ type Config struct {
|
|||
APISecret string
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
SequenceInterval time.Duration
|
||||
TTL int
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
@ -38,6 +35,7 @@ func NewDefaultConfig() *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),
|
||||
},
|
||||
|
@ -65,17 +63,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for godaddy.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiKey, apiSecret string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = apiKey
|
||||
config.APISecret = apiSecret
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for godaddy.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -99,17 +86,9 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
domainZone, err := d.getZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -128,30 +107,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
return d.updateRecords(rec, domainZone, recordName)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
domainZone, err := d.getZone(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -169,33 +127,25 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
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 := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return acme.UnFqdn(authZone), 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)
|
||||
}
|
||||
|
||||
// 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"`
|
||||
return dns01.UnFqdn(authZone), nil
|
||||
}
|
||||
|
|
52
vendor/github.com/xenolf/lego/providers/dns/hostingde/client.go
generated
vendored
52
vendor/github.com/xenolf/lego/providers/dns/hostingde/client.go
generated
vendored
|
@ -1,5 +1,16 @@
|
|||
package hostingde
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json"
|
||||
|
||||
// RecordsAddRequest represents a DNS record to add
|
||||
type RecordsAddRequest struct {
|
||||
Name string `json:"name"`
|
||||
|
@ -89,3 +100,44 @@ type ZoneUpdateRequest struct {
|
|||
RecordsToAdd []RecordsAddRequest `json:"recordsToAdd"`
|
||||
RecordsToDelete []RecordsDeleteRequest `json:"recordsToDelete"`
|
||||
}
|
||||
|
||||
func (d *DNSProvider) updateZone(updateRequest ZoneUpdateRequest) (*ZoneUpdateResponse, error) {
|
||||
body, err := json.Marshal(updateRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, defaultBaseURL+"/zoneUpdate", 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(req, content))
|
||||
}
|
||||
|
||||
// Everything looks good; but we'll need the ID later to delete the record
|
||||
updateResponse := &ZoneUpdateResponse{}
|
||||
err = json.Unmarshal(content, updateResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
if updateResponse.Status != "success" && updateResponse.Status != "pending" {
|
||||
return updateResponse, errors.New(toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
return updateResponse, 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))
|
||||
}
|
||||
|
|
63
vendor/github.com/xenolf/lego/providers/dns/hostingde/hostingde.go
generated
vendored
63
vendor/github.com/xenolf/lego/providers/dns/hostingde/hostingde.go
generated
vendored
|
@ -1,23 +1,17 @@
|
|||
// Package hostingde implements a DNS provider for solving the DNS-01
|
||||
// challenge using hosting.de.
|
||||
// Package hostingde implements a DNS provider for solving the DNS-01 challenge using hosting.de.
|
||||
package hostingde
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
const defaultBaseURL = "https://secure.hosting.de/api/dns/v1/json"
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
APIKey string
|
||||
|
@ -31,7 +25,7 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("HOSTINGDE_TTL", 120),
|
||||
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{
|
||||
|
@ -91,11 +85,11 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
rec := []RecordsAddRequest{{
|
||||
Type: "TXT",
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
TTL: d.config.TTL,
|
||||
}}
|
||||
|
@ -114,7 +108,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
}
|
||||
|
||||
for _, record := range resp.Response.Records {
|
||||
if record.Name == acme.UnFqdn(fqdn) && record.Content == fmt.Sprintf(`"%s"`, value) {
|
||||
if record.Name == dns01.UnFqdn(fqdn) && record.Content == fmt.Sprintf(`"%s"`, value) {
|
||||
d.recordIDsMu.Lock()
|
||||
d.recordIDs[fqdn] = record.ID
|
||||
d.recordIDsMu.Unlock()
|
||||
|
@ -130,7 +124,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
// get the record's unique ID from when we created it
|
||||
d.recordIDsMu.Lock()
|
||||
|
@ -142,7 +136,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
rec := []RecordsDeleteRequest{{
|
||||
Type: "TXT",
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Content: value,
|
||||
ID: recordID,
|
||||
}}
|
||||
|
@ -166,44 +160,3 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) updateZone(updateRequest ZoneUpdateRequest) (*ZoneUpdateResponse, error) {
|
||||
body, err := json.Marshal(updateRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, defaultBaseURL+"/zoneUpdate", 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(req, content))
|
||||
}
|
||||
|
||||
// Everything looks good; but we'll need the ID later to delete the record
|
||||
updateResponse := &ZoneUpdateResponse{}
|
||||
err = json.Unmarshal(content, updateResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %s", err, toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
if updateResponse.Status != "success" && updateResponse.Status != "pending" {
|
||||
return updateResponse, errors.New(toUnreadableBodyMessage(req, content))
|
||||
}
|
||||
|
||||
return updateResponse, 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))
|
||||
}
|
||||
|
|
10
vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
generated
vendored
10
vendor/github.com/xenolf/lego/providers/dns/httpreq/httpreq.go
generated
vendored
|
@ -12,7 +12,7 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -41,8 +41,8 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("HTTPREQ_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("HTTPREQ_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -109,7 +109,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
msg := &message{
|
||||
FQDN: fqdn,
|
||||
Value: value,
|
||||
|
@ -138,7 +138,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
msg := &message{
|
||||
FQDN: fqdn,
|
||||
Value: value,
|
||||
|
|
6
vendor/github.com/xenolf/lego/providers/dns/iij/iij.go
generated
vendored
6
vendor/github.com/xenolf/lego/providers/dns/iij/iij.go
generated
vendored
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"github.com/iij/doapi"
|
||||
"github.com/iij/doapi/protocol"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -73,7 +73,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.addTxtRecord(domain, value)
|
||||
if err != nil {
|
||||
|
@ -84,7 +84,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.deleteTxtRecord(domain, value)
|
||||
if err != nil {
|
||||
|
|
26
vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go
generated
vendored
26
vendor/github.com/xenolf/lego/providers/dns/inwx/inwx.go
generated
vendored
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/smueller18/goinwx"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
@ -25,8 +25,8 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
PropagationTimeout: env.GetOrDefaultSecond("INWX_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("INWX_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
@ -95,8 +95,8 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
}()
|
||||
|
||||
var request = &goinwx.NameserverRecordRequest{
|
||||
Domain: acme.UnFqdn(authZone),
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Domain: dns01.UnFqdn(authZone),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
Content: value,
|
||||
Ttl: d.config.TTL,
|
||||
|
@ -104,9 +104,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
_, err = d.client.Nameservers.CreateRecord(request)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
switch er := err.(type) {
|
||||
case *goinwx.ErrorResponse:
|
||||
if err.(*goinwx.ErrorResponse).Message == "Object exists" {
|
||||
if er.Message == "Object exists" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
|
@ -120,9 +120,9 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inwx: %v", err)
|
||||
}
|
||||
|
@ -140,8 +140,8 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
}()
|
||||
|
||||
response, err := d.client.Nameservers.Info(&goinwx.NameserverInfoRequest{
|
||||
Domain: acme.UnFqdn(authZone),
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Domain: dns01.UnFqdn(authZone),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Type: "TXT",
|
||||
})
|
||||
if err != nil {
|
||||
|
|
13
vendor/github.com/xenolf/lego/providers/dns/lightsail/lightsail.go
generated
vendored
13
vendor/github.com/xenolf/lego/providers/dns/lightsail/lightsail.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package lightsail implements a DNS provider for solving the DNS-01 challenge
|
||||
// using AWS Lightsail DNS.
|
||||
// Package lightsail implements a DNS provider for solving the DNS-01 challenge using AWS Lightsail DNS.
|
||||
package lightsail
|
||||
|
||||
import (
|
||||
|
@ -13,7 +12,7 @@ import (
|
|||
"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/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -54,8 +53,8 @@ type Config struct {
|
|||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
DNSZone: env.GetOrFile("DNS_ZONE"),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("LIGHTSAIL_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("LIGHTSAIL_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("LIGHTSAIL_PROPAGATION_TIMEOUT", dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("LIGHTSAIL_POLLING_INTERVAL", dns01.DefaultPollingInterval),
|
||||
Region: env.GetOrDefaultString("LIGHTSAIL_REGION", "us-east-1"),
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +104,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.newTxtRecord(fqdn, `"`+value+`"`)
|
||||
if err != nil {
|
||||
|
@ -116,7 +115,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
params := &lightsail.DeleteDomainEntryInput{
|
||||
DomainName: aws.String(d.config.DNSZone),
|
||||
|
|
25
vendor/github.com/xenolf/lego/providers/dns/linode/linode.go
generated
vendored
25
vendor/github.com/xenolf/lego/providers/dns/linode/linode.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package linode implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Linode DNS.
|
||||
// Package linode implements a DNS provider for solving the DNS-01 challenge using Linode DNS.
|
||||
package linode
|
||||
|
||||
import (
|
||||
|
@ -9,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/timewasted/linode/dns"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -59,16 +58,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for Linode.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = apiKey
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Linode.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -108,13 +97,13 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = d.client.CreateDomainResourceTXT(zone.domainID, acme.UnFqdn(fqdn), value, d.config.TTL); err != nil {
|
||||
if _, err = d.client.CreateDomainResourceTXT(zone.domainID, dns01.UnFqdn(fqdn), value, d.config.TTL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -123,7 +112,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -155,7 +144,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -163,7 +152,7 @@ func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
|||
resourceName := strings.TrimSuffix(fqdn, "."+authZone)
|
||||
|
||||
// Query the authority zone.
|
||||
domain, err := d.client.GetDomain(acme.UnFqdn(authZone))
|
||||
domain, err := d.client.GetDomain(dns01.UnFqdn(authZone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
15
vendor/github.com/xenolf/lego/providers/dns/linodev4/linodev4.go
generated
vendored
15
vendor/github.com/xenolf/lego/providers/dns/linodev4/linodev4.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package linodev4 implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Linode DNS and Linode's APIv4
|
||||
// Package linodev4 implements a DNS provider for solving the DNS-01 challenge using Linode DNS and Linode's APIv4
|
||||
package linodev4
|
||||
|
||||
import (
|
||||
|
@ -12,7 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/linode/linodego"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
@ -115,14 +114,14 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createOpts := linodego.DomainRecordCreateOptions{
|
||||
Name: acme.UnFqdn(fqdn),
|
||||
Name: dns01.UnFqdn(fqdn),
|
||||
Target: value,
|
||||
TTLSec: d.config.TTL,
|
||||
Type: linodego.RecordTypeTXT,
|
||||
|
@ -134,7 +133,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZoneInfo(fqdn)
|
||||
if err != nil {
|
||||
|
@ -163,13 +162,13 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
func (d *DNSProvider) getHostedZoneInfo(fqdn string) (*hostedZoneInfo, error) {
|
||||
// Lookup the zone that handles the specified FQDN.
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Query the authority zone.
|
||||
data, err := json.Marshal(map[string]string{"domain": acme.UnFqdn(authZone)})
|
||||
data, err := json.Marshal(map[string]string{"domain": dns01.UnFqdn(authZone)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
52
vendor/github.com/xenolf/lego/providers/dns/mydnsjp/client.go
generated
vendored
Normal file
52
vendor/github.com/xenolf/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
|
||||
}
|
55
vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go
generated
vendored
55
vendor/github.com/xenolf/lego/providers/dns/mydnsjp/mydnsjp.go
generated
vendored
|
@ -1,17 +1,13 @@
|
|||
// Package mydnsjp implements a DNS provider for solving the DNS-01
|
||||
// challenge using MyDNS.jp.
|
||||
// Package mydnsjp implements a DNS provider for solving the DNS-01 challenge using MyDNS.jp.
|
||||
package mydnsjp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -78,7 +74,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
_, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
err := d.doRequest(domain, value, "REGIST")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mydnsjp: %v", err)
|
||||
|
@ -88,53 +84,10 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
_, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
_, value := dns01.GetRecord(domain, keyAuth)
|
||||
err := d.doRequest(domain, value, "DELETE")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mydnsjp: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
10
vendor/github.com/xenolf/lego/providers/dns/namecheap/client.go
generated
vendored
10
vendor/github.com/xenolf/lego/providers/dns/namecheap/client.go
generated
vendored
|
@ -20,8 +20,8 @@ type Record struct {
|
|||
TTL string `xml:",attr"`
|
||||
}
|
||||
|
||||
// apierror describes an error record in a namecheap API response.
|
||||
type apierror struct {
|
||||
// apiError describes an error record in a namecheap API response.
|
||||
type apiError struct {
|
||||
Number int `xml:",attr"`
|
||||
Description string `xml:",innerxml"`
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ type apierror struct {
|
|||
type setHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apierror `xml:"Errors>Error"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Result struct {
|
||||
IsSuccess string `xml:",attr"`
|
||||
} `xml:"CommandResponse>DomainDNSSetHostsResult"`
|
||||
|
@ -38,13 +38,13 @@ type setHostsResponse struct {
|
|||
type getHostsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Status string `xml:"Status,attr"`
|
||||
Errors []apierror `xml:"Errors>Error"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Hosts []Record `xml:"CommandResponse>DomainDNSGetHostsResult>host"`
|
||||
}
|
||||
|
||||
type getTldsResponse struct {
|
||||
XMLName xml.Name `xml:"ApiResponse"`
|
||||
Errors []apierror `xml:"Errors>Error"`
|
||||
Errors []apiError `xml:"Errors>Error"`
|
||||
Result []struct {
|
||||
Name string `xml:",attr"`
|
||||
} `xml:"CommandResponse>Tlds>Tld"`
|
||||
|
|
22
vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go
generated
vendored
22
vendor/github.com/xenolf/lego/providers/dns/namecheap/namecheap.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package namecheap implements a DNS provider for solving the DNS-01
|
||||
// challenge using namecheap DNS.
|
||||
// Package namecheap implements a DNS provider for solving the DNS-01 challenge using namecheap DNS.
|
||||
package namecheap
|
||||
|
||||
import (
|
||||
|
@ -11,7 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
@ -64,7 +63,7 @@ func NewDefaultConfig() *Config {
|
|||
return &Config{
|
||||
BaseURL: defaultBaseURL,
|
||||
Debug: env.GetOrDefaultBool("NAMECHEAP_DEBUG", false),
|
||||
TTL: env.GetOrDefaultInt("NAMECHEAP_TTL", 120),
|
||||
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{
|
||||
|
@ -95,17 +94,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for namecheap.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(apiUser, apiKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIUser = apiUser
|
||||
config.APIKey = apiKey
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for namecheap.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -233,7 +221,7 @@ func getClientIP(client *http.Client, debug bool) (addr string, err error) {
|
|||
// 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 = acme.UnFqdn(domain)
|
||||
domain = dns01.UnFqdn(domain)
|
||||
parts := strings.Split(domain, ".")
|
||||
|
||||
// Find the longest matching TLD.
|
||||
|
@ -256,7 +244,7 @@ func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, e
|
|||
host = strings.Join(parts[:longest-1], ".")
|
||||
}
|
||||
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
return &challenge{
|
||||
domain: domain,
|
||||
|
|
27
vendor/github.com/xenolf/lego/providers/dns/namedotcom/namedotcom.go
generated
vendored
27
vendor/github.com/xenolf/lego/providers/dns/namedotcom/namedotcom.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package namedotcom implements a DNS provider for solving the DNS-01 challenge
|
||||
// using Name.com's DNS service.
|
||||
// Package namedotcom implements a DNS provider for solving the DNS-01 challenge using Name.com's DNS service.
|
||||
package namedotcom
|
||||
|
||||
import (
|
||||
|
@ -10,7 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/namedotcom/go/namecom"
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
||||
|
@ -32,8 +31,8 @@ type Config struct {
|
|||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NAMECOM_TTL", minTTL),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NAMECOM_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NAMECOM_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -63,18 +62,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for namedotcom.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(username, apiToken, server string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.Username = username
|
||||
config.APIToken = apiToken
|
||||
config.Server = server
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for namedotcom.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -105,7 +92,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
request := &namecom.Record{
|
||||
DomainName: domain,
|
||||
|
@ -125,7 +112,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
records, err := d.getRecords(domain)
|
||||
if err != nil {
|
||||
|
@ -175,7 +162,7 @@ func (d *DNSProvider) getRecords(domain string) ([]*namecom.Record, error) {
|
|||
}
|
||||
|
||||
func (d *DNSProvider) extractRecordName(fqdn, domain string) string {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
if idx := strings.Index(name, "."+domain); idx != -1 {
|
||||
return name[:idx]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package netcup
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -7,8 +7,6 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
)
|
||||
|
||||
// defaultBaseURL for reaching the jSON-based API-Endpoint of netcup
|
||||
|
@ -246,7 +244,6 @@ func (c *Client) doRequest(payload interface{}, responseData interface{}) error
|
|||
|
||||
req.Close = true
|
||||
req.Header.Set("content-type", "application/json")
|
||||
req.Header.Set("User-Agent", acme.UserAgent)
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
|
@ -316,3 +313,15 @@ func decodeResponseMsg(resp *http.Response) (*ResponseMsg, error) {
|
|||
|
||||
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")
|
||||
}
|
77
vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go
generated
vendored
77
vendor/github.com/xenolf/lego/providers/dns/netcup/netcup.go
generated
vendored
|
@ -8,7 +8,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/providers/dns/netcup/internal"
|
||||
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
)
|
||||
|
@ -27,7 +29,7 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NETCUP_TTL", 120),
|
||||
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{
|
||||
|
@ -38,7 +40,7 @@ func NewDefaultConfig() *Config {
|
|||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *Client
|
||||
client *internal.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
|
@ -59,25 +61,13 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for netcup.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(customer, key, password string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.Customer = customer
|
||||
config.Key = key
|
||||
config.Password = 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 := NewClient(config.Customer, config.Key, config.Password)
|
||||
client, err := internal.NewClient(config.Customer, config.Key, config.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
@ -89,9 +79,9 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domainName, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
|
||||
}
|
||||
|
@ -109,9 +99,14 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
}()
|
||||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
record := createTxtRecord(hostname, value, d.config.TTL)
|
||||
record := internal.DNSRecord{
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Destination: value,
|
||||
TTL: d.config.TTL,
|
||||
}
|
||||
|
||||
zone = acme.UnFqdn(zone)
|
||||
zone = dns01.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
|
@ -131,9 +126,9 @@ func (d *DNSProvider) Present(domainName, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domainName, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domainName, keyAuth)
|
||||
|
||||
zone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
zone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: failed to find DNSZone, %v", err)
|
||||
}
|
||||
|
@ -152,23 +147,27 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
|||
|
||||
hostname := strings.Replace(fqdn, "."+zone, "", 1)
|
||||
|
||||
zone = acme.UnFqdn(zone)
|
||||
zone = dns01.UnFqdn(zone)
|
||||
|
||||
records, err := d.client.GetDNSRecords(zone, sessionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
||||
record := createTxtRecord(hostname, value, 0)
|
||||
record := internal.DNSRecord{
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Destination: value,
|
||||
}
|
||||
|
||||
idx, err := getDNSRecordIdx(records, record)
|
||||
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, []DNSRecord{records[idx]})
|
||||
err = d.client.UpdateDNSRecord(sessionID, zone, []internal.DNSRecord{records[idx]})
|
||||
if err != nil {
|
||||
return fmt.Errorf("netcup: %v", err)
|
||||
}
|
||||
|
@ -181,29 +180,3 @@ func (d *DNSProvider) CleanUp(domainName, token, keyAuth string) error {
|
|||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// createTxtRecord uses the supplied values to return a DNSRecord of type TXT for the dns-01 challenge
|
||||
func createTxtRecord(hostname, value string, ttl int) DNSRecord {
|
||||
return DNSRecord{
|
||||
ID: 0,
|
||||
Hostname: hostname,
|
||||
RecordType: "TXT",
|
||||
Priority: "",
|
||||
Destination: value,
|
||||
DeleteRecord: false,
|
||||
State: "",
|
||||
TTL: ttl,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// Package nifcloud implements a DNS provider for solving the DNS-01 challenge
|
||||
// using NIFCLOUD DNS.
|
||||
package nifcloud
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -17,7 +15,8 @@ import (
|
|||
const (
|
||||
defaultBaseURL = "https://dns.api.cloud.nifty.com"
|
||||
apiVersion = "2012-12-12N2013-12-16"
|
||||
xmlNs = "https://route53.amazonaws.com/doc/2012-12-12/"
|
||||
// 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.
|
55
vendor/github.com/xenolf/lego/providers/dns/nifcloud/nifcloud.go
generated
vendored
55
vendor/github.com/xenolf/lego/providers/dns/nifcloud/nifcloud.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package nifcloud implements a DNS provider for solving the DNS-01 challenge
|
||||
// using NIFCLOUD DNS.
|
||||
// Package nifcloud implements a DNS provider for solving the DNS-01 challenge using NIFCLOUD DNS.
|
||||
package nifcloud
|
||||
|
||||
import (
|
||||
|
@ -8,8 +7,11 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/providers/dns/nifcloud/internal"
|
||||
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"github.com/xenolf/lego/platform/wait"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
|
@ -26,9 +28,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NIFCLOUD_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NIFCLOUD_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NIFCLOUD_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -37,7 +39,7 @@ func NewDefaultConfig() *Config {
|
|||
|
||||
// DNSProvider implements the acme.ChallengeProvider interface
|
||||
type DNSProvider struct {
|
||||
client *Client
|
||||
client *internal.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
|
@ -58,26 +60,13 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for NIFCLOUD.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(httpClient *http.Client, endpoint, accessKey, secretKey string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.HTTPClient = httpClient
|
||||
config.BaseURL = endpoint
|
||||
config.AccessKey = accessKey
|
||||
config.SecretKey = secretKey
|
||||
|
||||
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 := NewClient(config.AccessKey, config.SecretKey)
|
||||
client, err := internal.NewClient(config.AccessKey, config.SecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("nifcloud: %v", err)
|
||||
}
|
||||
|
@ -95,7 +84,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record using the specified parameters
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("CREATE", fqdn, value, domain, d.config.TTL)
|
||||
if err != nil {
|
||||
|
@ -106,7 +95,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
err := d.changeRecord("DELETE", fqdn, value, domain, d.config.TTL)
|
||||
if err != nil {
|
||||
|
@ -122,22 +111,22 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
}
|
||||
|
||||
func (d *DNSProvider) changeRecord(action, fqdn, value, domain string, ttl int) error {
|
||||
name := acme.UnFqdn(fqdn)
|
||||
name := dns01.UnFqdn(fqdn)
|
||||
|
||||
reqParams := ChangeResourceRecordSetsRequest{
|
||||
XMLNs: xmlNs,
|
||||
ChangeBatch: ChangeBatch{
|
||||
reqParams := internal.ChangeResourceRecordSetsRequest{
|
||||
XMLNs: internal.XMLNs,
|
||||
ChangeBatch: internal.ChangeBatch{
|
||||
Comment: "Managed by Lego",
|
||||
Changes: Changes{
|
||||
Change: []Change{
|
||||
Changes: internal.Changes{
|
||||
Change: []internal.Change{
|
||||
{
|
||||
Action: action,
|
||||
ResourceRecordSet: ResourceRecordSet{
|
||||
ResourceRecordSet: internal.ResourceRecordSet{
|
||||
Name: name,
|
||||
Type: "TXT",
|
||||
TTL: ttl,
|
||||
ResourceRecords: ResourceRecords{
|
||||
ResourceRecord: []ResourceRecord{
|
||||
ResourceRecords: internal.ResourceRecords{
|
||||
ResourceRecord: []internal.ResourceRecord{
|
||||
{
|
||||
Value: value,
|
||||
},
|
||||
|
@ -157,7 +146,7 @@ func (d *DNSProvider) changeRecord(action, fqdn, value, domain string, ttl int)
|
|||
|
||||
statusID := resp.ChangeInfo.ID
|
||||
|
||||
return acme.WaitFor(120*time.Second, 4*time.Second, func() (bool, error) {
|
||||
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)
|
||||
|
|
33
vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go
generated
vendored
33
vendor/github.com/xenolf/lego/providers/dns/ns1/ns1.go
generated
vendored
|
@ -1,5 +1,4 @@
|
|||
// Package ns1 implements a DNS provider for solving the DNS-01 challenge
|
||||
// using NS1 DNS.
|
||||
// Package ns1 implements a DNS provider for solving the DNS-01 challenge using NS1 DNS.
|
||||
package ns1
|
||||
|
||||
import (
|
||||
|
@ -9,7 +8,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/challenge/dns01"
|
||||
"github.com/xenolf/lego/log"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"gopkg.in/ns1/ns1-go.v2/rest"
|
||||
|
@ -28,9 +27,9 @@ type Config struct {
|
|||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("NS1_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("NS1_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("NS1_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
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),
|
||||
},
|
||||
|
@ -57,16 +56,6 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderCredentials uses the supplied credentials
|
||||
// to return a DNSProvider instance configured for NS1.
|
||||
// Deprecated
|
||||
func NewDNSProviderCredentials(key string) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.APIKey = key
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for NS1.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
|
@ -84,20 +73,20 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
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, acme.UnFqdn(fqdn), "TXT")
|
||||
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, acme.UnFqdn(fqdn), "TXT")
|
||||
record = dns.NewRecord(zone.Zone, dns01.UnFqdn(fqdn), "TXT")
|
||||
record.TTL = d.config.TTL
|
||||
record.Answers = []*dns.Answer{{Rdata: []string{value}}}
|
||||
|
||||
|
@ -128,14 +117,14 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
// CleanUp removes the TXT record matching the specified parameters.
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||
|
||||
zone, err := d.getHostedZone(fqdn)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ns1: %v", err)
|
||||
}
|
||||
|
||||
name := acme.UnFqdn(fqdn)
|
||||
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)
|
||||
|
@ -164,7 +153,7 @@ func (d *DNSProvider) getHostedZone(fqdn string) (*dns.Zone, error) {
|
|||
}
|
||||
|
||||
func getAuthZone(fqdn string) (string, error) {
|
||||
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||
authZone, err := dns01.FindZoneByFqdn(fqdn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
211
vendor/github.com/xenolf/lego/providers/dns/otc/client.go
generated
vendored
211
vendor/github.com/xenolf/lego/providers/dns/otc/client.go
generated
vendored
|
@ -1,5 +1,14 @@
|
|||
package otc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type recordset struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
|
@ -41,14 +50,20 @@ type loginResponse struct {
|
|||
}
|
||||
|
||||
type endpointResponse struct {
|
||||
Token struct {
|
||||
Catalog []struct {
|
||||
Type string `json:"type"`
|
||||
Endpoints []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"endpoints"`
|
||||
} `json:"catalog"`
|
||||
} `json:"token"`
|
||||
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 {
|
||||
|
@ -66,3 +81,183 @@ type recordSet struct {
|
|||
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
|
||||
}
|
||||
|
|
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